Homepage Redesign

This commit is contained in:
Eric Gullickson
2025-11-03 14:06:54 -06:00
parent 54d97a98b5
commit eeb20543fa
71 changed files with 3925 additions and 1340 deletions

View File

@@ -0,0 +1,156 @@
/**
* @ai-summary VIN decoding service with circuit breaker and fallback
* @ai-context PostgreSQL first, vPIC API fallback, Redis caching
*/
import { Pool } from 'pg';
import CircuitBreaker from 'opossum';
import { VehicleDataRepository } from '../data/vehicle-data.repository';
import { VPICClient } from '../data/vpic-client';
import { PlatformCacheService } from './platform-cache.service';
import { VINDecodeResponse, VINDecodeResult } from '../models/responses';
import { logger } from '../../../core/logging/logger';
export class VINDecodeService {
private repository: VehicleDataRepository;
private vpicClient: VPICClient;
private cache: PlatformCacheService;
private circuitBreaker: CircuitBreaker;
constructor(cache: PlatformCacheService) {
this.cache = cache;
this.repository = new VehicleDataRepository();
this.vpicClient = new VPICClient();
this.circuitBreaker = new CircuitBreaker(
async (vin: string) => this.vpicClient.decodeVIN(vin),
{
timeout: 6000,
errorThresholdPercentage: 50,
resetTimeout: 30000,
name: 'vpic-api'
}
);
this.circuitBreaker.on('open', () => {
logger.warn('Circuit breaker opened for vPIC API');
});
this.circuitBreaker.on('halfOpen', () => {
logger.info('Circuit breaker half-open for vPIC API');
});
this.circuitBreaker.on('close', () => {
logger.info('Circuit breaker closed for vPIC API');
});
}
/**
* Validate VIN format
*/
validateVIN(vin: string): { valid: boolean; error?: string } {
if (vin.length !== 17) {
return { valid: false, error: 'VIN must be exactly 17 characters' };
}
const invalidChars = /[IOQ]/i;
if (invalidChars.test(vin)) {
return { valid: false, error: 'VIN contains invalid characters (cannot contain I, O, Q)' };
}
const validFormat = /^[A-HJ-NPR-Z0-9]{17}$/i;
if (!validFormat.test(vin)) {
return { valid: false, error: 'VIN contains invalid characters' };
}
return { valid: true };
}
/**
* Decode VIN with multi-tier strategy:
* 1. Check cache
* 2. Try PostgreSQL function
* 3. Fallback to vPIC API (with circuit breaker)
*/
async decodeVIN(pool: Pool, vin: string): Promise<VINDecodeResponse> {
const normalizedVIN = vin.toUpperCase().trim();
const validation = this.validateVIN(normalizedVIN);
if (!validation.valid) {
return {
vin: normalizedVIN,
result: null,
success: false,
error: validation.error
};
}
try {
const cached = await this.cache.getVINDecode(normalizedVIN);
if (cached) {
logger.debug('VIN decode result retrieved from cache', { vin: normalizedVIN });
return cached;
}
let result = await this.repository.decodeVIN(pool, normalizedVIN);
if (result) {
const response: VINDecodeResponse = {
vin: normalizedVIN,
result,
success: true
};
await this.cache.setVINDecode(normalizedVIN, response, true);
logger.info('VIN decoded successfully via PostgreSQL', { vin: normalizedVIN, make: result.make, model: result.model, year: result.year });
return response;
}
logger.info('VIN not found in PostgreSQL, attempting vPIC fallback', { vin: normalizedVIN });
try {
result = await this.circuitBreaker.fire(normalizedVIN) as VINDecodeResult | null;
if (result) {
const response: VINDecodeResponse = {
vin: normalizedVIN,
result,
success: true
};
await this.cache.setVINDecode(normalizedVIN, response, true);
logger.info('VIN decoded successfully via vPIC fallback', { vin: normalizedVIN, make: result.make, model: result.model, year: result.year });
return response;
}
} catch (circuitError) {
logger.warn('vPIC API unavailable or circuit breaker open', { vin: normalizedVIN, error: circuitError });
}
const failureResponse: VINDecodeResponse = {
vin: normalizedVIN,
result: null,
success: false,
error: 'VIN not found in database and external API unavailable'
};
await this.cache.setVINDecode(normalizedVIN, failureResponse, false);
return failureResponse;
} catch (error) {
logger.error('VIN decode error', { vin: normalizedVIN, error });
return {
vin: normalizedVIN,
result: null,
success: false,
error: 'Internal server error during VIN decoding'
};
}
}
/**
* Get circuit breaker status
*/
getCircuitBreakerStatus(): { state: string; stats: any } {
return {
state: this.circuitBreaker.opened ? 'open' : this.circuitBreaker.halfOpen ? 'half-open' : 'closed',
stats: this.circuitBreaker.stats
};
}
}