feat(ADM-018): completed feature
This commit is contained in:
@@ -6,12 +6,33 @@ import { randomUUID } from 'node:crypto';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import Fastify, { type FastifyInstance } from 'fastify';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import fastifyCors from '@fastify/cors';
|
||||
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { IncomingMessage } from 'node:http';
|
||||
import type pg from 'pg';
|
||||
import { registerHealthRoutes } from '../modules/health/index.js';
|
||||
import { registerIdentityRoutes, createSessionAuthenticator } from '../modules/identity/index.js';
|
||||
import { registerUsersRoutes } from '../modules/users/index.js';
|
||||
import { registerCategoriesRoutes } from '../modules/categories/index.js';
|
||||
import { registerCatalogRoutes } from '../modules/catalog/index.js';
|
||||
import { registerBrandsRoutes } from '../modules/brands/index.js';
|
||||
import { createInventoryService, registerInventoryRoutes } from '../modules/inventory/index.js';
|
||||
import { createPricingService, registerPricingRoutes } from '../modules/pricing/index.js';
|
||||
import { createPromotionService, registerPromotionsRoutes } from '../modules/promotions/index.js';
|
||||
import { registerCartRoutes } from '../modules/cart/index.js';
|
||||
import { registerShippingRoutes } from '../modules/shipping/index.js';
|
||||
import { registerOrdersRoutes } from '../modules/orders/index.js';
|
||||
import { registerCheckoutRoutes } from '../modules/checkout/index.js';
|
||||
import { registerPaymentsRoutes } from '../modules/payments/index.js';
|
||||
import { registerNotificationsRoutes } from '../modules/notifications/index.js';
|
||||
import { registerReviewsRoutes } from '../modules/reviews/index.js';
|
||||
import { registerCmsRoutes } from '../modules/cms/index.js';
|
||||
import { registerStoreSettingsRoutes } from '../modules/store-settings/index.js';
|
||||
import { CacheService, InMemoryCacheAdapter, registerCacheRoutes } from '../modules/cache/index.js';
|
||||
import { AuditLogger, RateLimiter, registerSecurityRoutes } from '../modules/security/index.js';
|
||||
import { registerAdminStatsRoutes } from '../modules/admin-stats/api/stats.routes.js';
|
||||
import { createInMemoryTelemetry, registerMetricsRoutes } from '../modules/observability/index.js';
|
||||
import { LoggingEmailProvider } from '../modules/notifications/index.js';
|
||||
import { createFlagStore, type FeatureFlagProvider } from '../modules/flags/index.js';
|
||||
import { AppError, errorEnvelope } from '../shared/errors.js';
|
||||
import { createLogger, type Logger } from '../infrastructure/logging/logger.js';
|
||||
@@ -56,6 +77,11 @@ export async function buildApp(deps: BuildAppDeps = {}): Promise<FastifyInstance
|
||||
const app = Fastify({ logger: false, genReqId: generateRequestId });
|
||||
app.decorate('flags', flags);
|
||||
|
||||
await app.register(fastifyCors, {
|
||||
origin: true,
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
app.addHook('onRequest', async (request, reply) => {
|
||||
startTimes.set(request, performance.now());
|
||||
void reply.header(REQUEST_ID_HEADER, request.id);
|
||||
@@ -119,22 +145,196 @@ export async function buildApp(deps: BuildAppDeps = {}): Promise<FastifyInstance
|
||||
await app.register(fastifyCookie);
|
||||
|
||||
if (deps.pool) {
|
||||
// Session resolution; created before identity registration so it can be passed
|
||||
// to identity's /auth/me route.
|
||||
const authenticate = createSessionAuthenticator(deps.pool);
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerIdentityRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
cookieSecure: deps.cookieSecure,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
// Session resolution is identity's; users receives it by injection so no
|
||||
// module ever imports another module.
|
||||
const authenticate = createSessionAuthenticator(deps.pool);
|
||||
await app.register(async (instance) => {
|
||||
await registerUsersRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerCategoriesRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerBrandsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerCatalogRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
logger,
|
||||
});
|
||||
});
|
||||
|
||||
const inventory = createInventoryService(deps.pool);
|
||||
await app.register(async (instance) => {
|
||||
await registerInventoryRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
const pricing = createPricingService(deps.pool);
|
||||
await app.register(async (instance) => {
|
||||
await registerPricingRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
const promotions = createPromotionService(deps.pool);
|
||||
await app.register(async (instance) => {
|
||||
await registerPromotionsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerCartRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
pricing,
|
||||
inventory,
|
||||
promotions,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerShippingRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerOrdersRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
const { telemetry, meter } = createInMemoryTelemetry();
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerMetricsRoutes(instance, { meter, authenticate });
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerCheckoutRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
tracer: telemetry.tracer,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerPaymentsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET ?? 'whsec_test',
|
||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerNotificationsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
emailProvider: new LoggingEmailProvider(),
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerReviewsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerCmsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(async (instance) => {
|
||||
await registerStoreSettingsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
await registerAdminStatsRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
});
|
||||
});
|
||||
|
||||
const cache = new CacheService(new InMemoryCacheAdapter());
|
||||
cache.registerContract({
|
||||
name: 'product',
|
||||
keyPattern: 'product:{slug}',
|
||||
ttlSeconds: 300,
|
||||
sourceOfTruth: 'catalog_products',
|
||||
invalidation: 'ProductUpdated',
|
||||
});
|
||||
cache.registerContract({
|
||||
name: 'category',
|
||||
keyPattern: 'category:{slug}',
|
||||
ttlSeconds: 300,
|
||||
sourceOfTruth: 'categories_categories',
|
||||
invalidation: 'CategoryUpdated',
|
||||
});
|
||||
cache.registerContract({
|
||||
name: 'nav',
|
||||
keyPattern: 'nav:tree',
|
||||
ttlSeconds: 600,
|
||||
sourceOfTruth: 'categories_categories',
|
||||
invalidation: 'NavigationInvalidated',
|
||||
});
|
||||
cache.registerContract({
|
||||
name: 'search_popular',
|
||||
keyPattern: 'search:popular',
|
||||
ttlSeconds: 60,
|
||||
sourceOfTruth: 'catalog_search',
|
||||
invalidation: 'SearchInvalidated',
|
||||
});
|
||||
await app.register(async (instance) => {
|
||||
await registerCacheRoutes(instance, { cache, authenticate });
|
||||
});
|
||||
|
||||
const rateLimiter = new RateLimiter();
|
||||
const auditLogger = new AuditLogger(deps.pool);
|
||||
await app.register(async (instance) => {
|
||||
await registerSecurityRoutes(instance, {
|
||||
pool: deps.pool as pg.Pool,
|
||||
authenticate,
|
||||
rateLimiter,
|
||||
auditLogger,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return app;
|
||||
|
||||
Reference in New Issue
Block a user