Skip to content

Commit 05e65d7

Browse files
authored
Merge pull request #381 from AppQuality/develop
release-20250409
2 parents 03f1ce0 + ae859a9 commit 05e65d7

26 files changed

Lines changed: 5973 additions & 2295 deletions

File tree

.github/workflows/add-test-diff.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- name: Set up Node.js
1515
uses: actions/setup-node@v2
1616
with:
17-
node-version: 14
17+
node-version: 18
1818

1919
- name: Install dependencies
2020
run: |

Dockerfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:18-alpine3.16 AS node
1+
FROM node:18.17.1-alpine AS node
22
FROM alpine:3.16 as base
33

44
COPY --from=node /usr/lib /usr/lib
@@ -20,8 +20,7 @@ COPY . .
2020
RUN yarn global add npm-run-all
2121
RUN yarn build
2222

23-
24-
FROM node:18-alpine3.16 AS web
23+
FROM node:18.17.1-alpine AS web
2524

2625
COPY --from=base /dist /app/build
2726
COPY package*.json /app/

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"author": "",
2222
"license": "ISC",
2323
"dependencies": {
24-
"@appquality/tryber-database": "^0.42.2",
24+
"@appquality/tryber-database": "^0.42.27",
2525
"@appquality/wp-auth": "^1.0.7",
2626
"@googlemaps/google-maps-services-js": "^3.3.7",
2727
"@sendgrid/mail": "^7.6.0",
@@ -75,6 +75,7 @@
7575
"husky": "^7.0.4",
7676
"jest": "^28.1.0",
7777
"jest-cli": "^28.1.0",
78+
"node-gyp": "^11.1.0",
7879
"openapi-typescript": "^5.1.1",
7980
"prettier": "^2.5.1",
8081
"rimraf": "^3.0.2",

src/features/tranferwise/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { AxiosError } from "@googlemaps/google-maps-services-js/node_modules/axios";
22
import axios from "axios";
33
import dotenv from "dotenv";
4-
54
import signRequest from "./signRequest";
65
import stringToUuid from "./stringToUuid";
76

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PageImporter } from "./PageImporter";
2+
3+
export class ManualPageImporter extends PageImporter {
4+
public async updateMetaWithCampaignId(campaignId: number) {
5+
const manualLangs = this.getTranslationLanguages();
6+
for (const { lang } of manualLangs) {
7+
await this.updateMeta(lang, {
8+
man_campaign_id: campaignId.toString(),
9+
});
10+
}
11+
}
12+
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import { tryber } from "@src/features/database";
2+
import crypto from "crypto";
3+
import { serialize, unserialize } from "php-serialize";
4+
5+
export class PageImporter {
6+
private oldCampaignId: number = 0;
7+
protected fromId: number = 0;
8+
private withTranslations: boolean = false;
9+
10+
private newId: number = 0;
11+
12+
private translations: { [key: string]: number } = {};
13+
14+
constructor({
15+
campaignId,
16+
pageId,
17+
withTranslations,
18+
}: {
19+
campaignId: number;
20+
pageId: number;
21+
withTranslations?: boolean;
22+
}) {
23+
this.oldCampaignId = campaignId;
24+
this.fromId = pageId;
25+
this.withTranslations = withTranslations ?? false;
26+
}
27+
28+
async createPage() {
29+
const page = await tryber.tables.WpPosts.do()
30+
.select()
31+
.where("ID", this.fromId)
32+
.first();
33+
34+
if (!page) throw new Error("Page not found");
35+
36+
const { ID, ...rest } = page;
37+
38+
const newPage = await tryber.tables.WpPosts.do()
39+
.insert({
40+
...rest,
41+
post_name: "",
42+
post_status: "draft",
43+
})
44+
.returning("ID");
45+
46+
const meta = await tryber.tables.WpPostmeta.do()
47+
.select()
48+
.where("post_id", ID);
49+
50+
if (meta.length) {
51+
await tryber.tables.WpPostmeta.do().insert(
52+
meta.map((metaItem) => {
53+
const { meta_id, ...rest } = metaItem;
54+
return {
55+
...rest,
56+
post_id: newPage[0].ID ?? newPage[0],
57+
};
58+
})
59+
);
60+
}
61+
62+
const newId = newPage[0].ID ?? newPage[0];
63+
this.newId = newId;
64+
65+
if (this.withTranslations) {
66+
await this.linkTranslations();
67+
}
68+
return newId;
69+
}
70+
71+
protected getTranslationLanguages() {
72+
return Object.keys(this.translations).map((lang) => ({
73+
lang,
74+
}));
75+
}
76+
77+
protected async updateTitle(lang: string, title: (old: string) => string) {
78+
if (!this.translations[lang]) return;
79+
const oldTitle = await tryber.tables.WpPosts.do()
80+
.select("post_title")
81+
.where("ID", this.translations[lang])
82+
.first();
83+
if (!oldTitle) return;
84+
const newTitle = title(oldTitle.post_title);
85+
await tryber.tables.WpPosts.do()
86+
.update({ post_title: newTitle })
87+
.where("ID", this.translations[lang]);
88+
}
89+
90+
protected async updateMeta(lang: string, meta: { [key: string]: string }) {
91+
if (!this.translations[lang]) return;
92+
for (const key in meta) {
93+
await tryber.tables.WpPostmeta.do()
94+
.update({ meta_value: meta[key] })
95+
.where("post_id", this.translations[lang])
96+
.where("meta_key", key);
97+
}
98+
}
99+
100+
private async linkTranslations() {
101+
const defaultLanguage = await this.getDefaultLanguage();
102+
if (!defaultLanguage) return;
103+
104+
const languages = await this.getPolylangLanguages();
105+
const manualTranslations: { [key: string]: number } = {
106+
[defaultLanguage]: this.newId,
107+
};
108+
const parsedTranslations = await this.getPageTranslationIds({
109+
pageId: this.fromId,
110+
defaultLanguage,
111+
});
112+
for (const t in parsedTranslations) {
113+
const translation = new PageImporter({
114+
campaignId: this.oldCampaignId,
115+
pageId: parsedTranslations[t],
116+
});
117+
const newTranslationId = await translation.createPage();
118+
if (newTranslationId) manualTranslations[t] = newTranslationId;
119+
}
120+
await this.createPolylangTranslations({
121+
id: this.newId,
122+
translations: manualTranslations,
123+
languages,
124+
});
125+
this.translations = manualTranslations;
126+
}
127+
128+
private async getDefaultLanguage() {
129+
const polylangOptions = await tryber.tables.WpOptions.do()
130+
.select("option_value")
131+
.where("option_name", "polylang")
132+
.first();
133+
134+
if (!polylangOptions) return false;
135+
let parsedOptions: { [key: string]: any } = {};
136+
try {
137+
parsedOptions = unserialize(polylangOptions.option_value);
138+
} catch (e) {
139+
return false;
140+
}
141+
if (!("default_lang" in parsedOptions)) return false;
142+
return parsedOptions.default_lang;
143+
}
144+
145+
private async getPolylangLanguages() {
146+
const languages = await tryber.tables.WpTermTaxonomy.do()
147+
.select("term_id", "description")
148+
.where("taxonomy", "language");
149+
150+
let parsedLanguages: { [key: string]: number } = {};
151+
for (const language of languages) {
152+
try {
153+
const lang = unserialize(language.description);
154+
if ("locale" in lang)
155+
parsedLanguages[lang.locale.split("_")[0]] = language.term_id;
156+
} catch (e) {
157+
continue;
158+
}
159+
}
160+
return parsedLanguages;
161+
}
162+
163+
private async createPolylangTranslations({
164+
id,
165+
translations,
166+
languages,
167+
}: {
168+
id: number;
169+
translations: { [key: string]: number };
170+
languages: { [key: string]: any };
171+
}) {
172+
const term_name = crypto
173+
.createHash("md5")
174+
.update(id.toString())
175+
.digest("hex");
176+
const term = await tryber.tables.WpTerms.do()
177+
.insert({
178+
name: `pll_${term_name}`,
179+
slug: `pll_${term_name}`,
180+
})
181+
.returning("term_id");
182+
183+
const term_id = term[0].term_id ?? term[0];
184+
185+
await tryber.tables.WpTermTaxonomy.do().insert({
186+
term_id: term_id,
187+
term_taxonomy_id: term_id,
188+
taxonomy: "post_translations",
189+
description: serialize(translations),
190+
});
191+
192+
for (const trans of Object.keys(translations)) {
193+
const transId = translations[trans];
194+
const langId = trans in languages ? languages[trans] : false;
195+
if (langId) {
196+
await tryber.tables.WpTermRelationships.do().insert({
197+
object_id: transId,
198+
term_taxonomy_id: langId,
199+
});
200+
await tryber.tables.WpTermRelationships.do().insert({
201+
object_id: transId,
202+
term_taxonomy_id: term_id,
203+
});
204+
}
205+
}
206+
}
207+
208+
private async getPageTranslationIds({
209+
pageId,
210+
defaultLanguage,
211+
}: {
212+
pageId: number;
213+
defaultLanguage: string;
214+
}) {
215+
const translations = await tryber.tables.WpTermRelationships.do()
216+
.select("object_id", "description")
217+
.join(
218+
"wp_term_taxonomy",
219+
"wp_term_taxonomy.term_taxonomy_id",
220+
"wp_term_relationships.term_taxonomy_id"
221+
)
222+
.where("object_id", pageId)
223+
.where("taxonomy", "post_translations")
224+
.first();
225+
226+
if (!translations) return {};
227+
228+
let parsedTranslations: { [key: string]: any } = {};
229+
try {
230+
parsedTranslations = unserialize(translations.description);
231+
} catch (e) {
232+
return;
233+
}
234+
235+
if (defaultLanguage in parsedTranslations)
236+
delete parsedTranslations[defaultLanguage];
237+
return parsedTranslations;
238+
}
239+
240+
public async updateTitleWithCampaignId(campaignId: number) {
241+
const langs = this.getTranslationLanguages();
242+
for (const { lang } of langs) {
243+
await this.updateTitle(lang, (old) =>
244+
old.replace(this.oldCampaignId.toString(), campaignId.toString())
245+
);
246+
}
247+
}
248+
249+
public async updateMetaWithCampaignId(campaignId: number) {}
250+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PageImporter } from "./PageImporter";
2+
3+
export class PreviewPageImporter extends PageImporter {
4+
public async updateMetaWithCampaignId(campaignId: number) {
5+
const manualLangs = this.getTranslationLanguages();
6+
for (const { lang } of manualLangs) {
7+
await this.updateMeta(lang, {
8+
preview_campaign_id: campaignId.toString(),
9+
});
10+
}
11+
}
12+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { tryber } from "@src/features/database";
2+
import { ManualPageImporter } from "./ManualPageImporter";
3+
import { PreviewPageImporter } from "./PreviewPageImporter";
4+
5+
const importPages = async (from: number, to: number) => {
6+
const campaign = await tryber.tables.WpAppqEvdCampaign.do()
7+
.select("page_manual_id", "page_preview_id")
8+
.where("id", from);
9+
10+
if (!campaign.length) return;
11+
12+
const { page_manual_id, page_preview_id } = campaign[0];
13+
14+
const manual = new ManualPageImporter({
15+
campaignId: from,
16+
pageId: page_manual_id,
17+
withTranslations: true,
18+
});
19+
const newManId = await manual.createPage();
20+
await tryber.tables.WpAppqEvdCampaign.do()
21+
.update({
22+
page_manual_id: newManId,
23+
})
24+
.where("id", to);
25+
manual.updateTitleWithCampaignId(to);
26+
manual.updateMetaWithCampaignId(to);
27+
28+
const preview = new PreviewPageImporter({
29+
campaignId: from,
30+
pageId: page_preview_id,
31+
withTranslations: true,
32+
});
33+
const newPrevId = await preview.createPage();
34+
await tryber.tables.WpAppqEvdCampaign.do()
35+
.update({
36+
page_preview_id: newPrevId,
37+
})
38+
.where("id", to);
39+
preview.updateTitleWithCampaignId(to);
40+
preview.updateMetaWithCampaignId(to);
41+
};
42+
43+
export { importPages };

0 commit comments

Comments
 (0)