diff --git a/app/routes/shared/result.server.ts b/app/routes/shared/result.server.ts index 5a37cbbf4..c87c9c0e0 100644 --- a/app/routes/shared/result.server.ts +++ b/app/routes/shared/result.server.ts @@ -59,8 +59,8 @@ export const loader = async ({ request, context }: LoaderFunctionArgs) => { stepId, }); - const documents = cmsContent.documents.data?.attributes.element ?? []; - const nextSteps = cmsContent.nextSteps.data?.attributes.element ?? []; + const documents = cmsContent.documents?.element ?? []; + const nextSteps = cmsContent.nextSteps?.element ?? []; const cmsData = { ...cmsContent, nextSteps, documents }; diff --git a/app/services/cms/__test__/getStrapiEntryFromApi.test.ts b/app/services/cms/__test__/getStrapiEntryFromApi.test.ts index 1ddcb027b..086efba09 100644 --- a/app/services/cms/__test__/getStrapiEntryFromApi.test.ts +++ b/app/services/cms/__test__/getStrapiEntryFromApi.test.ts @@ -1,9 +1,6 @@ import axios from "axios"; import { getStrapiEntryFromApi } from "~/services/cms/getStrapiEntryFromApi"; -import { - defaultLocale, - stagingLocale, -} from "~/services/cms/models/StrapiLocale"; +import { stagingLocale } from "~/services/cms/models/StrapiLocale"; import type { GetStrapiEntryOpts } from "../filters"; const API_URL = "test://cms/api/"; @@ -18,45 +15,23 @@ describe("services/cms", () => { }); describe("getStrapiEntryFromApi", () => { - const dataResponse = [{ attributes: "data" }]; + const dataResponse = [{ testField: "testData" }]; const defaultOptions: GetStrapiEntryOpts = { apiId: "pages", locale: stagingLocale, }; - const defaultResponseData = { data: { data: dataResponse } }; - const emptyResponseData = { data: [] }; - const expectedRequestUrl = `${API_URL}pages?populate=deep&locale=de`; - const expectedStagingRequestUrl = `${API_URL}pages?populate=deep&locale=sg`; + const expectedStagingRequestUrl = `${API_URL}pages?populate=*&pLevel&locale=sg`; const axiosGetSpy = vi.spyOn(axios, "get"); beforeEach(() => { - axiosGetSpy - .mockResolvedValue(defaultResponseData) - .mockResolvedValueOnce(emptyResponseData); + axiosGetSpy.mockResolvedValue({ data: { data: dataResponse } }); }); afterEach(() => { axiosGetSpy.mockClear(); }); - test("first requests staging locale before", async () => { - await getStrapiEntryFromApi({ - ...defaultOptions, - locale: defaultLocale, - }); - expect(axiosGetSpy).toHaveBeenNthCalledWith( - 1, - expectedStagingRequestUrl, - expect.anything(), - ); - expect(axiosGetSpy).toHaveBeenNthCalledWith( - 2, - expectedRequestUrl, - expect.anything(), - ); - }); - test("request url with property filter", async () => { await getStrapiEntryFromApi({ ...defaultOptions, diff --git a/app/services/cms/__test__/getStrapiEntryFromFile.test.ts b/app/services/cms/__test__/getStrapiEntryFromFile.test.ts index af87d40da..77f17a46c 100644 --- a/app/services/cms/__test__/getStrapiEntryFromFile.test.ts +++ b/app/services/cms/__test__/getStrapiEntryFromFile.test.ts @@ -25,36 +25,32 @@ describe("services/cms", () => { const fileContent = { "page-header": [], - footer: [{ attributes: footerData }], - pages: [{ attributes: impressum }], + footer: [footerData], + pages: [impressum], "cookie-banner": [], "result-pages": [], "vorab-check-pages": [], "form-flow-pages": [ { - attributes: { - heading: "", - locale: StrapiLocaleSchema.Values.de, - preHeading: null, - nextButtonLabel: null, - backButtonLabel: null, - stepId: "/stepId", - meta: { - title: "", - description: null, - ogTitle: null, - breadcrumb: "", - }, - pre_form: [], - post_form: [], - form: [], - flow_ids: { - data: [ - { attributes: { flowId: "/geld-einklagen/formular" } }, - { attributes: { flowId: "/fluggastrechte/formular" } }, - ], - }, + heading: "", + locale: StrapiLocaleSchema.Values.de, + preHeading: null, + nextButtonLabel: null, + backButtonLabel: null, + stepId: "/stepId", + meta: { + title: "", + description: null, + ogTitle: null, + breadcrumb: "", }, + pre_form: [], + post_form: [], + form: [], + flow_ids: [ + { flowId: "/geld-einklagen/formular" }, + { flowId: "/fluggastrechte/formular" }, + ], }, ], translations: [], @@ -67,18 +63,7 @@ describe("services/cms", () => { apiId: "footer", locale: "de", }), - ).toEqual([{ attributes: footerData }]); - }); - - describe("when no entry exists for the given locale", () => { - it("returns default entry", async () => { - expect( - await getStrapiEntryFromFile({ - apiId: "footer", - locale: "en", - }), - ).toEqual([{ attributes: footerData }]); - }); + ).toEqual([footerData]); }); it("can filter by property", async () => { @@ -88,7 +73,7 @@ describe("services/cms", () => { filters: [{ field: "slug", value: impressumPath }], locale: "de", }), - ).toEqual([{ attributes: impressum }]); + ).toEqual([impressum]); }); it("can filter by nested property", async () => { @@ -131,15 +116,5 @@ describe("services/cms", () => { }), ).toStrictEqual([]); }); - - it("falls back to default locale", async () => { - expect( - await getStrapiEntryFromFile({ - apiId: "pages", - filters: [{ field: "slug", value: impressumPath }], - locale: "en", - }), - ).toEqual([{ attributes: impressum }]); - }); }); }); diff --git a/app/services/cms/__test__/index.test.ts b/app/services/cms/__test__/index.test.ts index fdab04145..094f275f3 100644 --- a/app/services/cms/__test__/index.test.ts +++ b/app/services/cms/__test__/index.test.ts @@ -16,9 +16,7 @@ describe("services/cms", () => { describe("fetchSingleEntry", () => { test("returns a footer entry", async () => { const footerData = getStrapiFooter(); - vi.mocked(getStrapiEntry).mockReturnValue( - Promise.resolve([{ attributes: footerData }]), - ); + vi.mocked(getStrapiEntry).mockReturnValue(Promise.resolve([footerData])); expect(await fetchSingleEntry("footer")).toEqual(footerData); }); }); @@ -26,18 +24,15 @@ describe("services/cms", () => { describe("fetchAllFormFields", () => { test("returns steps with it's form field names", async () => { vi.mocked(getStrapiEntry).mockResolvedValue([ - { - attributes: getStrapiFlowPage({ - stepId: "step1", - form: [getStrapiFormComponent({ name: "formFieldForStep1" })], - }), - }, - { - attributes: getStrapiFlowPage({ - stepId: "step2", - form: [getStrapiFormComponent({ name: "formFieldForStep2" })], - }), - }, + getStrapiFlowPage({ + stepId: "step1", + form: [getStrapiFormComponent({ name: "formFieldForStep1" })], + }), + + getStrapiFlowPage({ + stepId: "step2", + form: [getStrapiFormComponent({ name: "formFieldForStep2" })], + }), ]); expect(await fetchAllFormFields("/beratungshilfe/antrag")).toStrictEqual({ @@ -48,16 +43,14 @@ describe("services/cms", () => { test("returns steps with multiple form field names", async () => { vi.mocked(getStrapiEntry).mockResolvedValue([ - { - attributes: getStrapiFlowPage({ - stepId: "step1", - form: [ - getStrapiFormComponent({ name: "formField1ForStep1" }), - getStrapiFormComponent({ name: "formField2ForStep1" }), - getStrapiFormComponent({ name: "formField3ForStep1" }), - ], - }), - }, + getStrapiFlowPage({ + stepId: "step1", + form: [ + getStrapiFormComponent({ name: "formField1ForStep1" }), + getStrapiFormComponent({ name: "formField2ForStep1" }), + getStrapiFormComponent({ name: "formField3ForStep1" }), + ], + }), ]); expect(await fetchAllFormFields("/beratungshilfe/antrag")).toStrictEqual({ @@ -71,23 +64,19 @@ describe("services/cms", () => { test("overwrites formfields from staging", async () => { vi.mocked(getStrapiEntry).mockResolvedValue([ - { - attributes: getStrapiFlowPage({ - stepId: "step1", - form: [ - getStrapiFormComponent({ - name: "formFieldForStepProd", - }), - ], - }), - }, - { - attributes: getStrapiFlowPage({ - stepId: "step1", - locale: "sg", - form: [getStrapiFormComponent({ name: "formFieldForStage" })], - }), - }, + getStrapiFlowPage({ + stepId: "step1", + form: [ + getStrapiFormComponent({ + name: "formFieldForStepProd", + }), + ], + }), + getStrapiFlowPage({ + stepId: "step1", + locale: "sg", + form: [getStrapiFormComponent({ name: "formFieldForStage" })], + }), ]); expect(await fetchAllFormFields("/beratungshilfe/antrag")).toStrictEqual({ @@ -96,9 +85,9 @@ describe("services/cms", () => { }); test("disregards staging formfields in production", async () => { - vi.mocked(getStrapiEntry).mockResolvedValue([ - { - attributes: getStrapiFlowPage({ + vi.mocked(getStrapiEntry) + .mockResolvedValueOnce([ + getStrapiFlowPage({ stepId: "step1", form: [ getStrapiFormComponent({ @@ -106,9 +95,9 @@ describe("services/cms", () => { }), ], }), - }, - { - attributes: getStrapiFlowPage({ + ]) + .mockResolvedValueOnce([ + getStrapiFlowPage({ stepId: "step2", locale: "sg", form: [ @@ -117,8 +106,7 @@ describe("services/cms", () => { }), ], }), - }, - ]); + ]); expect( await fetchAllFormFields("/beratungshilfe/antrag", "production"), @@ -129,28 +117,25 @@ describe("services/cms", () => { test("adds staging steps when not in production", async () => { vi.mocked(getStrapiEntry).mockResolvedValue([ - { - attributes: getStrapiFlowPage({ - stepId: "step1", - locale: "de", - form: [ - getStrapiFormComponent({ - name: "formFieldForStepProd", - }), - ], - }), - }, - { - attributes: getStrapiFlowPage({ - stepId: "step2", - locale: "sg", - form: [ - getStrapiFormComponent({ - name: "formFieldForStepStage", - }), - ], - }), - }, + getStrapiFlowPage({ + stepId: "step1", + locale: "de", + form: [ + getStrapiFormComponent({ + name: "formFieldForStepProd", + }), + ], + }), + + getStrapiFlowPage({ + stepId: "step2", + locale: "sg", + form: [ + getStrapiFormComponent({ + name: "formFieldForStepStage", + }), + ], + }), ]); expect(await fetchAllFormFields("/beratungshilfe/antrag")).toStrictEqual({ @@ -161,12 +146,10 @@ describe("services/cms", () => { test("filters out steps without forms", async () => { vi.mocked(getStrapiEntry).mockResolvedValue([ - { - attributes: getStrapiFlowPage({ - stepId: "step1", - form: [], - }), - }, + getStrapiFlowPage({ + stepId: "step1", + form: [], + }), ]); expect(await fetchAllFormFields("/beratungshilfe/antrag")).toStrictEqual( diff --git a/app/services/cms/components/StrapiCheckbox.tsx b/app/services/cms/components/StrapiCheckbox.tsx index 3a54564f9..ca19482fb 100644 --- a/app/services/cms/components/StrapiCheckbox.tsx +++ b/app/services/cms/components/StrapiCheckbox.tsx @@ -10,11 +10,8 @@ const StrapiCheckboxSchema = z .object({ name: z.string(), label: z.string(), - isRequiredError: z.object({ - data: HasStrapiIdSchema.extend({ - attributes: StrapiErrorCategorySchema, - }).nullable(), - }), + isRequiredError: + StrapiErrorCategorySchema.merge(HasStrapiIdSchema).nullable(), }) .merge(HasOptionalStrapiIdSchema); @@ -27,8 +24,8 @@ export const StrapiCheckbox = ({ ...props }: z.infer) => ( ); diff --git a/app/services/cms/components/StrapiTileGroup.tsx b/app/services/cms/components/StrapiTileGroup.tsx index f0bd7b145..b6ec6c72c 100644 --- a/app/services/cms/components/StrapiTileGroup.tsx +++ b/app/services/cms/components/StrapiTileGroup.tsx @@ -15,13 +15,7 @@ const StrapiTileGroupSchema = z label: z.string().nullable(), altLabel: z.string().nullable(), options: z.array(StrapiTileSchema), - errors: z.object({ - data: z.array( - HasStrapiIdSchema.extend({ - attributes: StrapiErrorCategorySchema, - }), - ), - }), + errors: z.array(StrapiErrorCategorySchema.merge(HasStrapiIdSchema)), useTwoColumns: z.boolean(), }) .merge(HasOptionalStrapiIdSchema); @@ -37,9 +31,7 @@ export const StrapiTileGroup = ({ options, ...props }: StrapiTileGroup) => { - const errorMessages = errors.data?.flatMap( - (cmsError) => cmsError.attributes.errorCodes, - ); + const errorMessages = errors?.flatMap((cmsError) => cmsError.errorCodes); const tileOptions = options.map((tileOption) => ({ ...omitNull(tileOption), image: getImageProps(tileOption.image), diff --git a/app/services/cms/dumpCmsToFile.ts b/app/services/cms/dumpCmsToFile.ts index bed4b3b86..d222cfdbb 100644 --- a/app/services/cms/dumpCmsToFile.ts +++ b/app/services/cms/dumpCmsToFile.ts @@ -10,16 +10,25 @@ async function dumpCmsToFile() { const { CONTENT_FILE_PATH, STRAPI_API } = config(); console.log(`Fetching CMS data from ${STRAPI_API}`); - const locale = "all"; + const locales = ["de", "sg"] as const; const content: Record = {}; for (const apiId of Object.keys(strapiSchemas) as ApiId[]) { - console.log(`Fetching ${apiId}`); - content[apiId] = await getStrapiEntryFromApi({ - apiId, - locale, - pageSize: "500", - }); + const allEntries = []; + process.stdout.write(`Fetching ${apiId}:`); + for (const locale of locales) { + const entries = await getStrapiEntryFromApi({ + apiId, + locale, + pageSize: "500", + }); + if (entries.length > 0 && entries[0] !== null) { + process.stdout.write(` ${locale} (${entries.length}) |`); + allEntries.push(...entries); + } + } + content[apiId] = allEntries; + console.log(); } console.log(`Writing CMS content to ${CONTENT_FILE_PATH}...`); diff --git a/app/services/cms/fetchAllFormFields.ts b/app/services/cms/fetchAllFormFields.ts index 639e94de6..483c4cca0 100644 --- a/app/services/cms/fetchAllFormFields.ts +++ b/app/services/cms/fetchAllFormFields.ts @@ -12,42 +12,38 @@ export async function fetchAllFormFields( flowId: FlowId, environment: string = config().ENVIRONMENT, ): Promise { - const apiId = flowPageApiIdFromFlowType(flows[flowId].flowType); - const filters = [{ field: "flow_ids", nestedField: "flowId", value: flowId }]; - const populate = "form"; - const strapiEntries = await getStrapiEntry({ - apiId, - filters, - populate, - locale: "all", - }); + const args = { + apiId: flowPageApiIdFromFlowType(flows[flowId].flowType), + filters: [{ field: "flow_ids", nestedField: "flowId", value: flowId }], + populate: "form", + }; - const nonEmptyEntries = strapiEntries - .filter((formFlowPage) => formFlowPage !== null) - .filter(({ attributes: { stepId, form } }) => form.length > 0 && stepId); - - const [nonStagingEntries, stagingEntries] = _.partition( - nonEmptyEntries, - ({ attributes: { locale } }) => locale !== "sg", - ).map(formFieldsFromSchema); + const formFields = await getStrapiEntry({ ...args, locale: "de" }).then( + formFieldsFromEntries, + ); - if (environment !== "production") { - // inject staging flowpages - return _.merge(nonStagingEntries, stagingEntries); - } + const formFieldsStaging = + environment !== "production" + ? await getStrapiEntry({ ...args, locale: "sg" }).then( + formFieldsFromEntries, + ) + : {}; - return nonStagingEntries; + return _.merge(formFields, formFieldsStaging); } -function formFieldsFromSchema( - schemas: +function formFieldsFromEntries( + entries: | StrapiSchemas["form-flow-pages"] - | StrapiSchemas["vorab-check-pages"], + | StrapiSchemas["vorab-check-pages"] + | [null], ): FormFieldsMap { return Object.fromEntries( - schemas.map(({ attributes: { stepId, form } }) => [ - stepId, - form.map((formField) => formField.name), - ]), + entries + .filter((entry) => entry?.stepId && entry?.form.length > 0) + .map((entry) => [ + entry!.stepId, + entry!.form.map((formField) => formField.name), + ]), ); } diff --git a/app/services/cms/flattenStrapiErrors.ts b/app/services/cms/flattenStrapiErrors.ts index 8b82277ca..edb80650b 100644 --- a/app/services/cms/flattenStrapiErrors.ts +++ b/app/services/cms/flattenStrapiErrors.ts @@ -2,20 +2,12 @@ import { z } from "zod"; import { HasStrapiIdSchema } from "~/services/cms/models/HasStrapiId"; import { StrapiErrorCategorySchema } from "~/services/cms/models/StrapiErrorCategory"; -export const StrapiErrorRelationSchema = z.object({ - data: z - .array( - HasStrapiIdSchema.extend({ - attributes: StrapiErrorCategorySchema, - }), - ) - .optional(), -}); +export const StrapiErrorRelationSchema = z + .array(StrapiErrorCategorySchema.merge(HasStrapiIdSchema)) + .optional(); type StrapiErrorRelation = z.infer; export const flattenStrapiErrors = (cmsDataErrors: StrapiErrorRelation) => { - return cmsDataErrors.data?.flatMap( - (cmsError) => cmsError.attributes.errorCodes, - ); + return cmsDataErrors?.flatMap((cmsError) => cmsError.errorCodes); }; diff --git a/app/services/cms/getStrapiEntry.ts b/app/services/cms/getStrapiEntry.ts index 5da441376..1acffb172 100644 --- a/app/services/cms/getStrapiEntry.ts +++ b/app/services/cms/getStrapiEntry.ts @@ -3,10 +3,25 @@ import { getStrapiEntryFromApi } from "./getStrapiEntryFromApi"; import { getStrapiEntryFromFile } from "./getStrapiEntryFromFile"; import type { ApiId, StrapiSchemas } from "./schemas"; import { config } from "../env/env.server"; +import { defaultLocale, stagingLocale } from "./models/StrapiLocale"; export type GetStrapiEntry = ( opts: GetStrapiEntryOpts & { apiId: T }, ) => Promise; -export const getStrapiEntry: GetStrapiEntry = +const getterFunction = config().CMS === "FILE" ? getStrapiEntryFromFile : getStrapiEntryFromApi; + +export const getStrapiEntry: GetStrapiEntry = async (opts) => { + if (opts.locale) return getterFunction(opts); + + if (config().ENVIRONMENT === "production") + return getterFunction({ ...opts, locale: defaultLocale }); + + const stagingData = await getterFunction({ ...opts, locale: stagingLocale }); + const returnData = stagingData.at(0) + ? stagingData + : await getterFunction({ ...opts, locale: defaultLocale }); + + return returnData; +}; diff --git a/app/services/cms/getStrapiEntryFromApi.ts b/app/services/cms/getStrapiEntryFromApi.ts index 608e0463b..cbfb3ec84 100644 --- a/app/services/cms/getStrapiEntryFromApi.ts +++ b/app/services/cms/getStrapiEntryFromApi.ts @@ -1,7 +1,6 @@ import axios from "axios"; import type { Filter, GetStrapiEntryOpts } from "./filters"; import type { GetStrapiEntry } from "./getStrapiEntry"; -import { defaultLocale, stagingLocale } from "./models/StrapiLocale"; import type { ApiId, StrapiSchemas } from "./schemas"; import { config } from "../env/env.server"; @@ -21,13 +20,14 @@ const buildUrl = ({ apiId, pageSize, filters, - locale = defaultLocale, - populate = "deep", + locale, + populate = "*", }: GetStrapiEntryOpts) => [ config().STRAPI_API, apiId, `?populate=${populate}`, + `&pLevel`, // https://github.com/NEDDL/strapi-v5-plugin-populate-deep `&locale=${locale}`, pageSize ? `&pagination[pageSize]=${pageSize}` : "", buildFilters(filters), @@ -44,18 +44,6 @@ const makeStrapiRequest = async (url: string) => export const getStrapiEntryFromApi: GetStrapiEntry = async ( opts: GetStrapiEntryOpts, ) => { - const stagingUrl = buildUrl({ ...opts, locale: stagingLocale }); - const stagingData = - opts.locale !== "all" - ? (await makeStrapiRequest(stagingUrl)).data.data - : null; - - const invalidStagingData = - !stagingData || (Array.isArray(stagingData) && stagingData.length === 0); - - const returnData = invalidStagingData - ? (await makeStrapiRequest(buildUrl(opts))).data.data - : stagingData; - + const returnData = (await makeStrapiRequest(buildUrl(opts))).data.data; return Array.isArray(returnData) ? returnData : [returnData]; }; diff --git a/app/services/cms/getStrapiEntryFromFile.ts b/app/services/cms/getStrapiEntryFromFile.ts index c9ddaa340..205c6b5bd 100644 --- a/app/services/cms/getStrapiEntryFromFile.ts +++ b/app/services/cms/getStrapiEntryFromFile.ts @@ -2,16 +2,14 @@ import fs from "node:fs"; import type { GetStrapiEntryOpts } from "./filters"; import type { GetStrapiEntry } from "./getStrapiEntry"; -import { defaultLocale, stagingLocale } from "./models/StrapiLocale"; import { strapiFileSchema, type ApiId, type StrapiSchemas } from "./schemas"; import { config } from "../env/env.server"; let content: StrapiSchemas | undefined; -export const getStrapiEntryFromFile: GetStrapiEntry = async ({ - locale = config().ENVIRONMENT != "production" ? stagingLocale : defaultLocale, - ...opts -}: GetStrapiEntryOpts) => { +export const getStrapiEntryFromFile: GetStrapiEntry = async ( + opts: GetStrapiEntryOpts, +) => { if (!content) { try { const filePath = config().CONTENT_FILE_PATH; @@ -26,36 +24,22 @@ export const getStrapiEntryFromFile: GetStrapiEntry = async ({ } const contentItems = [...content[opts.apiId]].filter( - ({ attributes }) => + (content) => !opts.filters || opts.filters.every(({ field, value, nestedField }) => { - const relevantField = (attributes as Record)[field]; + const relevantField = (content as Record)[field]; if (!nestedField) return relevantField === value; return ( typeof relevantField === "object" && relevantField !== null && - "data" in relevantField && - Array.isArray(relevantField.data) && - relevantField.data.some( - (nestedItem) => nestedItem.attributes[nestedField] === value, - ) + Array.isArray(relevantField) && + relevantField.some((nestedItem) => nestedItem[nestedField] === value) ); }), ); - // search for the locale - let contentItem = contentItems.filter( - (item) => "locale" in item.attributes && item.attributes.locale == locale, - ); - - if (contentItem.length === 0) { - // if the locale is not found, search for the default locale - contentItem = contentItems.filter( - (item) => - "locale" in item.attributes && item.attributes.locale == defaultLocale, - ); - } - - return contentItem as StrapiSchemas[T]; + return contentItems.filter( + (item) => "locale" in item && item.locale == opts.locale, + ) as StrapiSchemas[T]; }; diff --git a/app/services/cms/index.server.ts b/app/services/cms/index.server.ts index b84c525e1..d73812e10 100644 --- a/app/services/cms/index.server.ts +++ b/app/services/cms/index.server.ts @@ -21,21 +21,21 @@ export async function fetchMeta( const filters = [{ value: opts.filterValue, field: "slug" }]; const apiId = "pages"; const pageEntry = await getStrapiEntry({ ...opts, filters, apiId, populate }); - const parsedEntry = HasStrapiMetaSchema.safeParse(pageEntry[0]?.attributes); + const parsedEntry = HasStrapiMetaSchema.safeParse(pageEntry[0]); return parsedEntry.success ? parsedEntry.data.meta : null; } export async function fetchSingleEntry( apiId: T, -): Promise { +): Promise { const strapiEntry = await getStrapiEntry({ apiId }); - return entrySchemas[apiId].parse(strapiEntry)[0].attributes; + return entrySchemas[apiId].parse(strapiEntry)[0]; } async function fetchCollectionEntry( apiId: T, filters?: Filter[], -): Promise { +): Promise { const strapiEntry = await getStrapiEntry({ apiId, filters }); const strapiEntryParsed = collectionSchemas[apiId].safeParse(strapiEntry); @@ -46,7 +46,7 @@ async function fetchCollectionEntry( error.name = "StrapiPageNotFound"; throw error; } - return strapiEntryParsed.data[0].attributes; + return strapiEntryParsed.data[0]; } export const fetchTranslations = async ( @@ -71,7 +71,7 @@ export const fetchFlowPage = ( collection: T, flowId: FlowId, stepId: string, -): Promise => +): Promise => fetchCollectionEntry(collection, [ { field: "stepId", value: stepId }, { field: "flow_ids", nestedField: "flowId", value: flowId }, diff --git a/app/services/cms/models/StrapiFormFlowPage.ts b/app/services/cms/models/StrapiFormFlowPage.ts index d6ccb84e6..bc9eaf258 100644 --- a/app/services/cms/models/StrapiFormFlowPage.ts +++ b/app/services/cms/models/StrapiFormFlowPage.ts @@ -9,9 +9,7 @@ export const StrapiFormFlowPageSchema = z .object({ heading: z.string(), stepId: z.string().nullable(), - flow_ids: z.object({ - data: z.array(z.object({ attributes: StrapiFlowIdSchema })), - }), + flow_ids: z.array(StrapiFlowIdSchema), preHeading: z.string().nullable(), nextButtonLabel: z.string().nullable(), backButtonLabel: z.string().nullable(), diff --git a/app/services/cms/models/StrapiImage.ts b/app/services/cms/models/StrapiImage.ts index 628d7e179..d9dd1143e 100644 --- a/app/services/cms/models/StrapiImage.ts +++ b/app/services/cms/models/StrapiImage.ts @@ -15,37 +15,32 @@ function appendStrapiUrlOnDev(imageUrl: string) { return strapiUrl + imageUrl; } -export const StrapiImageSchema = z.object({ - data: z - .object({ - attributes: z.object({ - name: z.string(), - url: z.string().transform(appendStrapiUrlOnDev), - previewUrl: z.string().url().nullable(), - width: z.number(), - height: z.number(), - size: z.number(), - alternativeText: z.string().nullable(), - ext: z.string().startsWith("."), - mime: z.string().startsWith("image/"), - caption: z.string().nullable(), - formats: z.record(z.string(), z.unknown()).nullable(), - hash: z.string(), - provider: z.string(), - - provider_metadata: z.string().nullable(), - }), - }) - .merge(HasOptionalStrapiIdSchema) - .nullish(), -}); +export const StrapiImageSchema = z + .object({ + name: z.string(), + url: z.string().transform(appendStrapiUrlOnDev), + previewUrl: z.string().url().nullable(), + width: z.number(), + height: z.number(), + size: z.number(), + alternativeText: z.string().nullable(), + ext: z.string().startsWith("."), + mime: z.string().startsWith("image/"), + caption: z.string().nullable(), + formats: z.record(z.string(), z.unknown()).nullable(), + hash: z.string(), + provider: z.string(), + provider_metadata: z.string().nullable(), + }) + .merge(HasOptionalStrapiIdSchema) + .nullish(); export type StrapiImage = z.infer; export const getImageProps = ( cmsData: StrapiImage | null, ): ImageProps | undefined => { - if (!cmsData?.data?.attributes) return undefined; - const { url, width, height, alternativeText } = cmsData.data.attributes; + if (!cmsData) return undefined; + const { url, width, height, alternativeText } = cmsData; return { url, width, height, alternativeText: alternativeText ?? undefined }; }; diff --git a/app/services/cms/models/StrapiInfoBoxItem.ts b/app/services/cms/models/StrapiInfoBoxItem.ts index cd21e1fb8..e03266a03 100644 --- a/app/services/cms/models/StrapiInfoBoxItem.ts +++ b/app/services/cms/models/StrapiInfoBoxItem.ts @@ -13,7 +13,7 @@ export const StrapiInfoBoxItemSchema = z .object({ label: StrapiHeadingSchema.nullable(), headline: StrapiHeadingSchema.nullable(), - image: StrapiImageSchema, + image: StrapiImageSchema.nullable(), content: z.string().nullable(), detailsSummary: z.array(StrapiDetailsSchema), buttons: z.array(StrapiButtonSchema), diff --git a/app/services/cms/models/StrapiLocale.ts b/app/services/cms/models/StrapiLocale.ts index 1137a83ec..6d7e0df06 100644 --- a/app/services/cms/models/StrapiLocale.ts +++ b/app/services/cms/models/StrapiLocale.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -export const StrapiLocaleSchema = z.enum(["de", "en", "sg", "all"]); +export const StrapiLocaleSchema = z.enum(["de", "en", "sg"]); export const defaultLocale = StrapiLocaleSchema.Values.de; export const stagingLocale = StrapiLocaleSchema.Values.sg; export type StrapiLocale = z.infer; diff --git a/app/services/cms/models/StrapiResultPage.ts b/app/services/cms/models/StrapiResultPage.ts index 467933ee2..14c481e45 100644 --- a/app/services/cms/models/StrapiResultPage.ts +++ b/app/services/cms/models/StrapiResultPage.ts @@ -13,22 +13,12 @@ import { StrapiResultPageTypeSchema } from "./StrapiResultPageType"; export const StrapiResultPageSchema = z .object({ stepId: z.string().nullable(), - flow_ids: z.object({ - data: z.array(z.object({ attributes: StrapiFlowIdSchema })), - }), + flow_ids: z.array(StrapiFlowIdSchema), pageType: StrapiResultPageTypeSchema, heading: StrapiHeadingSchema, hintText: StrapiParagraphSchema.nullable(), - documents: z.object({ - data: HasStrapiIdSchema.extend({ - attributes: StrapiElementWithIdSchema, - }).nullable(), - }), - nextSteps: z.object({ - data: HasStrapiIdSchema.extend({ - attributes: StrapiElementWithIdSchema, - }).nullable(), - }), + documents: StrapiElementWithIdSchema.merge(HasStrapiIdSchema).nullable(), + nextSteps: StrapiElementWithIdSchema.merge(HasStrapiIdSchema).nullable(), freeZone: z.array(StrapiContentComponentSchema), nextLink: StrapiLinkSchema.nullable(), }) diff --git a/app/services/cms/models/StrapiVorabCheckPage.ts b/app/services/cms/models/StrapiVorabCheckPage.ts index 5f1ab3957..83b73b5b5 100644 --- a/app/services/cms/models/StrapiVorabCheckPage.ts +++ b/app/services/cms/models/StrapiVorabCheckPage.ts @@ -8,10 +8,7 @@ import { StrapiFormComponentSchema } from "./StrapiFormComponent"; export const StrapiVorabCheckPageSchema = z .object({ stepId: z.string().nullable(), - flow_ids: z.object({ - data: z.array(z.object({ attributes: StrapiFlowIdSchema })), - }), - + flow_ids: z.array(StrapiFlowIdSchema), pre_form: z.array(StrapiContentComponentSchema), form: z.array(StrapiFormComponentSchema), nextButtonLabel: z.string().nullable(), diff --git a/app/services/cms/models/__test__/StrapiImage.test.ts b/app/services/cms/models/__test__/StrapiImage.test.ts index ac9e93eb2..08dda8169 100644 --- a/app/services/cms/models/__test__/StrapiImage.test.ts +++ b/app/services/cms/models/__test__/StrapiImage.test.ts @@ -3,24 +3,20 @@ import { getImageProps } from "~/services/cms/models/StrapiImage"; describe("getImageProps", () => { it("returns props in correct format", () => { const result = getImageProps({ - data: { - attributes: { - name: "name", - url: "url", - previewUrl: "previewUrl", - width: 1, - height: 2, - size: 3, - alternativeText: "alternativeText", - ext: ".ext", - mime: "image/mime", - caption: "caption", - formats: { key: "value" }, - hash: "hash", - provider: "provider", - provider_metadata: "provider_metadata", - }, - }, + name: "name", + url: "url", + previewUrl: "previewUrl", + width: 1, + height: 2, + size: 3, + alternativeText: "alternativeText", + ext: ".ext", + mime: "image/mime", + caption: "caption", + formats: { key: "value" }, + hash: "hash", + provider: "provider", + provider_metadata: "provider_metadata", }); expect(result).toEqual({ @@ -32,7 +28,7 @@ describe("getImageProps", () => { }); it("does not return an empty object", () => { - const result = getImageProps({}); + const result = getImageProps(null); expect(result).toEqual(undefined); }); }); diff --git a/app/services/cms/schemas.ts b/app/services/cms/schemas.ts index 8a9531deb..24b52215c 100644 --- a/app/services/cms/schemas.ts +++ b/app/services/cms/schemas.ts @@ -9,29 +9,25 @@ import { StrapiTranslationSchema } from "./models/StrapiTranslations"; import { StrapiVorabCheckPageSchema } from "./models/StrapiVorabCheckPage"; export const entrySchemas = { - "page-header": z.array(z.object({ attributes: StrapiPageHeaderSchema })), - footer: z.array(z.object({ attributes: StrapiFooterSchema })), - "cookie-banner": z.array(z.object({ attributes: StrapiCookieBannerSchema })), + "page-header": z.array(StrapiPageHeaderSchema), + footer: z.array(StrapiFooterSchema), + "cookie-banner": z.array(StrapiCookieBannerSchema), }; export type SingleEntryId = keyof typeof entrySchemas; const _entrySchemas = z.object(entrySchemas); export type EntrySchemas = z.infer; export const flowPageSchemas = { - "result-pages": z.array(z.object({ attributes: StrapiResultPageSchema })), - "vorab-check-pages": z.array( - z.object({ attributes: StrapiVorabCheckPageSchema }), - ), - "form-flow-pages": z.array( - z.object({ attributes: StrapiFormFlowPageSchema }), - ), + "result-pages": z.array(StrapiResultPageSchema), + "vorab-check-pages": z.array(StrapiVorabCheckPageSchema), + "form-flow-pages": z.array(StrapiFormFlowPageSchema), }; export type FlowPageId = keyof typeof flowPageSchemas; export const collectionSchemas = { - pages: z.array(z.object({ attributes: StrapiPageSchema })), - translations: z.array(z.object({ attributes: StrapiTranslationSchema })), + pages: z.array(StrapiPageSchema), + translations: z.array(StrapiTranslationSchema), ...flowPageSchemas, }; diff --git a/app/services/errorPages/fallbackInfobox.ts b/app/services/errorPages/fallbackInfobox.ts index 1f5ebf3a4..28d8d68e2 100644 --- a/app/services/errorPages/fallbackInfobox.ts +++ b/app/services/errorPages/fallbackInfobox.ts @@ -15,7 +15,6 @@ const fallbackStrapiInfoBox = { look: "ds-heading-02-reg", tagName: "h1", }, - image: {}, content: "Leider ist ein Fehler ist aufgetreten. Wir arbeiten ständig an der Verbesserung unserer Service und sind bereits informiert.\n\nBitte versuchen Sie es später noch einmal.", buttons: [], diff --git a/app/services/flow/__test__/pruner.test.ts b/app/services/flow/__test__/pruner.test.ts index 12cf7d669..3d5d54c72 100644 --- a/app/services/flow/__test__/pruner.test.ts +++ b/app/services/flow/__test__/pruner.test.ts @@ -53,120 +53,91 @@ describe("pruner", () => { it("prunes irrelevant data", async () => { const strapiEntries = [ { - attributes: { stepId: "/start", form: [] }, + stepId: "/start", + form: [], }, { - attributes: { - stepId: "/grundvoraussetzungen/rechtsschutzversicherung", - form: [{ name: "rechtsschutzversicherung" }], - }, + stepId: "/grundvoraussetzungen/rechtsschutzversicherung", + form: [{ name: "rechtsschutzversicherung" }], }, { - attributes: { - stepId: "/grundvoraussetzungen/wurde-verklagt", - form: [{ name: "wurdeVerklagt" }], - }, + stepId: "/grundvoraussetzungen/wurde-verklagt", + form: [{ name: "wurdeVerklagt" }], }, { - attributes: { - stepId: "/grundvoraussetzungen/klage-eingereicht", - form: [{ name: "klageEingereicht" }], - }, + stepId: "/grundvoraussetzungen/klage-eingereicht", + form: [{ name: "klageEingereicht" }], }, { - attributes: { - stepId: "/grundvoraussetzungen/beratungshilfe-beantragt", - form: [{ name: "beratungshilfeBeantragt" }], - }, + stepId: "/grundvoraussetzungen/beratungshilfe-beantragt", + form: [{ name: "beratungshilfeBeantragt" }], }, { - attributes: { - stepId: "/grundvoraussetzungen/eigeninitiative-grundvorraussetzung", - form: [{ name: "eigeninitiativeGrundvorraussetzung" }], - }, + stepId: "/grundvoraussetzungen/eigeninitiative-grundvorraussetzung", + form: [{ name: "eigeninitiativeGrundvorraussetzung" }], }, { - attributes: { - stepId: "/finanzielle-angaben/einkommen/staatliche-leistungen", - form: [{ name: "staatlicheLeistungen" }], - }, + stepId: "/finanzielle-angaben/einkommen/staatliche-leistungen", + form: [{ name: "staatlicheLeistungen" }], }, { - attributes: { - stepId: "/finanzielle-angaben/eigentum/bankkonten-frage", - form: [{ name: "hasBankkonto" }], - }, + stepId: "/finanzielle-angaben/eigentum/bankkonten-frage", + form: [{ name: "hasBankkonto" }], }, { - attributes: { - stepId: "/finanzielle-angaben/eigentum/geldanlagen-frage", - form: [{ name: "hasGeldanlage" }], - }, + stepId: "/finanzielle-angaben/eigentum/geldanlagen-frage", + form: [{ name: "hasGeldanlage" }], }, { - attributes: { - stepId: "/finanzielle-angaben/eigentum/wertgegenstaende-frage", - form: [{ name: "hasWertsache" }], - }, + stepId: "/finanzielle-angaben/eigentum/wertgegenstaende-frage", + form: [{ name: "hasWertsache" }], }, { - attributes: { - stepId: "/finanzielle-angaben/eigentum/grundeigentum-frage", - form: [{ name: "hasGrundeigentum" }], - }, + stepId: "/finanzielle-angaben/eigentum/grundeigentum-frage", + form: [{ name: "hasGrundeigentum" }], }, { - attributes: { - stepId: "/finanzielle-angaben/eigentum/kraftfahrzeuge-frage", - form: [{ name: "hasKraftfahrzeug" }], - }, + stepId: "/finanzielle-angaben/eigentum/kraftfahrzeuge-frage", + form: [{ name: "hasKraftfahrzeug" }], }, { - attributes: { - stepId: "/finanzielle-angaben/eigentum/gesamtwert", - form: [{ name: "eigentumTotalWorth" }], - }, + stepId: "/finanzielle-angaben/eigentum/gesamtwert", + form: [{ name: "eigentumTotalWorth" }], }, { - attributes: { - stepId: - "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/art", - form: [{ name: "geldanlagen#art" }], - }, + stepId: + "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/art", + form: [{ name: "geldanlagen#art" }], }, { - attributes: { - stepId: - "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/forderung", - form: [ - { name: "geldanlagen#forderung" }, - { name: "geldanlagen#eigentuemer" }, - { name: "geldanlagen#wert" }, - ], - }, + stepId: + "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/forderung", + form: [ + { name: "geldanlagen#forderung" }, + { name: "geldanlagen#eigentuemer" }, + { name: "geldanlagen#wert" }, + ], }, + { - attributes: { - stepId: - "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/befristet", - form: [ - { name: "geldanlagen#eigentuemer" }, - { name: "geldanlagen#befristetArt" }, - { name: "geldanlagen#verwendungszweck" }, - { name: "geldanlagen#wert" }, - { name: "geldanlagen#auszahlungdatum" }, - ], - }, + stepId: + "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/befristet", + form: [ + { name: "geldanlagen#eigentuemer" }, + { name: "geldanlagen#befristetArt" }, + { name: "geldanlagen#verwendungszweck" }, + { name: "geldanlagen#wert" }, + { name: "geldanlagen#auszahlungdatum" }, + ], }, + { - attributes: { - stepId: - "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/bargeld", - form: [ - { name: "geldanlagen#eigentuemer" }, - { name: "geldanlagen#wert" }, - ], - }, + stepId: + "/finanzielle-angaben/eigentum-zusammenfassung/geldanlagen/bargeld", + form: [ + { name: "geldanlagen#eigentuemer" }, + { name: "geldanlagen#wert" }, + ], }, ]; diff --git a/scripts/unusedStrapiEntries.ts b/scripts/unusedStrapiEntries.ts index 9976a682d..dbcbf2f5d 100644 --- a/scripts/unusedStrapiEntries.ts +++ b/scripts/unusedStrapiEntries.ts @@ -8,10 +8,8 @@ import { type Config } from "~/services/flow/server/buildFlowController"; const contentFilePath = "./content.json"; type MinimalPage = { - attributes: { - flow_ids: StrapiSchemas["form-flow-pages"][0]["attributes"]["flow_ids"]; - stepId: StrapiSchemas["form-flow-pages"][0]["attributes"]["stepId"]; - }; + flow_ids: StrapiSchemas["form-flow-pages"][0]["flow_ids"]; + stepId: StrapiSchemas["form-flow-pages"][0]["stepId"]; }; // recursivly traverse states object, while concatenating nested names. Returns a flat list @@ -35,18 +33,16 @@ function allStateNames(xstateConfigStates: Config["states"]): string[] { function urlsFromPages(pages: MinimalPage[]) { // A page containing multiple flowIDs results in multiple URLs return pages.flatMap((page) => - page.attributes.flow_ids.data.map( - (flowId) => flowId.attributes.flowId + page.attributes.stepId, - ), + page.flow_ids.map((flowId) => flowId.flowId + page.stepId), ); } function partitionPagesByStepId(pages: MinimalPage[]) { - return _.partition(pages, (page) => page.attributes.stepId !== null); + return _.partition(pages, (page) => page.stepId !== null); } function partitionPagesByFlowId(pages: MinimalPage[]) { - return _.partition(pages, (page) => page.attributes.flow_ids.data.length > 0); + return _.partition(pages, (page) => page.flow_ids.length > 0); } function unusedStrapiEntry() { diff --git a/tests/factories/cmsModels/strapiFlowPage.ts b/tests/factories/cmsModels/strapiFlowPage.ts index 493ea9b27..b34daee9c 100644 --- a/tests/factories/cmsModels/strapiFlowPage.ts +++ b/tests/factories/cmsModels/strapiFlowPage.ts @@ -12,9 +12,7 @@ export function getStrapiFlowPage( backButtonLabel: null, form: params.form ?? [], stepId: params.stepId ?? faker.lorem.word(), - flow_ids: { - data: [{ attributes: { flowId: "/beratungshilfe/antrag" } }], - }, + flow_ids: [{ flowId: "/beratungshilfe/antrag" }], pre_form: [], post_form: [], locale: params.locale ?? "de", @@ -36,7 +34,7 @@ export function getStrapiFormComponent( label: faker.lorem.word(), name: params.name ?? faker.lorem.word(), width: "characters3", - errors: {}, + errors: [], placeholder: null, suffix: null, helperText: null, diff --git a/tests/factories/cmsModels/strapiImage.ts b/tests/factories/cmsModels/strapiImage.ts index 2c0b722d1..267c0f565 100644 --- a/tests/factories/cmsModels/strapiImage.ts +++ b/tests/factories/cmsModels/strapiImage.ts @@ -7,23 +7,19 @@ export function getStrapiImage(): StrapiImage { const ext = faker.helpers.arrayElement(["png", "jpg", "svg", "gif"]); return { - data: { - attributes: { - name: `${name}.${ext}`, - url: `${faker.internet.url()}/${name}_${hash}.${ext}`, - previewUrl: null, - width: faker.number.int({ min: 1, max: 640 }), - height: faker.number.int({ min: 1, max: 480 }), - size: faker.number.float({ max: 10, multipleOf: 0.01 }), - alternativeText: faker.lorem.sentence(), - ext: `.${ext}`, - mime: `image/${ext}`, - caption: null, - formats: null, - hash: `${name}_${hash}`, - provider: "aws-s3", - provider_metadata: null, - }, - }, + name: `${name}.${ext}`, + url: `${faker.internet.url()}/${name}_${hash}.${ext}`, + previewUrl: null, + width: faker.number.int({ min: 1, max: 640 }), + height: faker.number.int({ min: 1, max: 480 }), + size: faker.number.float({ max: 10, multipleOf: 0.01 }), + alternativeText: faker.lorem.sentence(), + ext: `.${ext}`, + mime: `image/${ext}`, + caption: null, + formats: null, + hash: `${name}_${hash}`, + provider: "aws-s3", + provider_metadata: null, }; } diff --git a/tests/integration/util.test.ts b/tests/integration/util.test.ts index f4136b1b7..206cd0033 100644 --- a/tests/integration/util.test.ts +++ b/tests/integration/util.test.ts @@ -13,30 +13,24 @@ describe("integration testing helper functions", () => { const result = compileAllStrapiPages("/beratungshilfe/antrag", { "/beratungshilfe/antrag": { "vorab-check-pages": [ + // @ts-expect-error missing attributes { - // @ts-expect-error missing attributes - attributes: { - stepId: "step-1", - locale: "de", - }, + stepId: "step-1", + locale: "de", }, ], "form-flow-pages": [ + // @ts-expect-error missing attributes { - // @ts-expect-error missing attributes - attributes: { - stepId: "step-2", - locale: "de", - }, + stepId: "step-2", + locale: "de", }, ], "result-pages": [ + // @ts-expect-error missing attributes { - // @ts-expect-error missing attributes - attributes: { - stepId: "step-3", - locale: "de", - }, + stepId: "step-3", + locale: "de", }, ], }, @@ -52,19 +46,15 @@ describe("integration testing helper functions", () => { const result = compileAllStrapiPages("/beratungshilfe/antrag", { "/beratungshilfe/antrag": { "vorab-check-pages": [ + // @ts-expect-error missing attributes { - // @ts-expect-error missing attributes - attributes: { - stepId: "step-1", - locale: "en", - }, + stepId: "step-1", + locale: "en", }, + // @ts-expect-error missing attributes { - // @ts-expect-error missing attributes - attributes: { - stepId: "step-1", - locale: "en", - }, + stepId: "step-1", + locale: "en", }, ], "form-flow-pages": [], diff --git a/tests/integration/util.ts b/tests/integration/util.ts index 430f4e4d7..7734851de 100644 --- a/tests/integration/util.ts +++ b/tests/integration/util.ts @@ -30,8 +30,8 @@ export function compileAllStrapiPages( "result-pages": resultPages, } = allStrapiData[flowId]; return [...formFlowPages, ...resultPages, ...vorabCheckPages] - .filter((page) => page.attributes.locale === defaultLocale) - .map((page) => page.attributes.stepId); + .filter((page) => page.locale === defaultLocale) + .map((page) => page.stepId); } /** diff --git a/tests/integration/verifyCmsContent.test.ts b/tests/integration/verifyCmsContent.test.ts index c5e71da41..772a3aa97 100644 --- a/tests/integration/verifyCmsContent.test.ts +++ b/tests/integration/verifyCmsContent.test.ts @@ -47,21 +47,21 @@ beforeAll(async () => { await Promise.all([ getStrapiEntry({ apiId: "vorab-check-pages", - locale: "all", + locale: "de", filters: [ { value: flowId, field: "flow_ids", nestedField: "flowId" }, ], }), getStrapiEntry({ apiId: "result-pages", - locale: "all", + locale: "de", filters: [ { value: flowId, field: "flow_ids", nestedField: "flowId" }, ], }), getStrapiEntry({ apiId: "form-flow-pages", - locale: "all", + locale: "de", filters: [ { value: flowId, field: "flow_ids", nestedField: "flowId" }, ],