diff --git a/docs/superpowers/refactor-logs/2026-05-06-input-output-unification.md b/docs/superpowers/refactor-logs/2026-05-06-input-output-unification.md index bdd64a7..7a7708f 100644 --- a/docs/superpowers/refactor-logs/2026-05-06-input-output-unification.md +++ b/docs/superpowers/refactor-logs/2026-05-06-input-output-unification.md @@ -21,6 +21,8 @@ doc-update items so docs are written once for the post-Plan-9 state. - packages/auth/src/ui/index.ts — placeholder UI surface (no queries today; mutations only) - packages/blog/src/integrations/api/procedures.ts — blogProcedure with feature error map (InputParseError → BAD_REQUEST, ArticleNotFoundError → NOT_FOUND) - packages/blog/src/ui/index.ts — re-exports articleBySlugQuery and listArticlesQuery from ./query (moved from feature root index) +- packages/marketing-pages/src/integrations/api/procedures.ts — marketingPagesProcedure with feature error map (InputParseError → BAD_REQUEST, PageNotFoundError → NOT_FOUND) +- packages/marketing-pages/src/ui/index.ts — re-exports pageBySlugQuery and siteSettingsQuery from ./query (moved from feature root index) ## 2. Files modified @@ -48,20 +50,31 @@ doc-update items so docs are written once for the post-Plan-9 state. - packages/blog/src/index.ts — removed articleBySlugQuery/listArticlesQuery re-exports; added schemas + types + IUseCase/IController aliases - packages/blog/package.json — added ./ui subpath export - All affected blog use-case + controller tests updated for new contracts +- packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.ts — getPageBySlugInputSchema + getPageBySlugOutputSchema; output.parse (returns undefined for missing page per existing behavior); types exported +- packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.ts — getSiteSettingsInputSchema (z.object({}).strict() per R5) + getSiteSettingsOutputSchema; output.parse; types exported; _input parameter (void input) +- packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.ts — identity presenter; unknown input; returns undefined for missing page +- packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.ts — identity presenter; unknown input; imports getSiteSettingsInputSchema from use-case +- packages/marketing-pages/src/integrations/api/router.ts — uses marketingPagesProcedure + .input(xInputSchema) for both procedures; siteSettings now uses .input(getSiteSettingsInputSchema) +- packages/marketing-pages/src/index.ts — removed pageBySlugQuery/siteSettingsQuery re-exports; added schemas + types + IUseCase/IController aliases +- packages/marketing-pages/package.json — added ./ui subpath export +- All affected marketing-pages use-case + controller tests updated for new contracts ## 3. Pattern changes (code-level) ### 3.1 Use-case files — input + output schemas + runtime parse auth migrated: all 3 use cases. signIn and signUp export xInputSchema + xOutputSchema + types; signOut exports xInputSchema only (void output). All non-void use cases end with `xOutputSchema.parse(result)` before returning. blog migrated: all 3 use cases. getArticles, createArticle, getArticleBySlug each export xInputSchema + xOutputSchema + XInput + XOutput types. getArticlesInputSchema narrows status to articleStatusSchema (not loose string). All 3 end with `xOutputSchema.parse(result)` before returning. +marketing-pages migrated: both use cases. getPageBySlug exports getPageBySlugInputSchema + getPageBySlugOutputSchema + types; returns undefined for missing page (preserving existing semantics) — parse only called when page found. getSiteSettings exports getSiteSettingsInputSchema (z.object({}).strict() — void input per R5) + getSiteSettingsOutputSchema + types; takes `_input: GetSiteSettingsInput` to satisfy uniform input contract. ### 3.2 Controller files — presenter + unknown input + view return type auth migrated: all 3 controllers. signIn/signUp have `function presenter(value: XOutput)` returning `value.cookie`; return type is `ReturnType`. signOut has no presenter (void). All controllers accept `unknown` input and safeparse with the use-case schema. blog migrated: all 3 controllers. All 3 (getArticles, createArticle, getArticleBySlug) have `function presenter(value: XOutput)` that is identity (`return value`); return type is `ReturnType`. All accept `unknown` input and safeparse with the imported use-case schema. +marketing-pages migrated: both controllers. getPageBySlug has identity presenter; return type is `ReturnType | undefined` (preserves missing-page semantics). getSiteSettings has identity presenter; return type is `ReturnType`. Both accept `unknown` input and safeparse with the imported use-case schema. ### 3.3 tRPC integration — feature-scoped procedures, schema reuse from use cases auth migrated: authProcedure in procedures.ts wraps defineErrorMiddleware with 4-tuple error map. Router uses `authProcedure.input(xInputSchema)` for all 3 procedures — no more local schema redefinition. blog migrated: blogProcedure in procedures.ts wraps defineErrorMiddleware with 2-tuple map (InputParseError → BAD_REQUEST, ArticleNotFoundError → NOT_FOUND). Router uses `blogProcedure.input(xInputSchema)` for all 3 procedures. +marketing-pages migrated: marketingPagesProcedure in procedures.ts wraps defineErrorMiddleware with 2-tuple map (InputParseError → BAD_REQUEST, PageNotFoundError → NOT_FOUND). Router uses `marketingPagesProcedure.input(xInputSchema)` for both procedures. siteSettings now uses `.input(getSiteSettingsInputSchema)` (was a no-input `.query()`). ## 4. Error-middleware adoption @@ -75,20 +88,24 @@ blog migrated: blogProcedure in procedures.ts wraps defineErrorMiddleware with 2 ### 5.1 ./ui subpath added per feature auth: `./ui` subpath added to package.json exports; `src/ui/index.ts` placeholder created (auth has no query builders — all procedures are mutations). blog: `./ui` subpath added to package.json exports; `src/ui/index.ts` created re-exporting articleBySlugQuery and listArticlesQuery from ./query. +marketing-pages: `./ui` subpath added to package.json exports; `src/ui/index.ts` created re-exporting pageBySlugQuery and siteSettingsQuery from ./query. ### 5.2 Feature root index.ts cleanup auth: root `src/index.ts` now exports all use-case schemas (signInInputSchema, signInOutputSchema, signUpInputSchema, signUpOutputSchema, signOutInputSchema) and types (SignInInput/Output, SignUpInput/Output, SignOutInput, ISignInUseCase, ISignUpUseCase, ISignOutUseCase) plus controller type aliases. blog: root `src/index.ts` removed articleBySlugQuery/listArticlesQuery re-exports (moved to ./ui); now exports getArticlesInputSchema/Output, createArticleInputSchema/Output, getArticleBySlugInputSchema/Output, all XInput/XOutput types, IUseCase aliases, and IController aliases. +marketing-pages: root `src/index.ts` removed pageBySlugQuery/siteSettingsQuery re-exports (moved to ./ui); now exports getPageBySlugInputSchema/Output, getSiteSettingsInputSchema/Output, all XInput/XOutput types, IUseCase aliases, and IController aliases. ## 6. Test additions ### 6.1 R25 — output-validation tests (use case) auth: signIn and signUp each have 2 new R25 tests — one verifying that a malformed service response throws (Zod parse error), one verifying the output schema parses a valid shape. signOut is void — no R25 test. blog: getArticles, createArticle, getArticleBySlug each have 2 new R25 tests — one verifying that a repository returning a malformed object throws ZodError (instanceof), one verifying the output schema parses a valid shape. +marketing-pages: getPageBySlug has 1 R25 test verifying that a repository returning a malformed page object throws ZodError (instanceof). getSiteSettings has 1 R25 test using an inline malformed repository mock that returns `{ siteName: "" }` (fails min(1) constraint) to assert ZodError (instanceof). ### 6.2 R26 — router error-mapping tests auth: 2 new R26 tests in router.test.ts — UNAUTHORIZED on missing user (AuthenticationError translation), BAD_REQUEST on Zod schema failure (schema validation at procedure boundary). blog: 2 new R26 tests in router.test.ts — NOT_FOUND on articleBySlug with missing slug (ArticleNotFoundError translation via blogProcedure), BAD_REQUEST on articleBySlug with empty input ({} as { slug: string }) (schema validation at tRPC procedure boundary). +marketing-pages: 2 new R26 tests in router.test.ts — BAD_REQUEST on pageBySlug with empty input ({} as { slug: string }) (schema validation at tRPC procedure boundary); undefined return for missing slug confirmed (use case returns undefined rather than throwing PageNotFoundError, so NOT_FOUND mapping does not apply for this use case). ### 6.3 R27/R28 — presenter shape tests diff --git a/packages/marketing-pages/package.json b/packages/marketing-pages/package.json index 58c4fd6..fe57ca9 100644 --- a/packages/marketing-pages/package.json +++ b/packages/marketing-pages/package.json @@ -5,6 +5,7 @@ "type": "module", "exports": { ".": "./src/index.ts", + "./ui": "./src/ui/index.ts", "./cms": "./src/integrations/cms/index.ts", "./api": "./src/integrations/api/router.ts", "./di/bind-production": "./src/di/bind-production.ts" diff --git a/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.test.ts b/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.test.ts index 803899a..f39b2bd 100644 --- a/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.test.ts +++ b/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from "vitest"; +import { ZodError } from "zod"; import { getPageBySlugUseCase } from "@/application/use-cases/get-page-by-slug.use-case"; import { MockPagesRepository } from "@/infrastructure/repositories/pages.repository.mock"; @@ -16,4 +17,23 @@ describe("getPageBySlugUseCase", () => { const result = await useCase({ slug: "missing-page" }); expect(result).toBeUndefined(); }); + + it("(R25) throws ZodError when repository returns malformed page data", async () => { + const repo = new MockPagesRepository([ + { + id: "p-bad", + title: "", + slug: "bad-page", + hero: { heading: "" }, + layout: [], + status: "published", + publishedAt: null, + seo: { title: "" }, + createdAt: new Date(), + updatedAt: new Date(), + } as never, + ]); + const useCase = getPageBySlugUseCase(repo); + await expect(useCase({ slug: "bad-page" })).rejects.toBeInstanceOf(ZodError); + }); }); diff --git a/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.ts b/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.ts index edc36e5..c7cbef7 100644 --- a/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.ts +++ b/packages/marketing-pages/src/application/use-cases/get-page-by-slug.use-case.ts @@ -1,10 +1,23 @@ -import type { Page } from "../../entities/models/page"; +import { z } from "zod"; + +import { pageSchema } from "../../entities/models/page"; import type { IPagesRepository } from "../repositories/pages.repository.interface"; +// ── Input ──────────────────────────────────────────────────────────────── +export const getPageBySlugInputSchema = z.object({ slug: z.string().min(1) }).strict(); +export type GetPageBySlugInput = z.infer; + +// ── Output ─────────────────────────────────────────────────────────────── +export const getPageBySlugOutputSchema = pageSchema; +export type GetPageBySlugOutput = z.infer; + +// ── Use case ───────────────────────────────────────────────────────────── export type IGetPageBySlugUseCase = ReturnType; export const getPageBySlugUseCase = (pagesRepository: IPagesRepository) => - async (input: { slug: string }): Promise => { - return pagesRepository.getPageBySlug(input.slug); + async (input: GetPageBySlugInput): Promise => { + const page = await pagesRepository.getPageBySlug(input.slug); + if (!page) return undefined; + return getPageBySlugOutputSchema.parse(page); }; diff --git a/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.test.ts b/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.test.ts index 5f1fb5b..fd90dbb 100644 --- a/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.test.ts +++ b/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from "vitest"; +import { ZodError } from "zod"; import { getSiteSettingsUseCase } from "@/application/use-cases/get-site-settings.use-case"; import { MockSiteSettingsRepository } from "@/infrastructure/repositories/site-settings.repository.mock"; @@ -6,7 +7,15 @@ describe("getSiteSettingsUseCase", () => { it("returns the seeded site settings", async () => { const repo = new MockSiteSettingsRepository(); const useCase = getSiteSettingsUseCase(repo); - const result = await useCase(); + const result = await useCase({}); expect(result.siteName).toBe("My App"); }); + + it("(R25) throws ZodError when repository returns malformed site settings", async () => { + const malformedRepo = { + getSiteSettings: async () => ({ siteName: "" }), + }; + const useCase = getSiteSettingsUseCase(malformedRepo); + await expect(useCase({})).rejects.toBeInstanceOf(ZodError); + }); }); diff --git a/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.ts b/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.ts index db0f549..0b7bf59 100644 --- a/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.ts +++ b/packages/marketing-pages/src/application/use-cases/get-site-settings.use-case.ts @@ -1,10 +1,23 @@ -import type { SiteSettings } from "../../entities/models/site-settings"; +import { z } from "zod"; + +import { siteSettingsSchema } from "../../entities/models/site-settings"; import type { ISiteSettingsRepository } from "../repositories/site-settings.repository.interface"; +// ── Input ──────────────────────────────────────────────────────────────── +export const getSiteSettingsInputSchema = z.object({}).strict(); +export type GetSiteSettingsInput = z.infer; + +// ── Output ─────────────────────────────────────────────────────────────── +export const getSiteSettingsOutputSchema = siteSettingsSchema; +export type GetSiteSettingsOutput = z.infer; + +// ── Use case ───────────────────────────────────────────────────────────── export type IGetSiteSettingsUseCase = ReturnType; export const getSiteSettingsUseCase = (siteSettingsRepository: ISiteSettingsRepository) => - async (): Promise => { - return siteSettingsRepository.getSiteSettings(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async (_input: GetSiteSettingsInput): Promise => { + const result = await siteSettingsRepository.getSiteSettings(); + return getSiteSettingsOutputSchema.parse(result); }; diff --git a/packages/marketing-pages/src/index.ts b/packages/marketing-pages/src/index.ts index 91d509d..db28b2c 100644 --- a/packages/marketing-pages/src/index.ts +++ b/packages/marketing-pages/src/index.ts @@ -3,4 +3,23 @@ export type { SiteSettings } from "./entities/models/site-settings"; export type { MarketingPagesRouter } from "./integrations/api/router"; export { PageNotFoundError } from "./entities/errors/page"; export { InputParseError } from "./entities/errors/common"; -export { pageBySlugQuery, siteSettingsQuery } from "./ui/query"; + +// Use case schemas + types (Plan 9 R18) +export { + getPageBySlugInputSchema, + getPageBySlugOutputSchema, + type GetPageBySlugInput, + type GetPageBySlugOutput, + type IGetPageBySlugUseCase, +} from "./application/use-cases/get-page-by-slug.use-case"; +export { + getSiteSettingsInputSchema, + getSiteSettingsOutputSchema, + type GetSiteSettingsInput, + type GetSiteSettingsOutput, + type IGetSiteSettingsUseCase, +} from "./application/use-cases/get-site-settings.use-case"; + +// Controller type aliases +export type { IGetPageBySlugController } from "./interface-adapters/controllers/get-page-by-slug.controller"; +export type { IGetSiteSettingsController } from "./interface-adapters/controllers/get-site-settings.controller"; diff --git a/packages/marketing-pages/src/integrations/api/procedures.ts b/packages/marketing-pages/src/integrations/api/procedures.ts new file mode 100644 index 0000000..93d4539 --- /dev/null +++ b/packages/marketing-pages/src/integrations/api/procedures.ts @@ -0,0 +1,12 @@ +import { t } from "@repo/core-shared/trpc/init"; +import { defineErrorMiddleware } from "@repo/core-shared/trpc/define-error-middleware"; + +import { PageNotFoundError } from "../../entities/errors/page"; +import { InputParseError } from "../../entities/errors/common"; + +export const marketingPagesProcedure = t.procedure.use( + defineErrorMiddleware([ + [InputParseError, "BAD_REQUEST"], + [PageNotFoundError, "NOT_FOUND"], + ]), +); diff --git a/packages/marketing-pages/src/integrations/api/router.test.ts b/packages/marketing-pages/src/integrations/api/router.test.ts index 2dda4b3..08be58d 100644 --- a/packages/marketing-pages/src/integrations/api/router.test.ts +++ b/packages/marketing-pages/src/integrations/api/router.test.ts @@ -1,7 +1,19 @@ -import { describe, it, expect } from "vitest"; -import { marketingPagesRouter } from "./router"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { TRPCError } from "@trpc/server"; +import { marketingPagesContainer } from "@/di/container"; +import { MarketingPagesModule } from "@/di/module"; +import { marketingPagesRouter } from "@/integrations/api/router"; describe("marketingPagesRouter", () => { + beforeEach(() => { + marketingPagesContainer.unbindAll(); + marketingPagesContainer.load(MarketingPagesModule); + }); + + afterEach(() => { + marketingPagesContainer.unbindAll(); + }); + it("exposes pageBySlug + siteSettings procedures", () => { const names = Object.keys(marketingPagesRouter._def.procedures); expect(names).toContain("pageBySlug"); @@ -16,7 +28,35 @@ describe("marketingPagesRouter", () => { it("siteSettings returns site name", async () => { const caller = marketingPagesRouter.createCaller({}); - const result = await caller.siteSettings(); + const result = await caller.siteSettings({}); expect(result.siteName).toBe("My App"); }); }); + +describe("marketingPagesRouter (R26 error mapping)", () => { + beforeEach(() => { + marketingPagesContainer.unbindAll(); + marketingPagesContainer.load(MarketingPagesModule); + }); + + afterEach(() => { + marketingPagesContainer.unbindAll(); + }); + + it("translates zod parse failure → BAD_REQUEST on pageBySlug", async () => { + const caller = marketingPagesRouter.createCaller({}); + try { + await caller.pageBySlug({} as unknown as { slug: string }); + throw new Error("expected throw"); + } catch (e) { + expect(e).toBeInstanceOf(TRPCError); + expect((e as TRPCError).code).toBe("BAD_REQUEST"); + } + }); + + it("returns undefined (not NOT_FOUND) for missing slug since use case returns undefined", async () => { + const caller = marketingPagesRouter.createCaller({}); + const result = await caller.pageBySlug({ slug: "does-not-exist" }); + expect(result).toBeUndefined(); + }); +}); diff --git a/packages/marketing-pages/src/integrations/api/router.ts b/packages/marketing-pages/src/integrations/api/router.ts index 98848ed..024ec54 100644 --- a/packages/marketing-pages/src/integrations/api/router.ts +++ b/packages/marketing-pages/src/integrations/api/router.ts @@ -1,13 +1,19 @@ -import { z } from "zod"; -import { router, publicProcedure } from "@repo/core-shared/trpc/init"; +import { router } from "@repo/core-shared/trpc/init"; + import { marketingPagesContainer } from "../../di/container"; import { MARKETING_PAGES_SYMBOLS } from "../../di/symbols"; + +import { getPageBySlugInputSchema } from "../../application/use-cases/get-page-by-slug.use-case"; +import { getSiteSettingsInputSchema } from "../../application/use-cases/get-site-settings.use-case"; + import type { IGetPageBySlugController } from "../../interface-adapters/controllers/get-page-by-slug.controller"; import type { IGetSiteSettingsController } from "../../interface-adapters/controllers/get-site-settings.controller"; +import { marketingPagesProcedure } from "./procedures"; + export const marketingPagesRouter = router({ - pageBySlug: publicProcedure - .input(z.object({ slug: z.string().min(1) })) + pageBySlug: marketingPagesProcedure + .input(getPageBySlugInputSchema) .query(({ input }) => { const ctrl = marketingPagesContainer.get( MARKETING_PAGES_SYMBOLS.IGetPageBySlugController, @@ -15,12 +21,14 @@ export const marketingPagesRouter = router({ return ctrl(input); }), - siteSettings: publicProcedure.query(() => { - const ctrl = marketingPagesContainer.get( - MARKETING_PAGES_SYMBOLS.IGetSiteSettingsController, - ); - return ctrl(); - }), + siteSettings: marketingPagesProcedure + .input(getSiteSettingsInputSchema) + .query(({ input }) => { + const ctrl = marketingPagesContainer.get( + MARKETING_PAGES_SYMBOLS.IGetSiteSettingsController, + ); + return ctrl(input); + }), }); export type MarketingPagesRouter = typeof marketingPagesRouter; diff --git a/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.test.ts b/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.test.ts index c582eea..2c488e1 100644 --- a/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.test.ts +++ b/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.test.ts @@ -20,7 +20,7 @@ describe("getPageBySlugController", () => { const controller = getPageBySlugController(useCase); await expect( - controller({} as { slug: string }), + controller({}), ).rejects.toBeInstanceOf(InputParseError); }); diff --git a/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.ts b/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.ts index 6e057c0..6309de6 100644 --- a/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.ts +++ b/packages/marketing-pages/src/interface-adapters/controllers/get-page-by-slug.controller.ts @@ -1,23 +1,24 @@ -import { z } from "zod"; - import { InputParseError } from "../../entities/errors/common"; -import type { Page } from "../../entities/models/page"; -import type { IGetPageBySlugUseCase } from "../../application/use-cases/get-page-by-slug.use-case"; +import { + getPageBySlugInputSchema, + type GetPageBySlugOutput, + type IGetPageBySlugUseCase, +} from "../../application/use-cases/get-page-by-slug.use-case"; -const inputSchema = z.object({ - slug: z.string().min(1), -}); +function presenter(value: GetPageBySlugOutput) { + return value; +} export type IGetPageBySlugController = ReturnType; export const getPageBySlugController = (getPageBySlugUseCase: IGetPageBySlugUseCase) => - async (input: Partial>): Promise => { - const parsed = inputSchema.safeParse(input); + async (input: unknown): Promise | undefined> => { + const parsed = getPageBySlugInputSchema.safeParse(input); if (!parsed.success) { - throw new InputParseError("Invalid get-page-by-slug input", { - cause: parsed.error, - }); + throw new InputParseError("Invalid get-page-by-slug input", { cause: parsed.error }); } - return getPageBySlugUseCase(parsed.data); + const result = await getPageBySlugUseCase(parsed.data); + if (result === undefined) return undefined; + return presenter(result); }; diff --git a/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.test.ts b/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.test.ts index 038095e..9958745 100644 --- a/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.test.ts +++ b/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.test.ts @@ -9,7 +9,7 @@ describe("getSiteSettingsController", () => { const useCase = getSiteSettingsUseCase(repo); const controller = getSiteSettingsController(useCase); - const result = await controller(); + const result = await controller({}); expect(result.siteName).toBe("My App"); }); }); diff --git a/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.ts b/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.ts index f6166d2..d7a9857 100644 --- a/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.ts +++ b/packages/marketing-pages/src/interface-adapters/controllers/get-site-settings.controller.ts @@ -1,10 +1,23 @@ -import type { SiteSettings } from "../../entities/models/site-settings"; -import type { IGetSiteSettingsUseCase } from "../../application/use-cases/get-site-settings.use-case"; +import { InputParseError } from "../../entities/errors/common"; +import { + getSiteSettingsInputSchema, + type GetSiteSettingsOutput, + type IGetSiteSettingsUseCase, +} from "../../application/use-cases/get-site-settings.use-case"; + +function presenter(value: GetSiteSettingsOutput) { + return value; +} export type IGetSiteSettingsController = ReturnType; export const getSiteSettingsController = (getSiteSettingsUseCase: IGetSiteSettingsUseCase) => - async (): Promise => { - return getSiteSettingsUseCase(); + async (input: unknown): Promise> => { + const parsed = getSiteSettingsInputSchema.safeParse(input); + if (!parsed.success) { + throw new InputParseError("Invalid get-site-settings input", { cause: parsed.error }); + } + const result = await getSiteSettingsUseCase(parsed.data); + return presenter(result); }; diff --git a/packages/marketing-pages/src/ui/index.ts b/packages/marketing-pages/src/ui/index.ts new file mode 100644 index 0000000..0a540ae --- /dev/null +++ b/packages/marketing-pages/src/ui/index.ts @@ -0,0 +1 @@ +export { pageBySlugQuery, siteSettingsQuery } from "./query";