import { withSpan, withCapture, INSTRUMENTATION_SYMBOLS, type ITracer, type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindContext } from "@repo/core-shared/di"; import { assertFeatureConformance, wireUseCase, } from "@repo/core-shared/conformance"; import { marketingPagesManifest } from "../feature.manifest"; import { marketingPagesContainer } from "./container"; import { MARKETING_PAGES_SYMBOLS } from "./symbols"; import { MockPagesRepository } from "../infrastructure/repositories/pages.repository.mock"; import { buildDevPages, buildDevSiteSettings } from "../__seeds__/dev"; import { getSiteSettingsUseCase } from "../application/use-cases/get-site-settings.use-case"; import { getPageBySlugUseCase } from "../application/use-cases/get-page-by-slug.use-case"; import { getSiteSettingsController } from "../interface-adapters/controllers/get-site-settings.controller"; import { getPageBySlugController } from "../interface-adapters/controllers/get-page-by-slug.controller"; import type { IPagesRepository } from "../application/repositories/pages.repository.interface"; import type { ISiteSettingsRepository } from "../application/repositories/site-settings.repository.interface"; import { userSignedUpEvent } from "@repo/auth"; import { onAuthUserSignedUpHandler } from "../events/handlers/on-auth-user-signed-up.handler"; import { sendWelcomeEmailJob, type ISendWelcomeEmailJob, type SendWelcomeEmailInput, } from "../jobs/send-welcome-email.job"; import { RecordingMailerService } from "../infrastructure/services/recording-mailer.service"; import type { IMailerService } from "../application/services/mailer.service.interface"; /** * Replace the default empty mocks with populated ones for dev mode + storybook. * * Call this from app boot when `USE_DEV_SEED=true`, mutually exclusive with * `bindProductionMarketingPages(config)`. Tests must NOT call this — they * construct mocks directly and seed via factories per-test. * * Idempotent: safe to call multiple times; each call rebuilds fresh populated * repos and rebinds both symbols. */ export async function bindDevSeedMarketingPages( ctx: BindContext, ): Promise { const { tracer, logger, bus, queue, realtime, realtimeRegistry } = ctx; // Bind shared instrumentation into feature container if (marketingPagesContainer.isBound(INSTRUMENTATION_SYMBOLS.TRACER)) { marketingPagesContainer.unbind(INSTRUMENTATION_SYMBOLS.TRACER); } if (marketingPagesContainer.isBound(INSTRUMENTATION_SYMBOLS.LOGGER)) { marketingPagesContainer.unbind(INSTRUMENTATION_SYMBOLS.LOGGER); } marketingPagesContainer .bind(INSTRUMENTATION_SYMBOLS.TRACER) .toConstantValue(tracer); marketingPagesContainer .bind(INSTRUMENTATION_SYMBOLS.LOGGER) .toConstantValue(logger); // Pages repository — MockPagesRepository accepts an initial array. if ( marketingPagesContainer.isBound(MARKETING_PAGES_SYMBOLS.IPagesRepository) ) { marketingPagesContainer.unbind(MARKETING_PAGES_SYMBOLS.IPagesRepository); } const pagesRepo = new MockPagesRepository(buildDevPages(), tracer, logger); marketingPagesContainer .bind(MARKETING_PAGES_SYMBOLS.IPagesRepository) .toConstantValue(pagesRepo); // Site settings repository if ( marketingPagesContainer.isBound( MARKETING_PAGES_SYMBOLS.ISiteSettingsRepository, ) ) { marketingPagesContainer.unbind( MARKETING_PAGES_SYMBOLS.ISiteSettingsRepository, ); } const settings = buildDevSiteSettings(); // Inline constant so dev seed returns exactly the seeded value (not the mock's default) const siteSettingsRepo: ISiteSettingsRepository = { getSiteSettings: async () => settings, }; marketingPagesContainer .bind( MARKETING_PAGES_SYMBOLS.ISiteSettingsRepository, ) .toConstantValue(siteSettingsRepo); // Use cases const wrappedGetSiteSettings = wireUseCase({ container: marketingPagesContainer, symbol: MARKETING_PAGES_SYMBOLS.IGetSiteSettingsUseCase, factory: getSiteSettingsUseCase, deps: [siteSettingsRepo], feature: "marketing-pages", layer: "use-case", name: "getSiteSettings", tracer, logger, }); const wrappedGetPageBySlug = wireUseCase({ container: marketingPagesContainer, symbol: MARKETING_PAGES_SYMBOLS.IGetPageBySlugUseCase, factory: getPageBySlugUseCase, deps: [pagesRepo], feature: "marketing-pages", layer: "use-case", name: "getPageBySlug", tracer, logger, }); for (const sym of [ MARKETING_PAGES_SYMBOLS.IGetSiteSettingsController, MARKETING_PAGES_SYMBOLS.IGetPageBySlugController, ]) { if (marketingPagesContainer.isBound(sym)) marketingPagesContainer.unbind(sym); } marketingPagesContainer .bind(MARKETING_PAGES_SYMBOLS.IGetSiteSettingsController) .toConstantValue( withSpan( tracer, { name: "marketing-pages.getSiteSettings", op: "controller" }, withCapture( logger, { feature: "marketing-pages", layer: "controller", name: "marketing-pages.getSiteSettings", }, getSiteSettingsController(wrappedGetSiteSettings), ), ), ); marketingPagesContainer .bind(MARKETING_PAGES_SYMBOLS.IGetPageBySlugController) .toConstantValue( withSpan( tracer, { name: "marketing-pages.getPageBySlug", op: "controller" }, withCapture( logger, { feature: "marketing-pages", layer: "controller", name: "marketing-pages.getPageBySlug", }, getPageBySlugController(wrappedGetPageBySlug), ), ), ); // Bind the dev-seed mailer (recording so e2e tests can inspect calls). if (marketingPagesContainer.isBound(MARKETING_PAGES_SYMBOLS.IMailerService)) { marketingPagesContainer.unbind(MARKETING_PAGES_SYMBOLS.IMailerService); } const mailer = new RecordingMailerService(); marketingPagesContainer .bind(MARKETING_PAGES_SYMBOLS.IMailerService) .toConstantValue(mailer); // // onAuthUserSignedUpHandler subscription — generated, edit the handler file (not this block) for behavior. // bus is optional: when @repo/core-events is not scaffolded, ctx.bus is undefined and the handler is not bound. // queue (IJobQueue) is in core-shared and always provided; the inner if (queue) below is belt-and-suspenders. if (queue) { const wrappedAuthUserSignedUp = withSpan( tracer, { name: "marketing-pages.onAuthUserSignedUpHandler", op: "event-handler", }, withCapture( logger, { feature: "marketing-pages", layer: "event-handler", name: "marketing-pages.onAuthUserSignedUpHandler", }, onAuthUserSignedUpHandler(queue), ), ); if ( marketingPagesContainer.isBound( MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler, ) ) { marketingPagesContainer.unbind( MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler, ); } marketingPagesContainer .bind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler) .toConstantValue(wrappedAuthUserSignedUp); bus?.subscribe( userSignedUpEvent, "marketing-pages", wrappedAuthUserSignedUp, ); } // const wrappedSendWelcomeEmail = withSpan( tracer, { name: "marketing-pages.sendWelcomeEmail", op: "job" }, withCapture( logger, { feature: "marketing-pages", layer: "job", name: "marketing-pages.sendWelcomeEmail", }, sendWelcomeEmailJob(mailer), ), ); if ( marketingPagesContainer.isBound( MARKETING_PAGES_SYMBOLS.ISendWelcomeEmailJob, ) ) { marketingPagesContainer.unbind( MARKETING_PAGES_SYMBOLS.ISendWelcomeEmailJob, ); } marketingPagesContainer .bind(MARKETING_PAGES_SYMBOLS.ISendWelcomeEmailJob) .toConstantValue(wrappedSendWelcomeEmail); // Register the dev-seed in-memory queue handler so enqueue() actually fires // the wrapped job. Production binding skips this — the Payload task does it. if ( queue && "register" in queue && typeof (queue as { register?: unknown }).register === "function" ) { ( queue as { register: (slug: string, h: (input: unknown) => Promise) => void; } ).register("marketing-pages.send-welcome-email", async (input) => { const wrapped = marketingPagesContainer.get( MARKETING_PAGES_SYMBOLS.ISendWelcomeEmailJob, ); await wrapped(input as SendWelcomeEmailInput); }); } void realtime; void realtimeRegistry; // // Boot-time conformance check (dev-seed mode). assertFeatureConformance( marketingPagesContainer, marketingPagesManifest, { getPageBySlug: MARKETING_PAGES_SYMBOLS.IGetPageBySlugUseCase, getSiteSettings: MARKETING_PAGES_SYMBOLS.IGetSiteSettingsUseCase, }, ctx, ); }