From 8b366c34fb85a177b8252ac04d1fe1976d953790 Mon Sep 17 00:00:00 2001 From: Alberto Casado Torres Date: Wed, 27 Oct 2021 13:28:35 +0200 Subject: [PATCH] Sync route preserving translations for menu and filters --- app/api/sync/routes.ts | 21 +++++++++++++ app/api/sync/specs/routes.spec.js | 38 +++++++++++++++++++++++ app/shared/translationSchema.ts | 51 +++++++++++++++++++++++++++++++ app/shared/translationType.d.ts | 20 ++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 app/shared/translationSchema.ts create mode 100644 app/shared/translationType.d.ts diff --git a/app/api/sync/routes.ts b/app/api/sync/routes.ts index f61b9b3265..e1cf9f3c2b 100644 --- a/app/api/sync/routes.ts +++ b/app/api/sync/routes.ts @@ -7,6 +7,7 @@ import { Request, Application } from 'express'; import { FileType } from 'shared/types/fileType'; import { uploadsPath, customUploadsPath, uploadMiddleware } from 'api/files'; import { needsAuthorization } from '../auth'; +import { TranslationType } from 'shared/translationType'; const storage = multer.diskStorage({ filename(_req, file, cb) { @@ -47,6 +48,22 @@ const deleteFromIndex = async (req: Request, file: FileType) => { } }; +const preserveTranslations = async (syncData: TranslationType): Promise => { + const [translation] = (await models.translations.get({ _id: syncData._id })) as TranslationType[]; + if (!translation) { + return syncData; + } + const menu = translation.contexts?.find(c => c.id === 'Menu'); + const filters = translation.contexts?.find(c => c.id === 'Filters'); + if (menu) { + syncData.contexts?.push(menu); + } + if (filters) { + syncData.contexts?.push(filters); + } + return syncData; +}; + export default (app: Application) => { app.post('/api/sync', needsAuthorization(['admin']), async (req, res, next) => { try { @@ -55,6 +72,10 @@ export default (app: Application) => { req.body.data._id = settings._id; } + if (req.body.namespace === 'translations') { + req.body.data = await preserveTranslations(req.body.data); + } + await (Array.isArray(req.body.data) ? models[req.body.namespace].saveMultiple(req.body.data) : models[req.body.namespace].save(req.body.data)); diff --git a/app/api/sync/specs/routes.spec.js b/app/api/sync/specs/routes.spec.js index 25fd194f5a..bb288f341e 100644 --- a/app/api/sync/specs/routes.spec.js +++ b/app/api/sync/specs/routes.spec.js @@ -123,6 +123,44 @@ describe('sync', () => { expect(routes._post('/api/sync/upload', {})).toNeedAuthorization(); }); }); + + describe('when namespace is translations', () => { + it('should respect menu and filters translations', async () => { + models.translations = { + save: jasmine.createSpy('entities.save'), + delete: jasmine.createSpy('entities.delete'), + get: jasmine.createSpy('entities.get').and.returnValue( + Promise.resolve([ + { + _id: 'id', + contexts: [ + { id: 'Menu', values: [{ key: 'About us', value: 'About us' }] }, + { id: 'Filters', values: [{ key: 'Cause', value: 'Cause' }] }, + ], + }, + ]) + ), + }; + + req.body = { + namespace: 'translations', + data: { + _id: 'id', + contexts: [{ id: 'System', values: [{ key: 'Search', value: 'Search' }] }], + }, + }; + + await routes.post('/api/sync', req); + expect(models.translations.save).toHaveBeenCalledWith({ + _id: 'id', + contexts: [ + { id: 'System', values: [{ key: 'Search', value: 'Search' }] }, + { id: 'Menu', values: [{ key: 'About us', value: 'About us' }] }, + { id: 'Filters', values: [{ key: 'Cause', value: 'Cause' }] }, + ], + }); + }); + }); }); describe('DELETE', () => { diff --git a/app/shared/translationSchema.ts b/app/shared/translationSchema.ts new file mode 100644 index 0000000000..8acdda5894 --- /dev/null +++ b/app/shared/translationSchema.ts @@ -0,0 +1,51 @@ +import Ajv from 'ajv'; +import { objectIdSchema } from 'shared/types/commonSchemas'; +import { wrapValidator } from 'shared/tsUtils'; +import { TranslationType } from './translationType'; + +export const emitSchemaTypes = true; + +const ajv = Ajv({ allErrors: true, removeAdditional: true }); + +export const translationSchema = { + $schema: 'http://json-schema.org/schema#', + $async: true, + type: 'object', + additionalProperties: false, + title: 'TranslationType', + definitions: { objectIdSchema }, + properties: { + _id: objectIdSchema, + locale: { type: 'string', minLength: 1 }, + contexts: { + type: 'array', + items: { + type: 'object', + additionalProperties: false, + properties: { + _id: objectIdSchema, + id: { type: 'string', minLength: 1 }, + label: { type: 'string', minLength: 1 }, + type: { type: 'string', minLength: 1 }, + values: { + type: 'array', + items: { + type: 'object', + additionalProperties: false, + properties: { + _id: objectIdSchema, + key: { type: 'string', minLength: 1 }, + value: { type: 'string', minLength: 1 }, + }, + }, + }, + }, + }, + }, + }, +}; + +const validate = wrapValidator(ajv.compile(translationSchema)); + +export const validateTranslation = async (translation: TranslationType): Promise => + validate({ ...translation }); diff --git a/app/shared/translationType.d.ts b/app/shared/translationType.d.ts new file mode 100644 index 0000000000..ba1ebac6b8 --- /dev/null +++ b/app/shared/translationType.d.ts @@ -0,0 +1,20 @@ +/* eslint-disable */ +/**AUTO-GENERATED. RUN yarn emit-types to update.*/ + +import { ObjectIdSchema } from 'shared/types/commonTypes'; + +export interface TranslationType { + _id?: ObjectIdSchema; + locale?: string; + contexts?: { + _id?: ObjectIdSchema; + id?: string; + label?: string; + type?: string; + values?: { + _id?: ObjectIdSchema; + key?: string; + value?: string; + }[]; + }[]; +}