feat: reconcile application vertical slices

Backfill the completed acquisitions, sales, dashboard, pricing, search, public detail, worker, migration, contracts, validation, tests, and implementation-status changes into one verified application slice.
This commit is contained in:
2026-09-09 22:12:53 -04:00
parent b9ff7445a6
commit b8f089a5d9
78 changed files with 4561 additions and 2057 deletions
+44 -43
View File
@@ -1,32 +1,33 @@
import Fastify from 'fastify';
import cookie from '@fastify/cookie';
import multipart from '@fastify/multipart';
import Fastify from "fastify";
import cookie from "@fastify/cookie";
import multipart from "@fastify/multipart";
import { registerAuthRoutes } from '../modules/auth/routes/auth-routes.js';
import { registerAdminInventoryRoutes } from '../modules/inventory/routes/admin-inventory-routes.js';
import { registerAdminLookupRoutes } from '../modules/inventory/routes/admin-lookup-routes.js';
import { registerAdminImageRoutes } from '../modules/inventory/routes/admin-image-routes.js';
import { registerPublicInventoryRoutes } from '../modules/inventory/routes/public-inventory-routes.js';
import { registerPublicImageRoutes } from '../modules/inventory/routes/public-image-routes.js';
import { registerAdminListingRoutes } from '../modules/listings/routes/admin-listing-routes.js';
import { registerPublicListingRoutes } from '../modules/listings/routes/public-listing-routes.js';
import { registerAdminProductRoutes } from '../modules/products/routes/admin-product-routes.js';
import { registerPublicProductRoutes } from '../modules/products/routes/public-product-routes.js';
import { registerPublicSearchRoutes } from '../modules/search/routes/public-search-routes.js';
import { registerPublicSellerRoutes } from '../modules/sellers/routes/public-seller-routes.js';
import { registerPublicLookupRoutes } from '../modules/inventory/routes/public-lookup-routes.js';
import { registerAdminSaleRoutes } from '../modules/sales/routes/admin-sale-routes.js';
import { registerAdminAcquisitionRoutes } from '../modules/acquisitions/routes/admin-acquisition-routes.js';
import { registerAdminDashboardRoutes } from '../modules/dashboard/routes/admin-dashboard-routes.js';
import { registerAdminPricingRoutes } from '../modules/pricing/routes/admin-pricing-routes.js';
import { registerAdminProviderMappingRoutes } from '../modules/pricing/routes/admin-provider-mapping-routes.js';
import { registerAuthRoutes } from "../modules/auth/routes/auth-routes.js";
import { registerAdminInventoryRoutes } from "../modules/inventory/routes/admin-inventory-routes.js";
import { registerAdminLookupRoutes } from "../modules/inventory/routes/admin-lookup-routes.js";
import { registerAdminImageRoutes } from "../modules/inventory/routes/admin-image-routes.js";
import { registerPublicInventoryRoutes } from "../modules/inventory/routes/public-inventory-routes.js";
import { registerPublicImageRoutes } from "../modules/inventory/routes/public-image-routes.js";
import { registerAdminListingRoutes } from "../modules/listings/routes/admin-listing-routes.js";
import { registerPublicListingRoutes } from "../modules/listings/routes/public-listing-routes.js";
import { registerAdminProductRoutes } from "../modules/products/routes/admin-product-routes.js";
import { registerPublicProductRoutes } from "../modules/products/routes/public-product-routes.js";
import { registerPublicSearchRoutes } from "../modules/search/routes/public-search-routes.js";
import { registerPublicSellerRoutes } from "../modules/sellers/routes/public-seller-routes.js";
import { registerPublicLookupRoutes } from "../modules/inventory/routes/public-lookup-routes.js";
import { registerAdminSaleRoutes } from "../modules/sales/routes/admin-sale-routes.js";
import { registerAdminAcquisitionRoutes } from "../modules/acquisitions/routes/admin-acquisition-routes.js";
import { registerAdminDashboardRoutes } from "../modules/dashboard/routes/admin-dashboard-routes.js";
import { registerAdminPricingRoutes } from "../modules/pricing/routes/admin-pricing-routes.js";
import { registerAdminProviderMappingRoutes } from "../modules/pricing/routes/admin-provider-mapping-routes.js";
export function buildApp(dependencies) {
const app = Fastify();
app.setErrorHandler((error, request, reply) => {
const statusCode = error.statusCode ?? 500;
const message = statusCode >= 500 ? 'Internal server error.' : error.message;
const message =
statusCode >= 500 ? "Internal server error." : error.message;
if (statusCode >= 500) {
request.log.error(error);
@@ -34,87 +35,87 @@ export function buildApp(dependencies) {
return reply.code(statusCode).send({
error: {
code: error.code ?? 'INTERNAL_SERVER_ERROR',
code: error.code ?? "INTERNAL_SERVER_ERROR",
message,
},
});
});
app.get('/health', async () => ({ data: { status: 'ok' } }));
app.get("/health", async () => ({ data: { status: "ok" } }));
app.register(cookie);
app.register(multipart, { limits: { fileSize: 10 * 1024 * 1024, files: 1 } });
app.register(registerAuthRoutes, {
...dependencies,
prefix: '/api/v1/auth',
prefix: "/api/v1/auth",
});
app.register(registerAdminProductRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminInventoryRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminLookupRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminImageRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminListingRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminSaleRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminAcquisitionRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminDashboardRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminPricingRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerAdminProviderMappingRoutes, {
...dependencies,
prefix: '/api/v1/admin',
prefix: "/api/v1/admin",
});
app.register(registerPublicSearchRoutes, {
...dependencies,
prefix: '/api/v1',
prefix: "/api/v1",
});
app.register(registerPublicProductRoutes, {
...dependencies,
prefix: '/api/v1',
prefix: "/api/v1",
});
app.register(registerPublicListingRoutes, {
...dependencies,
prefix: '/api/v1',
prefix: "/api/v1",
});
app.register(registerPublicInventoryRoutes, {
...dependencies,
prefix: '/api/v1',
prefix: "/api/v1",
});
app.register(registerPublicImageRoutes, {
...dependencies,
prefix: '/api/v1',
prefix: "/api/v1",
});
app.register(registerPublicSellerRoutes, {
...dependencies,
prefix: '/api/v1',
prefix: "/api/v1",
});
app.register(registerPublicLookupRoutes, {
...dependencies,
prefix: '/api/v1',
prefix: "/api/v1",
});
return app;
}
}
+120 -72
View File
@@ -1,85 +1,106 @@
import dotenv from 'dotenv';
import { fileURLToPath } from 'node:url';
import dotenv from "dotenv";
import { fileURLToPath } from "node:url";
dotenv.config({ path: fileURLToPath(new URL('../../../../.env', import.meta.url)) });
dotenv.config({
path: fileURLToPath(new URL("../../../../.env", import.meta.url)),
});
import { buildApp } from './build-app.js';
import { createDatabase } from '../infrastructure/database/client.js';
import { resolveProjectImageStorageRoot } from '../infrastructure/images/local-image-storage.js';
import { createPasswordResetNotifier } from '../infrastructure/email/password-reset-notifier.js';
import { createRedisConnection } from '../infrastructure/redis/client.js';
import { createAuthenticationService } from '../modules/auth/services/authentication-service.js';
import { createMfaService } from '../modules/auth/services/mfa-service.js';
import { createSecretProtector } from '../modules/auth/domain/secret-protection.js';
import { createMfaMethodRepository } from '../modules/auth/repositories/mfa-method-repository.js';
import { createSecurityAuditRepository } from '../modules/auth/repositories/security-audit-repository.js';
import { createSessionRepository } from '../modules/auth/repositories/session-repository.js';
import { createUserRepository } from '../modules/auth/repositories/user-repository.js';
import { createPasswordResetRepository } from '../modules/auth/repositories/password-reset-repository.js';
import { createRedisLoginRateLimiter } from '../modules/auth/services/login-rate-limiter.js';
import { createPasswordRecoveryService } from '../modules/auth/services/password-recovery-service.js';
import { createInventoryItemRepository } from '../modules/inventory/repositories/inventory-item-repository.js';
import { createCatalogLookupRepository } from '../modules/inventory/repositories/catalog-lookup-repository.js';
import { createInventoryItemService } from '../modules/inventory/services/inventory-item-service.js';
import { createPublicInventoryService } from '../modules/inventory/services/public-inventory-service.js';
import { createPublicImageService } from '../modules/inventory/services/public-image-service.js';
import { createInventoryImageRepository } from '../modules/inventory/repositories/inventory-image-repository.js';
import { createImageDerivativeJobRepository } from '../modules/inventory/repositories/image-derivative-job-repository.js';
import { createInventoryImageService } from '../modules/inventory/services/inventory-image-service.js';
import { createLocalImageStorage } from '../infrastructure/images/local-image-storage.js';
import { createConditionRepository } from '../modules/inventory/repositories/condition-repository.js';
import { createListingRepository } from '../modules/listings/repositories/listing-repository.js';
import { createListingService } from '../modules/listings/services/listing-service.js';
import { createPublicListingService } from '../modules/listings/services/public-listing-service.js';
import { createProductRepository } from '../modules/products/repositories/product-repository.js';
import { createProductService } from '../modules/products/services/product-service.js';
import { createPublicProductService } from '../modules/products/services/public-product-service.js';
import { createSearchService } from '../modules/search/services/search-service.js';
import { createSellerRepository } from '../modules/sellers/repositories/seller-repository.js';
import { createPublicSellerService } from '../modules/sellers/services/public-seller-service.js';
import { createSaleRepository } from '../modules/sales/repositories/sale-repository.js';
import { createSaleService } from '../modules/sales/services/sale-service.js';
import { createAcquisitionRepository } from '../modules/acquisitions/repositories/acquisition-repository.js';
import { createAcquisitionService } from '../modules/acquisitions/services/acquisition-service.js';
import { createDashboardService } from '../modules/dashboard/services/dashboard-service.js';
import { createPricingRepository } from '../modules/pricing/repositories/pricing-repository.js';
import { createPricingService } from '../modules/pricing/services/pricing-service.js';
import { createPricingRefreshJobRepository } from '../modules/pricing/repositories/pricing-refresh-job-repository.js';
import { createProviderMappingRepository } from '../modules/pricing/repositories/provider-mapping-repository.js';
import { createProviderMappingService } from '../modules/pricing/services/provider-mapping-service.js';
import { createPricingProviderRegistry } from '../modules/pricing/providers/pricing-provider.js';
import { createFixturePricingProvider } from '../modules/pricing/providers/fixture-pricing-provider.js';
import { buildApp } from "./build-app.js";
import { createDatabase } from "../infrastructure/database/client.js";
import { resolveProjectImageStorageRoot } from "../infrastructure/images/local-image-storage.js";
import { createPasswordResetNotifier } from "../infrastructure/email/password-reset-notifier.js";
import { createRedisConnection } from "../infrastructure/redis/client.js";
import { createAuthenticationService } from "../modules/auth/services/authentication-service.js";
import { createMfaService } from "../modules/auth/services/mfa-service.js";
import { createSecretProtector } from "../modules/auth/domain/secret-protection.js";
import { createMfaMethodRepository } from "../modules/auth/repositories/mfa-method-repository.js";
import { createSecurityAuditRepository } from "../modules/auth/repositories/security-audit-repository.js";
import { createSessionRepository } from "../modules/auth/repositories/session-repository.js";
import { createUserRepository } from "../modules/auth/repositories/user-repository.js";
import { createPasswordResetRepository } from "../modules/auth/repositories/password-reset-repository.js";
import { createRedisLoginRateLimiter } from "../modules/auth/services/login-rate-limiter.js";
import { createPasswordRecoveryService } from "../modules/auth/services/password-recovery-service.js";
import { createInventoryItemRepository } from "../modules/inventory/repositories/inventory-item-repository.js";
import { createCatalogLookupRepository } from "../modules/inventory/repositories/catalog-lookup-repository.js";
import { createInventoryItemService } from "../modules/inventory/services/inventory-item-service.js";
import { createPublicInventoryService } from "../modules/inventory/services/public-inventory-service.js";
import { createPublicImageService } from "../modules/inventory/services/public-image-service.js";
import { createInventoryImageRepository } from "../modules/inventory/repositories/inventory-image-repository.js";
import { createImageDerivativeJobRepository } from "../modules/inventory/repositories/image-derivative-job-repository.js";
import { createInventoryImageService } from "../modules/inventory/services/inventory-image-service.js";
import { createLocalImageStorage } from "../infrastructure/images/local-image-storage.js";
import { createConditionRepository } from "../modules/inventory/repositories/condition-repository.js";
import { createListingRepository } from "../modules/listings/repositories/listing-repository.js";
import { createListingService } from "../modules/listings/services/listing-service.js";
import { createPublicListingService } from "../modules/listings/services/public-listing-service.js";
import { createProductRepository } from "../modules/products/repositories/product-repository.js";
import { createProductService } from "../modules/products/services/product-service.js";
import { createPublicProductService } from "../modules/products/services/public-product-service.js";
import { createSearchService } from "../modules/search/services/search-service.js";
import { createSellerRepository } from "../modules/sellers/repositories/seller-repository.js";
import { createPublicSellerService } from "../modules/sellers/services/public-seller-service.js";
import { createSaleRepository } from "../modules/sales/repositories/sale-repository.js";
import { createSaleService } from "../modules/sales/services/sale-service.js";
import { createAcquisitionRepository } from "../modules/acquisitions/repositories/acquisition-repository.js";
import { createAcquisitionService } from "../modules/acquisitions/services/acquisition-service.js";
import { createDashboardService } from "../modules/dashboard/services/dashboard-service.js";
import { createPricingRepository } from "../modules/pricing/repositories/pricing-repository.js";
import { createPricingService } from "../modules/pricing/services/pricing-service.js";
import { createPricingRefreshJobRepository } from "../modules/pricing/repositories/pricing-refresh-job-repository.js";
import { createProviderMappingRepository } from "../modules/pricing/repositories/provider-mapping-repository.js";
import { createProviderMappingService } from "../modules/pricing/services/provider-mapping-service.js";
import { createPricingProviderRegistry } from "../modules/pricing/providers/pricing-provider.js";
import { createFixturePricingProvider } from "../modules/pricing/providers/fixture-pricing-provider.js";
import { createHttpPricingProvider } from "../modules/pricing/providers/http-pricing-provider.js";
function parsePort(value) {
const port = Number.parseInt(value ?? '3000', 10);
const port = Number.parseInt(value ?? "3000", 10);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('API_PORT must be an integer between 1 and 65535.');
throw new Error("API_PORT must be an integer between 1 and 65535.");
}
return port;
}
async function startServer() {
const imageStorageRoot = resolveProjectImageStorageRoot(process.env.IMAGE_STORAGE_ROOT ?? '.local/image-storage', import.meta.url);
const imageStorageRoot = resolveProjectImageStorageRoot(
process.env.IMAGE_STORAGE_ROOT ?? ".local/image-storage",
import.meta.url,
);
const database = createDatabase();
const redis = await createRedisConnection();
const productRepository = createProductRepository(database.db);
const inventoryItemRepository = createInventoryItemRepository(database.db);
const catalogLookupRepository = createCatalogLookupRepository(database.db);
const inventoryImageRepository = createInventoryImageRepository(database.db);
const imageDerivativeJobRepository = createImageDerivativeJobRepository(database.db);
const imageDerivativeJobRepository = createImageDerivativeJobRepository(
database.db,
);
const listingRepository = createListingRepository(database.db);
const sellerRepository = createSellerRepository(database.db);
const conditionRepository = createConditionRepository(database.db);
const saleRepository = createSaleRepository(database.db);
const acquisitionRepository = createAcquisitionRepository(database.db);
const pricingRepository = createPricingRepository(database.db);
const pricingRefreshJobRepository = createPricingRefreshJobRepository(database.db);
const providerMappingRepository = createProviderMappingRepository(database.db);
const pricingProviders = process.env.PRICING_FIXTURE_PROVIDER_ENABLED === 'true'
? createPricingProviderRegistry([createFixturePricingProvider()])
: createPricingProviderRegistry();
const pricingRefreshJobRepository = createPricingRefreshJobRepository(
database.db,
);
const providerMappingRepository = createProviderMappingRepository(
database.db,
);
const providers = [];
if (process.env.PRICING_FIXTURE_PROVIDER_ENABLED === "true")
providers.push(createFixturePricingProvider());
if (process.env.PRICING_HTTP_PROVIDER_ENDPOINT)
providers.push(
createHttpPricingProvider({
id: process.env.PRICING_HTTP_PROVIDER_ID ?? "http-provider",
endpoint: process.env.PRICING_HTTP_PROVIDER_ENDPOINT,
token: process.env.PRICING_HTTP_PROVIDER_TOKEN,
}),
);
const pricingProviders = createPricingProviderRegistry(providers);
const userRepository = createUserRepository(database.db);
const sessionRepository = createSessionRepository(database.db);
const passwordResetRepository = createPasswordResetRepository(database.db);
@@ -105,11 +126,15 @@ async function startServer() {
const app = buildApp({
authenticationService,
authenticateAdmin: (request) =>
authenticationService.authenticate(request.cookies.pokemon_inventory_session),
authenticationService.authenticate(
request.cookies.pokemon_inventory_session,
),
authorizeUnsafeRequest: (sessionToken, csrfToken) =>
authenticationService.authorizeUnsafeRequest(sessionToken, csrfToken),
isProduction: process.env.NODE_ENV === 'production',
loginRateLimiter: createRedisLoginRateLimiter({ redisClient: redis.client }),
isProduction: process.env.NODE_ENV === "production",
loginRateLimiter: createRedisLoginRateLimiter({
redisClient: redis.client,
}),
mfaService,
passwordRecoveryService: createPasswordRecoveryService({
passwordResetNotifier,
@@ -118,7 +143,7 @@ async function startServer() {
userRepository,
}),
passwordResetRateLimiter: createRedisLoginRateLimiter({
keyPrefix: 'pokemon-inventory:auth:password-reset:',
keyPrefix: "pokemon-inventory:auth:password-reset:",
redisClient: redis.client,
}),
inventoryItemService: createInventoryItemService({
@@ -128,42 +153,65 @@ async function startServer() {
}),
catalogLookupRepository,
publicImageService,
publicInventoryService: createPublicInventoryService({ inventoryItemRepository, publicImageService }),
publicInventoryService: createPublicInventoryService({
inventoryItemRepository,
publicImageService,
}),
inventoryImageService: createInventoryImageService({
imageStorage: createLocalImageStorage(imageStorageRoot),
imageDerivativeJobRepository,
inventoryImageRepository,
}),
listingService: createListingService({ listingRepository, inventoryImageRepository }),
listingService: createListingService({
listingRepository,
inventoryImageRepository,
}),
saleService: createSaleService({ saleRepository }),
acquisitionService: createAcquisitionService({ acquisitionRepository }),
dashboardService: createDashboardService({
productService: createProductService({ productRepository }),
inventoryItemService: createInventoryItemService({ conditionRepository, inventoryItemRepository, productRepository }),
listingService: createListingService({ listingRepository, inventoryImageRepository }),
inventoryItemService: createInventoryItemService({
conditionRepository,
inventoryItemRepository,
productRepository,
}),
listingService: createListingService({
listingRepository,
inventoryImageRepository,
}),
saleService: createSaleService({ saleRepository }),
acquisitionService: createAcquisitionService({ acquisitionRepository }),
pricingRepository,
pricingRefreshJobRepository,
}),
pricingService: createPricingService({ pricingRepository, pricingRefreshJobRepository, providerMappingRepository, pricingProviderRegistry: pricingProviders }),
providerMappingService: createProviderMappingService({ providerMappingRepository }),
publicListingService: createPublicListingService({ listingRepository, publicImageService }),
pricingService: createPricingService({
pricingRepository,
pricingRefreshJobRepository,
providerMappingRepository,
pricingProviderRegistry: pricingProviders,
}),
providerMappingService: createProviderMappingService({
providerMappingRepository,
}),
publicListingService: createPublicListingService({
listingRepository,
publicImageService,
}),
productService: createProductService({ productRepository }),
publicProductService: createPublicProductService({ productRepository }),
searchService: createSearchService({ listingRepository }),
publicSellerService: createPublicSellerService({ sellerRepository }),
});
app.addHook('onClose', async () => {
app.addHook("onClose", async () => {
await database.close();
await redis.close();
});
await app.listen({ host: '0.0.0.0', port: parsePort(process.env.API_PORT) });
await app.listen({ host: "0.0.0.0", port: parsePort(process.env.API_PORT) });
}
startServer().catch((error) => {
console.error(error);
process.exitCode = 1;
});
});