Initial Commit

This commit is contained in:
Eric Gullickson
2025-09-17 16:09:15 -05:00
parent 0cdb9803de
commit a052040e3a
373 changed files with 437090 additions and 6773 deletions

View File

@@ -4,14 +4,12 @@
*/
import { Pool } from 'pg';
import { logger } from '../logging/logger';
import { env } from './environment';
import { getTenantConfig } from './tenant';
const tenant = getTenantConfig();
export const pool = new Pool({
host: env.DB_HOST,
port: env.DB_PORT,
database: env.DB_NAME,
user: env.DB_USER,
password: env.DB_PASSWORD,
connectionString: tenant.databaseUrl,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
@@ -30,4 +28,4 @@ process.on('SIGTERM', async () => {
await pool.end();
});
export default pool;
export default pool;

View File

@@ -8,7 +8,7 @@ import * as dotenv from 'dotenv';
dotenv.config();
const envSchema = z.object({
NODE_ENV: z.string().default('development'),
NODE_ENV: z.string().default('production'),
PORT: z.string().transform(Number).default('3001'),
// Database
@@ -32,6 +32,10 @@ const envSchema = z.object({
GOOGLE_MAPS_API_KEY: z.string().default('development'),
VPIC_API_URL: z.string().default('https://vpic.nhtsa.dot.gov/api/vehicles'),
// Platform Services
PLATFORM_VEHICLES_API_URL: z.string().default('http://mvp-platform-vehicles-api:8000'),
PLATFORM_VEHICLES_API_KEY: z.string().default('mvp-platform-vehicles-secret-key'),
// MinIO
MINIO_ENDPOINT: z.string().default('localhost'),
MINIO_PORT: z.string().transform(Number).default('9000'),
@@ -45,4 +49,4 @@ export type Environment = z.infer<typeof envSchema>;
// Validate and export - now with defaults for build-time compilation
export const env = envSchema.parse(process.env);
// Environment configuration validated and exported
// Environment configuration validated and exported

View File

@@ -4,11 +4,11 @@
*/
import Redis from 'ioredis';
import { logger } from '../logging/logger';
import { env } from './environment';
import { getTenantConfig } from './tenant';
export const redis = new Redis({
host: env.REDIS_HOST,
port: env.REDIS_PORT,
const tenant = getTenantConfig();
export const redis = new Redis(tenant.redisUrl, {
retryStrategy: (times) => Math.min(times * 50, 2000),
});
@@ -55,4 +55,4 @@ export class CacheService {
}
}
export const cacheService = new CacheService();
export const cacheService = new CacheService();

View File

@@ -0,0 +1,68 @@
import axios from 'axios';
// Simple in-memory cache for tenant validation
const tenantValidityCache = new Map<string, { ok: boolean; ts: number }>();
const TENANT_CACHE_TTL_MS = 60_000; // 1 minute
/**
* Tenant-aware configuration for multi-tenant architecture
*/
export interface TenantConfig {
tenantId: string;
databaseUrl: string;
redisUrl: string;
platformServicesUrl: string;
isAdminTenant: boolean;
}
export const getTenantConfig = (): TenantConfig => {
const tenantId = process.env.TENANT_ID || 'admin';
const databaseUrl = tenantId === 'admin'
? `postgresql://${process.env.DB_USER || 'motovault_user'}:${process.env.DB_PASSWORD}@${process.env.DB_HOST || 'postgres'}:${process.env.DB_PORT || '5432'}/${process.env.DB_NAME || 'motovault'}`
: `postgresql://motovault_user:${process.env.DB_PASSWORD}@${tenantId}-postgres:5432/motovault`;
const redisUrl = tenantId === 'admin'
? `redis://${process.env.REDIS_HOST || 'redis'}:${process.env.REDIS_PORT || '6379'}`
: `redis://${tenantId}-redis:6379`;
const platformServicesUrl = process.env.PLATFORM_TENANTS_API_URL || 'http://mvp-platform-tenants:8000';
return {
tenantId,
databaseUrl,
redisUrl,
platformServicesUrl,
isAdminTenant: tenantId === 'admin'
};
};
export const isValidTenant = async (tenantId: string): Promise<boolean> => {
// Check cache
const now = Date.now();
const cached = tenantValidityCache.get(tenantId);
if (cached && (now - cached.ts) < TENANT_CACHE_TTL_MS) {
return cached.ok;
}
let ok = false;
try {
const baseUrl = process.env.PLATFORM_TENANTS_API_URL || 'http://mvp-platform-tenants:8000';
const url = `${baseUrl}/api/v1/tenants/${encodeURIComponent(tenantId)}`;
const resp = await axios.get(url, { timeout: 2000 });
ok = resp.status === 200;
} catch { ok = false; }
tenantValidityCache.set(tenantId, { ok, ts: now });
return ok;
};
export const extractTenantId = (options: {
envTenantId?: string;
jwtTenantId?: string;
subdomain?: string;
}): string => {
const { envTenantId, jwtTenantId, subdomain } = options;
return envTenantId || jwtTenantId || subdomain || 'admin';
};