From a814deef81bd62ae7f64050f5c935e3923e4d994 Mon Sep 17 00:00:00 2001 From: Mark Harnyk Date: Fri, 27 Sep 2024 18:10:52 +0200 Subject: [PATCH 01/13] refactor(registry): move updateTemplate logic into the repo --- registry/package-lock.json | 7 ++ registry/package.json | 1 + registry/server/templates/interfaces/index.ts | 13 ++- .../server/templates/routes/createTemplate.ts | 4 +- .../templates/routes/getRenderedTemplate.ts | 4 +- .../server/templates/routes/updateTemplate.ts | 53 +++++----- .../server/templates/routes/validation.ts | 24 +++-- .../templates/services/templatesRepository.ts | 98 ++++++++++++++++--- 8 files changed, 153 insertions(+), 51 deletions(-) diff --git a/registry/package-lock.json b/registry/package-lock.json index 121fe76c1..6335803e1 100644 --- a/registry/package-lock.json +++ b/registry/package-lock.json @@ -39,6 +39,7 @@ "serve-static": "^1.16.2", "source-map-support": "^0.5.21", "sqlite3": "^5.1.7", + "ts-exhaustive-check": "^1.0.0", "url-join": "^4.0.1", "uuid": "^9.0.1" }, @@ -8556,6 +8557,12 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-exhaustive-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ts-exhaustive-check/-/ts-exhaustive-check-1.0.0.tgz", + "integrity": "sha512-sKNyrdJfB1nsPMip5+xSKPdwuZ8XZ1WgxGw3ho4qXGlUQ4BGmmS0voGBK0ecimcvqzhVk84RF4JOhtZEIJWiNw==", + "license": "MIT" + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", diff --git a/registry/package.json b/registry/package.json index 18d82b980..0cfa5efde 100644 --- a/registry/package.json +++ b/registry/package.json @@ -95,6 +95,7 @@ "serve-static": "^1.16.2", "source-map-support": "^0.5.21", "sqlite3": "^5.1.7", + "ts-exhaustive-check": "^1.0.0", "url-join": "^4.0.1", "uuid": "^9.0.1" }, diff --git a/registry/server/templates/interfaces/index.ts b/registry/server/templates/interfaces/index.ts index 665aad1db..cf0f84a90 100644 --- a/registry/server/templates/interfaces/index.ts +++ b/registry/server/templates/interfaces/index.ts @@ -3,8 +3,19 @@ export default interface Template { content: string; } -export interface LocalizedTemplate { +export interface LocalizedTemplateRow { templateName: string; content: string; locale: string; } + +export interface LocalizedVersion { + content: string; +} + +export interface TemplateWithLocalizedVersions extends Template { + localizedVersions: Record; +} + +export type UpdateTemplatePayload = Omit & + Partial>; diff --git a/registry/server/templates/routes/createTemplate.ts b/registry/server/templates/routes/createTemplate.ts index d106f4fbc..82cce0be6 100644 --- a/registry/server/templates/routes/createTemplate.ts +++ b/registry/server/templates/routes/createTemplate.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express'; import db from '../../db'; import validateRequestFactory from '../../common/services/validateRequest'; -import Template, { LocalizedTemplate } from '../interfaces'; +import Template, { LocalizedTemplateRow } from '../interfaces'; import { Tables } from '../../db/structure'; import { templatesRepository } from '../services/templatesRepository'; import { templateSchema, validateLocalesAreSupported } from './validation'; @@ -46,7 +46,7 @@ const createTemplate = async (req: Request, res: Response): Promise => { function insertLocalizedVersions(locales: string[], template: Template, request: Record) { return Promise.all( locales.map((locale) => { - const localizedTemplate: LocalizedTemplate = { + const localizedTemplate: LocalizedTemplateRow = { templateName: template.name, content: request.localizedVersions[locale].content, locale: locale, diff --git a/registry/server/templates/routes/getRenderedTemplate.ts b/registry/server/templates/routes/getRenderedTemplate.ts index 0f98221ba..0d83a44b0 100644 --- a/registry/server/templates/routes/getRenderedTemplate.ts +++ b/registry/server/templates/routes/getRenderedTemplate.ts @@ -3,7 +3,7 @@ import Joi from 'joi'; import noticeError from '../../errorHandler/noticeError'; import db from '../../db'; -import Template, { LocalizedTemplate } from '../interfaces'; +import Template, { LocalizedTemplateRow } from '../interfaces'; import validateRequestFactory from '../../common/services/validateRequest'; import renderTemplate from '../services/renderTemplate'; import errors from '../errors'; @@ -89,7 +89,7 @@ async function getRenderedTemplate(req: Request(Tables.TemplatesLocalized) + .from(Tables.TemplatesLocalized) .where('templateName', templateName) .andWhere('locale', locale as string); diff --git a/registry/server/templates/routes/updateTemplate.ts b/registry/server/templates/routes/updateTemplate.ts index 5c7ab54ba..fe0deaa9c 100644 --- a/registry/server/templates/routes/updateTemplate.ts +++ b/registry/server/templates/routes/updateTemplate.ts @@ -1,10 +1,11 @@ import { Request, Response } from 'express'; import Joi from 'joi'; +import { exhaustiveCheck } from 'ts-exhaustive-check'; -import db from '../../db'; import validateRequestFactory from '../../common/services/validateRequest'; +import { joiErrorToResponse } from '../../util/helpers'; import { templatesRepository } from '../services/templatesRepository'; -import { partialTemplateSchema, templateNameSchema, validateLocalesAreSupported } from './validation'; +import { partialTemplateSchema, templateNameSchema, unsupportedLocalesToJoiError } from './validation'; type UpdateTemplateRequestParams = { name: string; @@ -24,32 +25,30 @@ const validateRequestBeforeUpdateTemplate = validateRequestFactory([ ]); const updateTemplate = async (req: Request, res: Response): Promise => { - const template = { - content: req.body.content, - }; - const templateName = req.params.name; - - const templatesToUpdate = await db('templates').where({ - name: templateName, - }); - if (!templatesToUpdate.length) { - res.status(404).send('Not found'); - return; - } - - const localizedVersions = req.body.localizedVersions || {}; - const localesAreValid = await validateLocalesAreSupported(Object.keys(localizedVersions), res); - if (!localesAreValid) { - return; + const result = await templatesRepository.updateTemplate( + req.params.name, + req.body, + // TODO: cheeck if user here is really required + req.user!, + ); + + switch (result.type) { + case 'notFound': { + res.status(404).send('Not found'); + return; + } + case 'localeNotSupported': { + res.status(422).send(joiErrorToResponse(unsupportedLocalesToJoiError(result.locales))); + return; + } + case 'ok': { + res.status(200).send(result.template); + return; + } + default: { + exhaustiveCheck(result); + } } - - await db.versioning(req.user, { type: 'templates', id: templateName }, async (trx) => { - await db('templates').where({ name: templateName }).update(template).transacting(trx); - await templatesRepository.upsertLocalizedVersions(templateName, localizedVersions, trx); - }); - - const updatedTemplate = await templatesRepository.readTemplateWithAllVersions(templateName); - res.status(200).send(updatedTemplate); }; export default [validateRequestBeforeUpdateTemplate, updateTemplate]; diff --git a/registry/server/templates/routes/validation.ts b/registry/server/templates/routes/validation.ts index d0f1e03f8..f571e97d6 100644 --- a/registry/server/templates/routes/validation.ts +++ b/registry/server/templates/routes/validation.ts @@ -47,16 +47,24 @@ export const templateSchema = Joi.object({ localizedVersions, }); -export async function validateLocalesAreSupported(locales: string[], res: Response) { +export async function getUnsupportedLocales(locales: string[]): Promise { const supportedLocales = await settingsService.get(SettingKeys.I18nSupportedLocales); - let unsupportedLocales = locales.filter((l) => !supportedLocales.includes(l)); + return locales.filter((l) => !supportedLocales.includes(l)); +} + +export function unsupportedLocalesToJoiError(unsupportedLocales: string[]): Joi.ValidationError { + return getJoiErr( + `localizedVersions.${unsupportedLocales[0]}`, + `Next locales are not supported ${unsupportedLocales.join(',')}. Either change request or change ${ + SettingKeys.I18nSupportedLocales + } setting.`, + ); +} + +export async function validateLocalesAreSupported(locales: string[], res: Response) { + const unsupportedLocales = await getUnsupportedLocales(locales); if (unsupportedLocales.length > 0) { - let joiError = getJoiErr( - `localizedVersions.${unsupportedLocales[0]}`, - `Next locales are not supported ${unsupportedLocales.join(',')}. Either change request or change ${ - SettingKeys.I18nSupportedLocales - } setting.`, - ); + const joiError = unsupportedLocalesToJoiError(unsupportedLocales); res.status(422); res.send(joiErrorToResponse(joiError)); return false; diff --git a/registry/server/templates/services/templatesRepository.ts b/registry/server/templates/services/templatesRepository.ts index 3045893cc..0692bd37a 100644 --- a/registry/server/templates/services/templatesRepository.ts +++ b/registry/server/templates/services/templatesRepository.ts @@ -1,15 +1,21 @@ -import { ok } from 'assert'; import { Knex } from 'knex'; import { PaginatedResult } from '../../../typings/PaginatedResult'; import db, { VersionedKnex } from '../../db'; import { Tables } from '../../db/structure'; import { appendDigest } from '../../util/hmac'; -import { EntityTypes } from '../../versioning/interfaces'; -import Template, { LocalizedTemplate } from '../interfaces'; +import { normalizeArray } from '../../util/normalizeArray'; +import { EntityTypes, VersionedRecord } from '../../versioning/interfaces'; +import Template, { + LocalizedTemplateRow, + LocalizedVersion, + TemplateWithLocalizedVersions, + UpdateTemplatePayload, +} from '../interfaces'; import Transaction = Knex.Transaction; -import { normalizeArray } from '../../util/normalizeArray'; +import { getUnsupportedLocales } from '../routes/validation'; +import { User } from '../../../typings/User'; export interface TemplatesGetListFilters { domainId?: number | 'null'; @@ -17,6 +23,25 @@ export interface TemplatesGetListFilters { name?: string[] | string; } +interface UpdateTemplateResultOk { + type: 'ok'; + template: VersionedRecord; +} + +interface UpdateTemplateResultNotFound { + type: 'notFound'; +} + +interface UpdateTemplateResultLocalesNotSupported { + type: 'localeNotSupported'; + locales: string[]; +} + +type UpdateTemplateResult = + | UpdateTemplateResultOk + | UpdateTemplateResultNotFound + | UpdateTemplateResultLocalesNotSupported; + export class TemplatesRepository { constructor(private db: VersionedKnex) {} @@ -47,11 +72,15 @@ export class TemplatesRepository { }; } - async readTemplateWithAllVersions(templateName: string) { + async readTemplateWithAllVersions( + templateName: string, + ): Promise | undefined> { const { db } = this; const [template] = await db - .selectVersionedRowsFrom(Tables.Templates, 'name', EntityTypes.templates, [`${Tables.Templates}.*`]) + .selectVersionedRowsFrom