235 lines
12 KiB
JavaScript
235 lines
12 KiB
JavaScript
import dotenv from "dotenv";
|
|
import { fileURLToPath } from "node: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 { createHttpPricingProvider } from "../modules/pricing/providers/http-pricing-provider.js";
|
|
import { createAdminExportService } from "../modules/exports/services/admin-export-service.js";
|
|
import { createInventoryExportJobRepository } from "../modules/exports/repositories/inventory-export-job-repository.js";
|
|
import { createSellerSettingsRepository } from "../modules/settings/repositories/seller-settings-repository.js";
|
|
import { createSellerSettingsService } from "../modules/settings/services/seller-settings-service.js";
|
|
import { createSavedViewRepository } from "../modules/search/repositories/saved-view-repository.js";
|
|
import { createSavedViewService } from "../modules/search/services/saved-view-service.js";
|
|
|
|
function parsePort(value) {
|
|
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.");
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
async function startServer() {
|
|
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 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 sellerSettingsService = createSellerSettingsService({
|
|
sellerSettingsRepository: createSellerSettingsRepository(database.db),
|
|
});
|
|
const savedViewService = createSavedViewService({
|
|
savedViewRepository: createSavedViewRepository(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 pricingService = createPricingService({
|
|
pricingRepository,
|
|
pricingRefreshJobRepository,
|
|
providerMappingRepository,
|
|
pricingProviderRegistry: pricingProviders,
|
|
});
|
|
const inventoryItemService = createInventoryItemService({
|
|
conditionRepository,
|
|
inventoryItemRepository,
|
|
pricingService,
|
|
productRepository,
|
|
});
|
|
const userRepository = createUserRepository(database.db);
|
|
const sessionRepository = createSessionRepository(database.db);
|
|
const passwordResetRepository = createPasswordResetRepository(database.db);
|
|
const mfaMethodRepository = createMfaMethodRepository(database.db);
|
|
const securityAuditRepository = createSecurityAuditRepository(database.db);
|
|
const secretProtector = createSecretProtector();
|
|
const mfaService = createMfaService({
|
|
mfaMethodRepository,
|
|
secretProtector,
|
|
securityAuditRepository,
|
|
});
|
|
const passwordResetNotifier = createPasswordResetNotifier();
|
|
const publicImageService = createPublicImageService({
|
|
imageStorage: createLocalImageStorage(imageStorageRoot),
|
|
inventoryImageRepository,
|
|
});
|
|
const authenticationService = createAuthenticationService({
|
|
mfaService,
|
|
sessionRepository,
|
|
securityAuditRepository,
|
|
userRepository,
|
|
});
|
|
const app = buildApp({
|
|
authenticationService,
|
|
authenticateAdmin: (request) =>
|
|
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,
|
|
}),
|
|
mfaService,
|
|
passwordRecoveryService: createPasswordRecoveryService({
|
|
passwordResetNotifier,
|
|
passwordResetRepository,
|
|
securityAuditRepository,
|
|
userRepository,
|
|
}),
|
|
passwordResetRateLimiter: createRedisLoginRateLimiter({
|
|
keyPrefix: "pokemon-inventory:auth:password-reset:",
|
|
redisClient: redis.client,
|
|
}),
|
|
inventoryItemService,
|
|
adminExportService: createAdminExportService({
|
|
inventoryExportJobRepository: createInventoryExportJobRepository(database.db),
|
|
inventoryItemService,
|
|
}),
|
|
sellerSettingsService,
|
|
savedViewService,
|
|
catalogLookupRepository,
|
|
publicImageService,
|
|
publicInventoryService: createPublicInventoryService({
|
|
inventoryItemRepository,
|
|
publicImageService,
|
|
}),
|
|
inventoryImageService: createInventoryImageService({
|
|
imageStorage: createLocalImageStorage(imageStorageRoot),
|
|
imageDerivativeJobRepository,
|
|
inventoryImageRepository,
|
|
}),
|
|
listingService: createListingService({
|
|
listingRepository,
|
|
inventoryImageRepository,
|
|
}),
|
|
saleService: createSaleService({ saleRepository }),
|
|
acquisitionService: createAcquisitionService({ acquisitionRepository }),
|
|
dashboardService: createDashboardService({
|
|
productService: createProductService({ productRepository }),
|
|
inventoryItemService,
|
|
listingService: createListingService({
|
|
listingRepository,
|
|
inventoryImageRepository,
|
|
}),
|
|
saleService: createSaleService({ saleRepository }),
|
|
acquisitionService: createAcquisitionService({ acquisitionRepository }),
|
|
pricingRepository,
|
|
pricingRefreshJobRepository,
|
|
}),
|
|
pricingService,
|
|
providerMappingService: createProviderMappingService({
|
|
providerMappingRepository,
|
|
}),
|
|
publicListingService: createPublicListingService({
|
|
listingRepository,
|
|
publicImageService,
|
|
}),
|
|
productService: createProductService({ productRepository }),
|
|
publicProductService: createPublicProductService({ productRepository }),
|
|
searchService: createSearchService({ listingRepository }),
|
|
publicSellerService: createPublicSellerService({ sellerRepository }),
|
|
});
|
|
|
|
app.addHook("onClose", async () => {
|
|
await database.close();
|
|
await redis.close();
|
|
});
|
|
|
|
await app.listen({ host: "0.0.0.0", port: parsePort(process.env.API_PORT) });
|
|
}
|
|
|
|
startServer().catch((error) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|