@localazy/strapi-plugin 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/qa.yml +25 -0
- package/.github/workflows/release.yml +44 -0
- package/CHANGELOG.md +15 -0
- package/admin/src/config/index.js +0 -2
- package/admin/src/index.js +1 -1
- package/admin/src/modules/@common/models/supported-custom-field-plugins.js +1 -0
- package/admin/src/modules/@common/services/plugin.service.js +16 -0
- package/admin/src/modules/@common/services/product-analytics-service.js +42 -23
- package/admin/src/modules/login/components/LoginButton/index.js +7 -7
- package/admin/src/plugins/generic-connector-client.js +9 -0
- package/package.json +9 -2
- package/server/controllers/index.js +0 -2
- package/server/controllers/localazy-auth-controller.js +11 -35
- package/server/controllers/strapi-controller.js +8 -0
- package/server/lifecycles/deprecate-event-entry-in-localazy-hook.js +16 -12
- package/server/lifecycles/upload-event-entry-to-localazy-hook.js +11 -6
- package/server/models/supported-custom-field-plugins.js +1 -0
- package/server/routes/strapi.js +8 -0
- package/server/services/index.js +0 -4
- package/server/services/localazy-pubapi-service.js +7 -7
- package/server/services/localazy-transfer-download-service.js +7 -9
- package/server/services/localazy-transfer-upload-service.js +12 -8
- package/server/services/localazy-upload-service.js +24 -74
- package/server/services/strapi-service.js +3 -0
- package/server/utils/{get-localazy-api.js → localazy-api-client-factory.js} +4 -4
- package/server/utils/localazy-generic-connector-client-factory.js +15 -0
- package/.github/workflows/.build.yml +0 -24
- package/admin/src/plugins/analytics-service.js +0 -14
- package/admin/src/utils/createPluginConnectorAxiosInstance.js +0 -45
- package/server/controllers/my-controller.js +0 -10
- package/server/services/localazy-auth-service.js +0 -38
- package/server/services/localazy-download-service.js +0 -54
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: QA checks
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint:
|
|
10
|
+
name: Tests
|
|
11
|
+
runs-on: [self-hosted, Linux]
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout repository
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Node
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 16
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: npm ci
|
|
23
|
+
|
|
24
|
+
- name: Run tests
|
|
25
|
+
run: npm run test
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Release CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
ci:
|
|
14
|
+
name: CI Init
|
|
15
|
+
runs-on: [self-hosted, Linux]
|
|
16
|
+
outputs:
|
|
17
|
+
action: ${{ steps.init.outputs.action }}
|
|
18
|
+
steps:
|
|
19
|
+
- id: init
|
|
20
|
+
uses: localazy/release/init@v2
|
|
21
|
+
|
|
22
|
+
prepare:
|
|
23
|
+
name: Prepare Release PR
|
|
24
|
+
needs: ci
|
|
25
|
+
if: needs.ci.outputs.action == 'prepare'
|
|
26
|
+
runs-on: [self-hosted, Linux]
|
|
27
|
+
steps:
|
|
28
|
+
- uses: localazy/release/prepare@v2
|
|
29
|
+
with:
|
|
30
|
+
app-id: ${{ secrets.AUTH_APP_ID }}
|
|
31
|
+
app-key: ${{ secrets.AUTH_APP_KEY }}
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
name: Publish Release
|
|
35
|
+
needs: ci
|
|
36
|
+
if: needs.ci.outputs.action == 'publish'
|
|
37
|
+
runs-on: [self-hosted, Linux]
|
|
38
|
+
steps:
|
|
39
|
+
- uses: localazy/release/publish@v2
|
|
40
|
+
with:
|
|
41
|
+
app-id: ${{ secrets.AUTH_APP_ID }}
|
|
42
|
+
app-key: ${{ secrets.AUTH_APP_KEY }}
|
|
43
|
+
npm-publish: public
|
|
44
|
+
npm-token: ${{ secrets.NPM_AUTH_TOKEN_PUBLIC }}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## <small>0.2.6 (2024-05-09)</small>
|
|
2
|
+
|
|
3
|
+
* 🔧 conf: update .nvmrc node version to v18 ([78f1e4a](https://github.com/localazy/strapi-plugin/commit/78f1e4a))
|
|
4
|
+
* 0.2.3 ([70566df](https://github.com/localazy/strapi-plugin/commit/70566df))
|
|
5
|
+
* 0.2.4 (#49) ([17f864c](https://github.com/localazy/strapi-plugin/commit/17f864c)), closes [#49](https://github.com/localazy/strapi-plugin/issues/49)
|
|
6
|
+
* Add release CI (#52) ([770ea8a](https://github.com/localazy/strapi-plugin/commit/770ea8a)), closes [#52](https://github.com/localazy/strapi-plugin/issues/52)
|
|
7
|
+
* Bump @strapi/strapi from 4.5.6 to 4.15.0 (#42) ([7c4f5f7](https://github.com/localazy/strapi-plugin/commit/7c4f5f7)), closes [#42](https://github.com/localazy/strapi-plugin/issues/42)
|
|
8
|
+
* Bump browserify-sign from 4.2.1 to 4.2.2 (#43) ([1e2900e](https://github.com/localazy/strapi-plugin/commit/1e2900e)), closes [#43](https://github.com/localazy/strapi-plugin/issues/43)
|
|
9
|
+
* Bump http-cache-semantics from 4.1.0 to 4.1.1 (#44) ([3abdd42](https://github.com/localazy/strapi-plugin/commit/3abdd42)), closes [#44](https://github.com/localazy/strapi-plugin/issues/44)
|
|
10
|
+
* LOC-1015 Strapi plugin does not work on certain versions (#48) ([5f55f1b](https://github.com/localazy/strapi-plugin/commit/5f55f1b)), closes [#48](https://github.com/localazy/strapi-plugin/issues/48)
|
|
11
|
+
* LOC-1056 - Add support for the unofficial CKEditor5 plugin (#50) ([dddaebc](https://github.com/localazy/strapi-plugin/commit/dddaebc)), closes [#50](https://github.com/localazy/strapi-plugin/issues/50)
|
|
12
|
+
* LOC-1096 - Fix Analytics User Identification Within Integrations (#51) ([6000259](https://github.com/localazy/strapi-plugin/commit/6000259)), closes [#51](https://github.com/localazy/strapi-plugin/issues/51)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
package/admin/src/index.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* eslint-disable no-useless-catch */
|
|
2
|
+
import createAxiosInstance from "../../../utils/createAxiosInstance";
|
|
3
|
+
|
|
4
|
+
const axiosInstance = createAxiosInstance();
|
|
5
|
+
|
|
6
|
+
export default class PluginService {
|
|
7
|
+
static async getPluginVersion() {
|
|
8
|
+
try {
|
|
9
|
+
const result = await axiosInstance.get("/strapi/version");
|
|
10
|
+
|
|
11
|
+
return result.data;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw e;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import GenericConnectorClient from "../../../plugins/generic-connector-client";
|
|
2
|
+
import PluginService from "./plugin.service";
|
|
2
3
|
|
|
3
4
|
export default class ProductAnalyticsService {
|
|
4
5
|
static async trackAppConnected(userId, project, params = {}) {
|
|
5
6
|
try {
|
|
6
|
-
const data = this.buildData(
|
|
7
|
-
await
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const data = await this.buildData("Strapi Connected", project, params);
|
|
8
|
+
await GenericConnectorClient.analytics.track({
|
|
9
|
+
event: data.event,
|
|
10
|
+
data: {
|
|
11
|
+
...data.data,
|
|
12
|
+
userId,
|
|
13
|
+
orgId: project.orgId,
|
|
14
|
+
category: "Project",
|
|
15
|
+
}
|
|
11
16
|
});
|
|
12
17
|
} catch (e) {
|
|
13
18
|
console.warn(e);
|
|
@@ -16,11 +21,15 @@ export default class ProductAnalyticsService {
|
|
|
16
21
|
|
|
17
22
|
static async trackAppDisconnected(userId, project, params = {}) {
|
|
18
23
|
try {
|
|
19
|
-
const data = this.buildData(
|
|
20
|
-
await
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const data = await this.buildData("Strapi Disconnected", project, params);
|
|
25
|
+
await GenericConnectorClient.analytics.track({
|
|
26
|
+
event: data.event,
|
|
27
|
+
data: {
|
|
28
|
+
...data.data,
|
|
29
|
+
userId,
|
|
30
|
+
orgId: project.orgId,
|
|
31
|
+
category: "Project",
|
|
32
|
+
}
|
|
24
33
|
});
|
|
25
34
|
} catch (e) {
|
|
26
35
|
console.warn(e);
|
|
@@ -29,11 +38,15 @@ export default class ProductAnalyticsService {
|
|
|
29
38
|
|
|
30
39
|
static async trackUploadToLocalazy(userId, project, params = {}) {
|
|
31
40
|
try {
|
|
32
|
-
const data = this.buildData(
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const data = await this.buildData("Strapi Upload", project, params);
|
|
42
|
+
await GenericConnectorClient.analytics.track({
|
|
43
|
+
event: data.event,
|
|
44
|
+
data: {
|
|
45
|
+
...data.data,
|
|
46
|
+
userId,
|
|
47
|
+
orgId: project.orgId,
|
|
48
|
+
category: "Project",
|
|
49
|
+
}
|
|
37
50
|
});
|
|
38
51
|
} catch (e) {
|
|
39
52
|
console.warn(e);
|
|
@@ -42,24 +55,30 @@ export default class ProductAnalyticsService {
|
|
|
42
55
|
|
|
43
56
|
static async trackDownloadToStrapi(userId, project, params = {}) {
|
|
44
57
|
try {
|
|
45
|
-
const data = this.buildData(
|
|
46
|
-
await
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
const data = await this.buildData("Strapi Download", project, params);
|
|
59
|
+
await GenericConnectorClient.analytics.track({
|
|
60
|
+
event: data.event,
|
|
61
|
+
data: {
|
|
62
|
+
...data.data,
|
|
63
|
+
userId,
|
|
64
|
+
orgId: project.orgId,
|
|
65
|
+
category: "Project",
|
|
66
|
+
}
|
|
50
67
|
});
|
|
51
68
|
} catch (e) {
|
|
52
69
|
console.warn(e);
|
|
53
70
|
}
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
static buildData(
|
|
73
|
+
static async buildData(event, project, params) {
|
|
74
|
+
const { version } = await PluginService.getPluginVersion();
|
|
75
|
+
|
|
57
76
|
return {
|
|
58
77
|
event,
|
|
59
78
|
data: {
|
|
60
|
-
"User Id": userId,
|
|
61
79
|
"Project Id": project.id,
|
|
62
80
|
"Project Name": project.name,
|
|
81
|
+
version,
|
|
63
82
|
...params,
|
|
64
83
|
},
|
|
65
84
|
};
|
|
@@ -8,6 +8,7 @@ import React, { useState } from "react";
|
|
|
8
8
|
import PropTypes from "prop-types";
|
|
9
9
|
import { Button } from "@strapi/design-system/Button";
|
|
10
10
|
import { useTranslation } from "react-i18next";
|
|
11
|
+
import { getOAuthAuthorizationUrl } from "@localazy/generic-connector-client";
|
|
11
12
|
import LocalazyLoginService from "../../services/localazy-login-service";
|
|
12
13
|
import { getStrapiDefaultLocale } from "../../../@common/utils/get-default-locale";
|
|
13
14
|
import { isoStrapiToLocalazy } from "../../../@common/utils/iso-locales-utils";
|
|
@@ -23,13 +24,12 @@ function LoginButton(props) {
|
|
|
23
24
|
const strapiDefaultLocale = await getStrapiDefaultLocale();
|
|
24
25
|
const localazyFormatLocaleCode = isoStrapiToLocalazy(strapiDefaultLocale.code);
|
|
25
26
|
const keys = await LocalazyLoginService.generateKeys();
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
const url = `${config.LOCALAZY_OAUTH_URL}?${params.toString()}`;
|
|
27
|
+
const url = getOAuthAuthorizationUrl({
|
|
28
|
+
clientId: config.LOCALAZY_OAUTH_APP_CLIENT_ID,
|
|
29
|
+
customId: keys.writeKey,
|
|
30
|
+
allowCreate: true,
|
|
31
|
+
createLocale: localazyFormatLocaleCode,
|
|
32
|
+
}, config.LOCALAZY_OAUTH_URL);
|
|
33
33
|
window.open(url);
|
|
34
34
|
|
|
35
35
|
const pollResult = await LocalazyLoginService.continuousPoll(keys.readKey);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GenericConnectorClient, Services } from "@localazy/generic-connector-client";
|
|
2
|
+
import config from "../config";
|
|
3
|
+
|
|
4
|
+
const client = new GenericConnectorClient({
|
|
5
|
+
pluginId: Services.STRAPI,
|
|
6
|
+
genericConnectorUrl: config.LOCALAZY_PLUGIN_CONNECTOR_API_URL,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export default client;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@localazy/strapi-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "The official Strapi Plugin by Localazy.",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"name": "localazy",
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@david-vaclavek/deep-keys": "^0.5.0",
|
|
43
|
-
"@localazy/
|
|
43
|
+
"@localazy/api-client": "^2.1.5",
|
|
44
|
+
"@localazy/generic-connector-client": "^0.2.3",
|
|
45
|
+
"@localazy/ts-api": "^1.1.0",
|
|
44
46
|
"chalk": "^5.3.0",
|
|
45
47
|
"fs-extra": "^11.1.1",
|
|
46
48
|
"i18next": "^22.4.9",
|
|
@@ -58,7 +60,12 @@
|
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
62
|
"@babel/plugin-transform-modules-commonjs": "^7.20.11",
|
|
63
|
+
"@localazy/conventional-changelog-preset": "^1.2.8",
|
|
61
64
|
"@strapi-community/eslint-config": "^0.4.3",
|
|
65
|
+
"conventional-changelog": "^6.0.0",
|
|
66
|
+
"conventional-changelog-cli": "^5.0.0",
|
|
67
|
+
"conventional-changelog-writer": "github:localazy/conventional-changelog-writer",
|
|
68
|
+
"conventional-recommended-bump": "^9.0.0",
|
|
62
69
|
"eslint": "^8.32.0",
|
|
63
70
|
"eslint-plugin-jest": "^27.2.1",
|
|
64
71
|
"jest": "^29.6.4"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const myController = require("./my-controller");
|
|
4
3
|
const localazyUserController = require("./localazy-user-controller");
|
|
5
4
|
const localazyAuthController = require("./localazy-auth-controller");
|
|
6
5
|
const strapiController = require("./strapi-controller");
|
|
@@ -9,7 +8,6 @@ const localazyTransferController = require("./localazy-transfer-controller");
|
|
|
9
8
|
const localazyProjectController = require("./localazy-project-controller");
|
|
10
9
|
|
|
11
10
|
module.exports = {
|
|
12
|
-
myController,
|
|
13
11
|
localazyUserController,
|
|
14
12
|
localazyAuthController,
|
|
15
13
|
strapiController,
|
|
@@ -1,46 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
const localazyGenericConnectorClientFactory = require("../utils/localazy-generic-connector-client-factory");
|
|
4
|
+
|
|
3
5
|
module.exports = {
|
|
4
6
|
async generateKeys(ctx) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.service("localazyAuthService")
|
|
8
|
-
.generateKeys();
|
|
7
|
+
const GenericConnectorApi = await localazyGenericConnectorClientFactory();
|
|
8
|
+
ctx.body = await GenericConnectorApi.public.keys();
|
|
9
9
|
},
|
|
10
10
|
|
|
11
11
|
async continuousPoll(ctx) {
|
|
12
|
-
|
|
13
|
-
const INTERVAL_PERIOD = 2000; // ms
|
|
14
|
-
const localazyAuthService = strapi
|
|
15
|
-
.plugin("localazy")
|
|
16
|
-
.service("localazyAuthService");
|
|
17
|
-
|
|
18
|
-
let counter = 1;
|
|
12
|
+
// init continuous poll
|
|
19
13
|
const { readKey } = ctx.query;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!result.completed) {
|
|
26
|
-
data = await new Promise((resolve, reject) => {
|
|
27
|
-
const pollInterval = setInterval(async () => {
|
|
28
|
-
result = await localazyAuthService.completeLogin(readKey);
|
|
29
|
-
|
|
30
|
-
if (result.completed) {
|
|
31
|
-
clearInterval(pollInterval);
|
|
32
|
-
resolve(result.data);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
counter += 1;
|
|
36
|
-
if (counter >= POLLING_LIMIT) {
|
|
37
|
-
clearInterval(pollInterval);
|
|
38
|
-
reject(new Error("Sign in attempts timed out."));
|
|
39
|
-
}
|
|
40
|
-
}, INTERVAL_PERIOD);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
14
|
+
const GenericConnectorApi = await localazyGenericConnectorClientFactory();
|
|
15
|
+
const pollResult = await GenericConnectorApi.oauth.continuousPoll({
|
|
16
|
+
readKey,
|
|
17
|
+
});
|
|
18
|
+
const pollResultData = pollResult.data;
|
|
43
19
|
|
|
44
|
-
ctx.body =
|
|
20
|
+
ctx.body = pollResultData;
|
|
45
21
|
},
|
|
46
22
|
};
|
|
@@ -16,4 +16,12 @@ module.exports = {
|
|
|
16
16
|
async postLifecycleLocalazyWebhooks(ctx) {
|
|
17
17
|
strapi.log("done");
|
|
18
18
|
},
|
|
19
|
+
async getPluginVersion(ctx) {
|
|
20
|
+
ctx.body = {
|
|
21
|
+
version: await strapi
|
|
22
|
+
.plugin("localazy")
|
|
23
|
+
.service("strapiService")
|
|
24
|
+
.getPluginVersion()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const getPickedFlattenKeysForHookEntry = require('../utils/get-picked-flatten-keys-for-hook-entry');
|
|
4
4
|
const config = require("../config").default;
|
|
5
|
-
const
|
|
5
|
+
const localazyApiClientFactory = require("../utils/localazy-api-client-factory");
|
|
6
6
|
const delay = require("../utils/delay");
|
|
7
7
|
|
|
8
8
|
module.exports = async (event) => {
|
|
@@ -20,9 +20,6 @@ module.exports = async (event) => {
|
|
|
20
20
|
const LocalazyPubApiService = strapi
|
|
21
21
|
.plugin("localazy")
|
|
22
22
|
.service("localazyPubApiService");
|
|
23
|
-
const LocalazyDownloadService = strapi
|
|
24
|
-
.plugin("localazy")
|
|
25
|
-
.service("localazyDownloadService");
|
|
26
23
|
|
|
27
24
|
const user = await LocalazyUserService.getUser();
|
|
28
25
|
|
|
@@ -37,24 +34,31 @@ module.exports = async (event) => {
|
|
|
37
34
|
return;
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
const LocalazyApi = await localazyApiClientFactory();
|
|
38
|
+
const projectKeys = await LocalazyApi.files.listKeys({
|
|
39
|
+
project: user.project.id,
|
|
40
|
+
file: strapiFile.id,
|
|
43
41
|
lang: eventEntryLocale,
|
|
44
42
|
});
|
|
45
43
|
|
|
44
|
+
if(!projectKeys) {
|
|
45
|
+
strapi.log.error(`Keys not found for file ${strapiFile.id}`);
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
const pickedFlattenKeys = Object.keys(pickedFlatten);
|
|
47
|
-
const filteredProjectKeysIds = projectKeys
|
|
52
|
+
const filteredProjectKeysIds = projectKeys
|
|
48
53
|
.filter((key) => pickedFlattenKeys.includes(key.key[0]))
|
|
49
54
|
.map((key) => key.id);
|
|
50
55
|
|
|
51
|
-
const LocalazyApi = await getLocalazyApi();
|
|
52
56
|
// call in a loop in a sequence, to avoid hitting PubAPI limits
|
|
53
57
|
for (const filteredProjectKeysId of filteredProjectKeysIds) {
|
|
54
58
|
try {
|
|
55
|
-
await LocalazyApi.
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
await LocalazyApi.keys.update({
|
|
60
|
+
project: user.project.id,
|
|
61
|
+
key: filteredProjectKeysId,
|
|
58
62
|
deprecated: 0,
|
|
59
63
|
});
|
|
60
64
|
await delay();
|
|
@@ -17,15 +17,20 @@ module.exports = async (event) => {
|
|
|
17
17
|
.plugin("localazy")
|
|
18
18
|
.service("localazyUploadService");
|
|
19
19
|
|
|
20
|
-
const chunks = LocalazyUploadService.splitToChunks(pickedFlatten);
|
|
21
20
|
const importFile = LocalazyUploadService.createImportFileRepresentation(
|
|
22
|
-
config.LOCALAZY_DEFAULT_FILE_NAME,
|
|
23
|
-
config.LOCALAZY_DEFAULT_FILE_PATH,
|
|
24
|
-
config.LOCALAZY_DEFAULT_FILE_EXTENSION,
|
|
25
21
|
eventEntryLocale,
|
|
26
|
-
|
|
22
|
+
pickedFlatten
|
|
27
23
|
);
|
|
28
24
|
|
|
29
|
-
const
|
|
25
|
+
const uploadConfig = {
|
|
26
|
+
contentOptions: {
|
|
27
|
+
type: config.LOCALAZY_DEFAULT_FILE_EXTENSION,
|
|
28
|
+
},
|
|
29
|
+
fileOptions: {
|
|
30
|
+
name: config.LOCALAZY_DEFAULT_FILE_NAME,
|
|
31
|
+
path: config.LOCALAZY_DEFAULT_FILE_PATH,
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const result = await LocalazyUploadService.upload(importFile, uploadConfig);
|
|
30
35
|
return result;
|
|
31
36
|
}
|
package/server/routes/strapi.js
CHANGED
package/server/services/index.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const localazyUserService = require("./localazy-user-service");
|
|
4
|
-
const localazyAuthService = require("./localazy-auth-service");
|
|
5
4
|
const strapiService = require("./strapi-service");
|
|
6
5
|
const pluginSettingsService = require("./plugin-settings-service");
|
|
7
6
|
const localazyUploadService = require("./localazy-upload-service");
|
|
8
|
-
const localazyDownloadService = require("./localazy-download-service");
|
|
9
7
|
const localazyPubApiService = require("./localazy-pubapi-service");
|
|
10
8
|
const strapiI18nService = require("./strapi-i18n-service");
|
|
11
9
|
const strapiLocalazyI18nService = require("./strapi-localazy-i18n-service");
|
|
@@ -14,11 +12,9 @@ const localazyTransferDownloadService = require("./localazy-transfer-download-se
|
|
|
14
12
|
|
|
15
13
|
module.exports = {
|
|
16
14
|
localazyUserService,
|
|
17
|
-
localazyAuthService,
|
|
18
15
|
strapiService,
|
|
19
16
|
pluginSettingsService,
|
|
20
17
|
localazyUploadService,
|
|
21
|
-
localazyDownloadService,
|
|
22
18
|
localazyPubApiService,
|
|
23
19
|
strapiI18nService,
|
|
24
20
|
strapiLocalazyI18nService,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const localazyApiClientFactory = require("../utils/localazy-api-client-factory");
|
|
4
4
|
const config = require("../config").default;
|
|
5
5
|
|
|
6
6
|
module.exports = ({ strapi }) => ({
|
|
7
7
|
async listFiles(projectId) {
|
|
8
8
|
try {
|
|
9
|
-
const LocalazyApi = await
|
|
10
|
-
const result = await LocalazyApi.
|
|
9
|
+
const LocalazyApi = await localazyApiClientFactory();
|
|
10
|
+
const result = await LocalazyApi.files.list({ project: projectId });
|
|
11
11
|
|
|
12
12
|
return result;
|
|
13
13
|
} catch (e) {
|
|
@@ -27,8 +27,8 @@ module.exports = ({ strapi }) => ({
|
|
|
27
27
|
},
|
|
28
28
|
async listProjects(addOrganization = true, addLanguages = true) {
|
|
29
29
|
try {
|
|
30
|
-
const LocalazyApi = await
|
|
31
|
-
const result = await LocalazyApi.
|
|
30
|
+
const LocalazyApi = await localazyApiClientFactory();
|
|
31
|
+
const result = await LocalazyApi.projects.list({
|
|
32
32
|
organization: addOrganization,
|
|
33
33
|
languages: addLanguages,
|
|
34
34
|
});
|
|
@@ -45,8 +45,8 @@ module.exports = ({ strapi }) => ({
|
|
|
45
45
|
return projects.find((project) => project.id === projectId);
|
|
46
46
|
},
|
|
47
47
|
async getWebhooksSecret(projectId) {
|
|
48
|
-
const LocalazyApi = await
|
|
49
|
-
const result = await LocalazyApi.
|
|
48
|
+
const LocalazyApi = await localazyApiClientFactory();
|
|
49
|
+
const result = await LocalazyApi.webhooks.getSecret({ project: projectId });
|
|
50
50
|
|
|
51
51
|
return result;
|
|
52
52
|
}
|
|
@@ -13,6 +13,7 @@ const isEmpty = require("lodash/isEmpty");
|
|
|
13
13
|
const RequestInitiatorHelper = require('../utils/request-initiator-helper');
|
|
14
14
|
const PluginSettingsServiceHelper = require('../services/helpers/plugin-settings-service-helper');
|
|
15
15
|
const { DOWNLOAD_EVENT, DOWNLOAD_FINISHED_EVENT } = require('../constants/events');
|
|
16
|
+
const localazyApiClientFactory = require("../utils/localazy-api-client-factory");
|
|
16
17
|
|
|
17
18
|
const getFilteredLanguagesCodesForDownload = async (languagesCodes) => {
|
|
18
19
|
const pluginSettingsServiceHelper = new PluginSettingsServiceHelper(strapi);
|
|
@@ -63,9 +64,6 @@ module.exports = ({ strapi }) => ({
|
|
|
63
64
|
const LocalazyPubApiService = strapi
|
|
64
65
|
.plugin("localazy")
|
|
65
66
|
.service("localazyPubApiService");
|
|
66
|
-
const LocalazyDownloadService = strapi
|
|
67
|
-
.plugin("localazy")
|
|
68
|
-
.service("localazyDownloadService");
|
|
69
67
|
const strapiLocalazyI18nService = strapi
|
|
70
68
|
.plugin("localazy")
|
|
71
69
|
.service("strapiLocalazyI18nService");
|
|
@@ -204,19 +202,19 @@ module.exports = ({ strapi }) => ({
|
|
|
204
202
|
(languageCode) => !strapiUnsupportedLanguages.includes(languageCode)
|
|
205
203
|
);
|
|
206
204
|
const localazyContent = {};
|
|
205
|
+
const LocalazyApi = await localazyApiClientFactory();
|
|
207
206
|
for (const isoLocalazy of supportedLanguages) {
|
|
208
207
|
const isoStrapi = isoLocalazyToStrapi(isoLocalazy);
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
const langKeys = await LocalazyApi.files.listKeys({
|
|
209
|
+
project: user.project.id,
|
|
210
|
+
file: strapiFile.id,
|
|
212
211
|
lang: isoStrapi,
|
|
213
212
|
});
|
|
214
|
-
if (!
|
|
213
|
+
if (!langKeys) {
|
|
215
214
|
await JobNotificationService.emit(DOWNLOAD_EVENT, {
|
|
216
|
-
message:
|
|
215
|
+
message: `No keys found for language ${isoLocalazy}`,
|
|
217
216
|
});
|
|
218
217
|
}
|
|
219
|
-
const langKeys = result.data;
|
|
220
218
|
localazyContent[isoLocalazy] = langKeys;
|
|
221
219
|
}
|
|
222
220
|
|
|
@@ -146,17 +146,21 @@ module.exports = ({ strapi }) => ({
|
|
|
146
146
|
? isoStrapiToLocalazy(defaultLocale.code)
|
|
147
147
|
: config.LOCALAZY_DEFAULT_LOCALE;
|
|
148
148
|
|
|
149
|
-
const chunks = LocalazyUploadService.splitToChunks(flattenContent);
|
|
150
149
|
const importFile = LocalazyUploadService.createImportFileRepresentation(
|
|
151
|
-
config.LOCALAZY_DEFAULT_FILE_NAME,
|
|
152
|
-
config.LOCALAZY_DEFAULT_FILE_PATH,
|
|
153
|
-
config.LOCALAZY_DEFAULT_FILE_EXTENSION,
|
|
154
150
|
locale,
|
|
155
|
-
|
|
151
|
+
flattenContent
|
|
156
152
|
);
|
|
157
|
-
// Use `deprecate: "file"` if there is one chunk of transferred data only!
|
|
158
|
-
const
|
|
159
|
-
|
|
153
|
+
// Use `deprecate: "file"` if there is one chunk of transferred data only (99900 keys)!
|
|
154
|
+
const uploadConfig = {
|
|
155
|
+
contentOptions: {
|
|
156
|
+
type: config.LOCALAZY_DEFAULT_FILE_EXTENSION,
|
|
157
|
+
},
|
|
158
|
+
i18nOptions: { deprecate: "file" },
|
|
159
|
+
fileOptions: {
|
|
160
|
+
name: config.LOCALAZY_DEFAULT_FILE_NAME,
|
|
161
|
+
path: config.LOCALAZY_DEFAULT_FILE_PATH,
|
|
162
|
+
}
|
|
163
|
+
};
|
|
160
164
|
await JobNotificationService.emit(UPLOAD_EVENT, {
|
|
161
165
|
message: "Uploading collections to Localazy...",
|
|
162
166
|
});
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const delay = require("../utils/delay");
|
|
7
|
-
const config = require("../config").default;
|
|
5
|
+
const localazyApiClientFactory = require("../utils/localazy-api-client-factory");
|
|
8
6
|
|
|
9
7
|
module.exports = ({ strapi }) => ({
|
|
10
8
|
/**
|
|
@@ -12,31 +10,24 @@ module.exports = ({ strapi }) => ({
|
|
|
12
10
|
* Use config to adjust the upload process.
|
|
13
11
|
* Returns status and id of a last chunk
|
|
14
12
|
*/
|
|
15
|
-
async upload(
|
|
16
|
-
let ret = {
|
|
17
|
-
success: false,
|
|
18
|
-
message: "No data was uploaded",
|
|
19
|
-
};
|
|
13
|
+
async upload(file, config = {}) {
|
|
20
14
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
const LocalazyApi = await localazyApiClientFactory();
|
|
16
|
+
const user = await strapi
|
|
17
|
+
.plugin("localazy")
|
|
18
|
+
.service("localazyUserService")
|
|
19
|
+
.getUser();
|
|
20
|
+
|
|
21
|
+
const result = await LocalazyApi.import.json({
|
|
22
|
+
project: user.project.id,
|
|
23
|
+
json: file,
|
|
24
|
+
...config,
|
|
25
|
+
});
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
await delay();
|
|
34
|
-
ret = {
|
|
35
|
-
success: true,
|
|
36
|
-
result: result.result,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
return ret;
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
result,
|
|
30
|
+
};
|
|
40
31
|
} catch (e) {
|
|
41
32
|
strapi.log.error(e);
|
|
42
33
|
return {
|
|
@@ -46,57 +37,16 @@ module.exports = ({ strapi }) => ({
|
|
|
46
37
|
}
|
|
47
38
|
},
|
|
48
39
|
|
|
49
|
-
/**
|
|
50
|
-
* Check Directus for the Lifted Limits OAuth Apps IDs
|
|
51
|
-
*/
|
|
52
|
-
CHUNK_LIMIT: config.LOCALAZY_PUBLIC_API_LIFTED_LIMITS ? 99900 : 9990,
|
|
53
|
-
|
|
54
|
-
splitToChunks(data, CHUNK_LIMIT = null) {
|
|
55
|
-
const chunks = [];
|
|
56
|
-
const keys = Object.keys(data);
|
|
57
|
-
const keysCount = keys.length;
|
|
58
|
-
const localChunkLimit = CHUNK_LIMIT || this.CHUNK_LIMIT;
|
|
59
|
-
const chunksCount = Math.ceil(keysCount / localChunkLimit);
|
|
60
|
-
for (let i = 0; i < chunksCount; i += 1) {
|
|
61
|
-
const chunkStrings = {};
|
|
62
|
-
const from = localChunkLimit * i;
|
|
63
|
-
const to = localChunkLimit * (i + 1);
|
|
64
|
-
|
|
65
|
-
const currentKeys = keys.slice(from, to);
|
|
66
|
-
currentKeys.forEach((key) => {
|
|
67
|
-
chunkStrings[key] = data[key];
|
|
68
|
-
});
|
|
69
|
-
chunks.push(chunkStrings);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return chunks;
|
|
73
|
-
},
|
|
74
|
-
|
|
75
40
|
createImportFileRepresentation(
|
|
76
|
-
filename,
|
|
77
|
-
path,
|
|
78
|
-
type,
|
|
79
41
|
sourceLang,
|
|
80
|
-
|
|
42
|
+
strings
|
|
81
43
|
) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
name: filename,
|
|
88
|
-
path,
|
|
89
|
-
content: {
|
|
90
|
-
type,
|
|
91
|
-
[sourceLang]: {
|
|
92
|
-
...strings,
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
];
|
|
97
|
-
files.push(file);
|
|
98
|
-
}
|
|
44
|
+
const file = {
|
|
45
|
+
[sourceLang]: {
|
|
46
|
+
...strings,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
99
49
|
|
|
100
|
-
return
|
|
50
|
+
return file;
|
|
101
51
|
},
|
|
102
52
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const ApiClient = require("@localazy/api-client").ApiClient;
|
|
4
4
|
const { LOCALAZY_PUBLIC_API_URL } = require("../config").default;
|
|
5
5
|
|
|
6
6
|
module.exports = async () => {
|
|
@@ -14,9 +14,9 @@ module.exports = async () => {
|
|
|
14
14
|
throw new Error("Localazy user is not logged in.");
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const api =
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const api = new ApiClient({
|
|
18
|
+
authToken: accessToken,
|
|
19
|
+
apiUrl: LOCALAZY_PUBLIC_API_URL,
|
|
20
20
|
});
|
|
21
21
|
return api;
|
|
22
22
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const GenericConnectorClient = require("@localazy/generic-connector-client").GenericConnectorClient;
|
|
4
|
+
const config = require("../config");
|
|
5
|
+
|
|
6
|
+
const ROOT_URL = config.default.LOCALAZY_PLUGIN_CONNECTOR_API_URL;
|
|
7
|
+
const PLUGIN_ID = config.default.LOCALAZY_PLUGIN_ID;
|
|
8
|
+
|
|
9
|
+
module.exports = async () => {
|
|
10
|
+
const api = new GenericConnectorClient({
|
|
11
|
+
pluginId: PLUGIN_ID,
|
|
12
|
+
genericConnectorUrl: ROOT_URL,
|
|
13
|
+
});
|
|
14
|
+
return api;
|
|
15
|
+
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
on: [push, pull_request]
|
|
3
|
-
jobs:
|
|
4
|
-
execute:
|
|
5
|
-
runs-on: [self-hosted, Linux, aws]
|
|
6
|
-
env:
|
|
7
|
-
INPUT_TOKEN: ""
|
|
8
|
-
steps:
|
|
9
|
-
- name: Set work folder permissions
|
|
10
|
-
run: pwd && sudo chown -R $USER:$USER ./
|
|
11
|
-
- uses: actions/checkout@v2
|
|
12
|
-
- name: Setup Node.js environment
|
|
13
|
-
uses: actions/setup-node@v3
|
|
14
|
-
with:
|
|
15
|
-
node-version: 16.x
|
|
16
|
-
- name: Install dependencies
|
|
17
|
-
run: npm ci
|
|
18
|
-
- name: Run unit tests
|
|
19
|
-
run: npm run test
|
|
20
|
-
- name: Publish to npm
|
|
21
|
-
uses: JS-DevTools/npm-publish@v1
|
|
22
|
-
if: github.ref == 'refs/heads/main'
|
|
23
|
-
with:
|
|
24
|
-
token: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import createPluginConnectorAxiosInstance from "../utils/createPluginConnectorAxiosInstance";
|
|
2
|
-
|
|
3
|
-
const ANALYTICS_PATH = "/analytics";
|
|
4
|
-
const axiosInstance = createPluginConnectorAxiosInstance();
|
|
5
|
-
export default class AnalyticsService {
|
|
6
|
-
static async trackEvent(event, data) {
|
|
7
|
-
const result = await axiosInstance.post(`${ANALYTICS_PATH}/track`, {
|
|
8
|
-
event,
|
|
9
|
-
...data,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
return result.data;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* axios with plugin connector config.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import axios from "axios";
|
|
6
|
-
import config from "../config";
|
|
7
|
-
|
|
8
|
-
const BASE_PLUGIN_PATH = config.LOCALAZY_PLUGIN_CONNECTOR_API_URL;
|
|
9
|
-
|
|
10
|
-
const createPluginConnectorAxiosInstance = (baseUrl = null) => {
|
|
11
|
-
if (baseUrl === null) {
|
|
12
|
-
baseUrl = `${BASE_PLUGIN_PATH}`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const instance = axios.create({
|
|
16
|
-
baseURL: baseUrl,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
instance.interceptors.request.use(
|
|
20
|
-
async (config) => {
|
|
21
|
-
config.headers = {
|
|
22
|
-
Accept: "application/json",
|
|
23
|
-
"Content-Type": "application/json",
|
|
24
|
-
// Strapi plugin id in terms of plugin connector
|
|
25
|
-
"X-Localazy-Plugin-Id": 1,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return config;
|
|
29
|
-
},
|
|
30
|
-
(error) => {
|
|
31
|
-
Promise.reject(error);
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
instance.interceptors.response.use(
|
|
36
|
-
(response) => response,
|
|
37
|
-
(error) => {
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
return instance;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export default createPluginConnectorAxiosInstance;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const axios = require("axios");
|
|
4
|
-
const config = require("../config");
|
|
5
|
-
|
|
6
|
-
const ROOT_URL = config.default.LOCALAZY_PLUGIN_CONNECTOR_API_URL;
|
|
7
|
-
const PLUGIN_ID = config.default.LOCALAZY_PLUGIN_ID;
|
|
8
|
-
|
|
9
|
-
module.exports = (/* { strapi } */) => ({
|
|
10
|
-
async generateKeys() {
|
|
11
|
-
try {
|
|
12
|
-
const result = await axios.get(`${ROOT_URL}/public/keys`, {
|
|
13
|
-
headers: {
|
|
14
|
-
"X-Localazy-Plugin-Id": PLUGIN_ID,
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
return result.data;
|
|
18
|
-
} catch (e) {
|
|
19
|
-
throw e.data;
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
async completeLogin(readKey) {
|
|
24
|
-
try {
|
|
25
|
-
const result = await axios.get(
|
|
26
|
-
`${ROOT_URL}/oauth/poll?readKey=${readKey}`,
|
|
27
|
-
{
|
|
28
|
-
headers: {
|
|
29
|
-
"X-Localazy-Plugin-Id": PLUGIN_ID,
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
);
|
|
33
|
-
return result.data;
|
|
34
|
-
} catch (e) {
|
|
35
|
-
throw e.data;
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const getLocalazyApi = require("../utils/get-localazy-api");
|
|
4
|
-
const delay = require("../utils/delay");
|
|
5
|
-
|
|
6
|
-
const fetchChunkResult = async (projectId, fileId, lang, next) => {
|
|
7
|
-
try {
|
|
8
|
-
const LocalazyApi = await getLocalazyApi();
|
|
9
|
-
const chunkResult = await LocalazyApi.listKeysInFileForLanguage({
|
|
10
|
-
projectId,
|
|
11
|
-
fileId,
|
|
12
|
-
lang,
|
|
13
|
-
next,
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
return chunkResult;
|
|
17
|
-
} catch (e) {
|
|
18
|
-
strapi.log.error(e);
|
|
19
|
-
return {
|
|
20
|
-
keys: [],
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
module.exports = ({ strapi }) => ({
|
|
26
|
-
async download(config = {}) {
|
|
27
|
-
try {
|
|
28
|
-
const data = [];
|
|
29
|
-
let chunkResult;
|
|
30
|
-
do {
|
|
31
|
-
const next = (chunkResult && chunkResult.next) || "";
|
|
32
|
-
chunkResult = await fetchChunkResult(
|
|
33
|
-
config.projectId,
|
|
34
|
-
config.fileId,
|
|
35
|
-
config.lang,
|
|
36
|
-
next
|
|
37
|
-
);
|
|
38
|
-
await delay();
|
|
39
|
-
data.push(chunkResult);
|
|
40
|
-
} while (chunkResult.next);
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
success: true,
|
|
44
|
-
data: data.map((chunk) => chunk.keys).flat(),
|
|
45
|
-
};
|
|
46
|
-
} catch (e) {
|
|
47
|
-
strapi.log.error(e);
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
message: e.message,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
});
|