Admin User v1

This commit is contained in:
Eric Gullickson
2025-11-05 19:04:06 -06:00
parent e4e7e32a4f
commit 8174e0d5f9
48 changed files with 11289 additions and 1112 deletions

View File

@@ -0,0 +1,975 @@
/**
* @ai-summary Vehicle catalog management service
* @ai-context Handles CRUD operations on platform vehicle catalog data with transaction support
*/
import { Pool } from 'pg';
import { logger } from '../../../core/logging/logger';
import { PlatformCacheService } from '../../platform/domain/platform-cache.service';
export interface CatalogMake {
id: number;
name: string;
}
export interface CatalogModel {
id: number;
makeId: number;
name: string;
}
export interface CatalogYear {
id: number;
modelId: number;
year: number;
}
export interface CatalogTrim {
id: number;
yearId: number;
name: string;
}
export interface CatalogEngine {
id: number;
trimId: number;
name: string;
description?: string;
}
export interface PlatformChangeLog {
id: string;
changeType: 'CREATE' | 'UPDATE' | 'DELETE';
resourceType: 'makes' | 'models' | 'years' | 'trims' | 'engines';
resourceId: string;
oldValue: any;
newValue: any;
changedBy: string;
createdAt: Date;
}
export class VehicleCatalogService {
constructor(
private pool: Pool,
private cacheService: PlatformCacheService
) {}
// MAKES OPERATIONS
async getAllMakes(): Promise<CatalogMake[]> {
const query = `
SELECT cache_key, data
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:makes:%'
ORDER BY (data->>'name')
`;
try {
const result = await this.pool.query(query);
return result.rows.map(row => ({
id: parseInt(row.cache_key.split(':')[2]),
name: row.data.name
}));
} catch (error) {
logger.error('Error getting all makes', { error });
throw error;
}
}
async createMake(name: string, changedBy: string): Promise<CatalogMake> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Get next ID
const idResult = await client.query(`
SELECT COALESCE(MAX(CAST(SPLIT_PART(cache_key, ':', 3) AS INTEGER)), 0) + 1 as next_id
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:makes:%'
`);
const makeId = idResult.rows[0].next_id;
// Insert make
const make: CatalogMake = { id: makeId, name };
await client.query(`
INSERT INTO vehicle_dropdown_cache (cache_key, data, expires_at)
VALUES ($1, $2, NOW() + INTERVAL '10 years')
`, [`catalog:makes:${makeId}`, JSON.stringify(make)]);
// Log change
await this.logChange(client, 'CREATE', 'makes', makeId.toString(), null, make, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Make created', { makeId, name, changedBy });
return make;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error creating make', { error, name });
throw error;
} finally {
client.release();
}
}
async updateMake(makeId: number, name: string, changedBy: string): Promise<CatalogMake> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:makes:${makeId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Make ${makeId} not found`);
}
const oldValue = oldResult.rows[0].data;
const newValue: CatalogMake = { id: makeId, name };
// Update make
await client.query(`
UPDATE vehicle_dropdown_cache
SET data = $1, updated_at = NOW()
WHERE cache_key = $2
`, [JSON.stringify(newValue), `catalog:makes:${makeId}`]);
// Log change
await this.logChange(client, 'UPDATE', 'makes', makeId.toString(), oldValue, newValue, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Make updated', { makeId, name, changedBy });
return newValue;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error updating make', { error, makeId, name });
throw error;
} finally {
client.release();
}
}
async deleteMake(makeId: number, changedBy: string): Promise<void> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Check for dependent models
const modelsCheck = await client.query(`
SELECT COUNT(*) as count
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:models:%'
AND (data->>'makeId')::int = $1
`, [makeId]);
if (parseInt(modelsCheck.rows[0].count) > 0) {
throw new Error('Cannot delete make with existing models');
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:makes:${makeId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Make ${makeId} not found`);
}
const oldValue = oldResult.rows[0].data;
// Delete make
await client.query(`
DELETE FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:makes:${makeId}`]);
// Log change
await this.logChange(client, 'DELETE', 'makes', makeId.toString(), oldValue, null, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Make deleted', { makeId, changedBy });
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error deleting make', { error, makeId });
throw error;
} finally {
client.release();
}
}
// MODELS OPERATIONS
async getModelsByMake(makeId: number): Promise<CatalogModel[]> {
const query = `
SELECT cache_key, data
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:models:%'
AND (data->>'makeId')::int = $1
ORDER BY (data->>'name')
`;
try {
const result = await this.pool.query(query, [makeId]);
return result.rows.map(row => ({
id: parseInt(row.cache_key.split(':')[2]),
makeId: row.data.makeId,
name: row.data.name
}));
} catch (error) {
logger.error('Error getting models by make', { error, makeId });
throw error;
}
}
async createModel(makeId: number, name: string, changedBy: string): Promise<CatalogModel> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify make exists
const makeCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:makes:${makeId}`]);
if (makeCheck.rows.length === 0) {
throw new Error(`Make ${makeId} not found`);
}
// Get next ID
const idResult = await client.query(`
SELECT COALESCE(MAX(CAST(SPLIT_PART(cache_key, ':', 3) AS INTEGER)), 0) + 1 as next_id
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:models:%'
`);
const modelId = idResult.rows[0].next_id;
// Insert model
const model: CatalogModel = { id: modelId, makeId, name };
await client.query(`
INSERT INTO vehicle_dropdown_cache (cache_key, data, expires_at)
VALUES ($1, $2, NOW() + INTERVAL '10 years')
`, [`catalog:models:${modelId}`, JSON.stringify(model)]);
// Log change
await this.logChange(client, 'CREATE', 'models', modelId.toString(), null, model, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Model created', { modelId, makeId, name, changedBy });
return model;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error creating model', { error, makeId, name });
throw error;
} finally {
client.release();
}
}
async updateModel(modelId: number, makeId: number, name: string, changedBy: string): Promise<CatalogModel> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify make exists
const makeCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:makes:${makeId}`]);
if (makeCheck.rows.length === 0) {
throw new Error(`Make ${makeId} not found`);
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:models:${modelId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Model ${modelId} not found`);
}
const oldValue = oldResult.rows[0].data;
const newValue: CatalogModel = { id: modelId, makeId, name };
// Update model
await client.query(`
UPDATE vehicle_dropdown_cache
SET data = $1, updated_at = NOW()
WHERE cache_key = $2
`, [JSON.stringify(newValue), `catalog:models:${modelId}`]);
// Log change
await this.logChange(client, 'UPDATE', 'models', modelId.toString(), oldValue, newValue, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Model updated', { modelId, makeId, name, changedBy });
return newValue;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error updating model', { error, modelId, name });
throw error;
} finally {
client.release();
}
}
async deleteModel(modelId: number, changedBy: string): Promise<void> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Check for dependent years
const yearsCheck = await client.query(`
SELECT COUNT(*) as count
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:years:%'
AND (data->>'modelId')::int = $1
`, [modelId]);
if (parseInt(yearsCheck.rows[0].count) > 0) {
throw new Error('Cannot delete model with existing years');
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:models:${modelId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Model ${modelId} not found`);
}
const oldValue = oldResult.rows[0].data;
// Delete model
await client.query(`
DELETE FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:models:${modelId}`]);
// Log change
await this.logChange(client, 'DELETE', 'models', modelId.toString(), oldValue, null, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Model deleted', { modelId, changedBy });
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error deleting model', { error, modelId });
throw error;
} finally {
client.release();
}
}
// YEARS OPERATIONS
async getYearsByModel(modelId: number): Promise<CatalogYear[]> {
const query = `
SELECT cache_key, data
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:years:%'
AND (data->>'modelId')::int = $1
ORDER BY (data->>'year')::int DESC
`;
try {
const result = await this.pool.query(query, [modelId]);
return result.rows.map(row => ({
id: parseInt(row.cache_key.split(':')[2]),
modelId: row.data.modelId,
year: row.data.year
}));
} catch (error) {
logger.error('Error getting years by model', { error, modelId });
throw error;
}
}
async createYear(modelId: number, year: number, changedBy: string): Promise<CatalogYear> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify model exists
const modelCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:models:${modelId}`]);
if (modelCheck.rows.length === 0) {
throw new Error(`Model ${modelId} not found`);
}
// Get next ID
const idResult = await client.query(`
SELECT COALESCE(MAX(CAST(SPLIT_PART(cache_key, ':', 3) AS INTEGER)), 0) + 1 as next_id
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:years:%'
`);
const yearId = idResult.rows[0].next_id;
// Insert year
const yearData: CatalogYear = { id: yearId, modelId, year };
await client.query(`
INSERT INTO vehicle_dropdown_cache (cache_key, data, expires_at)
VALUES ($1, $2, NOW() + INTERVAL '10 years')
`, [`catalog:years:${yearId}`, JSON.stringify(yearData)]);
// Log change
await this.logChange(client, 'CREATE', 'years', yearId.toString(), null, yearData, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Year created', { yearId, modelId, year, changedBy });
return yearData;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error creating year', { error, modelId, year });
throw error;
} finally {
client.release();
}
}
async updateYear(yearId: number, modelId: number, year: number, changedBy: string): Promise<CatalogYear> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify model exists
const modelCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:models:${modelId}`]);
if (modelCheck.rows.length === 0) {
throw new Error(`Model ${modelId} not found`);
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:years:${yearId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Year ${yearId} not found`);
}
const oldValue = oldResult.rows[0].data;
const newValue: CatalogYear = { id: yearId, modelId, year };
// Update year
await client.query(`
UPDATE vehicle_dropdown_cache
SET data = $1, updated_at = NOW()
WHERE cache_key = $2
`, [JSON.stringify(newValue), `catalog:years:${yearId}`]);
// Log change
await this.logChange(client, 'UPDATE', 'years', yearId.toString(), oldValue, newValue, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Year updated', { yearId, modelId, year, changedBy });
return newValue;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error updating year', { error, yearId, year });
throw error;
} finally {
client.release();
}
}
async deleteYear(yearId: number, changedBy: string): Promise<void> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Check for dependent trims
const trimsCheck = await client.query(`
SELECT COUNT(*) as count
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:trims:%'
AND (data->>'yearId')::int = $1
`, [yearId]);
if (parseInt(trimsCheck.rows[0].count) > 0) {
throw new Error('Cannot delete year with existing trims');
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:years:${yearId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Year ${yearId} not found`);
}
const oldValue = oldResult.rows[0].data;
// Delete year
await client.query(`
DELETE FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:years:${yearId}`]);
// Log change
await this.logChange(client, 'DELETE', 'years', yearId.toString(), oldValue, null, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Year deleted', { yearId, changedBy });
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error deleting year', { error, yearId });
throw error;
} finally {
client.release();
}
}
// TRIMS OPERATIONS
async getTrimsByYear(yearId: number): Promise<CatalogTrim[]> {
const query = `
SELECT cache_key, data
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:trims:%'
AND (data->>'yearId')::int = $1
ORDER BY (data->>'name')
`;
try {
const result = await this.pool.query(query, [yearId]);
return result.rows.map(row => ({
id: parseInt(row.cache_key.split(':')[2]),
yearId: row.data.yearId,
name: row.data.name
}));
} catch (error) {
logger.error('Error getting trims by year', { error, yearId });
throw error;
}
}
async createTrim(yearId: number, name: string, changedBy: string): Promise<CatalogTrim> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify year exists
const yearCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:years:${yearId}`]);
if (yearCheck.rows.length === 0) {
throw new Error(`Year ${yearId} not found`);
}
// Get next ID
const idResult = await client.query(`
SELECT COALESCE(MAX(CAST(SPLIT_PART(cache_key, ':', 3) AS INTEGER)), 0) + 1 as next_id
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:trims:%'
`);
const trimId = idResult.rows[0].next_id;
// Insert trim
const trim: CatalogTrim = { id: trimId, yearId, name };
await client.query(`
INSERT INTO vehicle_dropdown_cache (cache_key, data, expires_at)
VALUES ($1, $2, NOW() + INTERVAL '10 years')
`, [`catalog:trims:${trimId}`, JSON.stringify(trim)]);
// Log change
await this.logChange(client, 'CREATE', 'trims', trimId.toString(), null, trim, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Trim created', { trimId, yearId, name, changedBy });
return trim;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error creating trim', { error, yearId, name });
throw error;
} finally {
client.release();
}
}
async updateTrim(trimId: number, yearId: number, name: string, changedBy: string): Promise<CatalogTrim> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify year exists
const yearCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:years:${yearId}`]);
if (yearCheck.rows.length === 0) {
throw new Error(`Year ${yearId} not found`);
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:trims:${trimId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Trim ${trimId} not found`);
}
const oldValue = oldResult.rows[0].data;
const newValue: CatalogTrim = { id: trimId, yearId, name };
// Update trim
await client.query(`
UPDATE vehicle_dropdown_cache
SET data = $1, updated_at = NOW()
WHERE cache_key = $2
`, [JSON.stringify(newValue), `catalog:trims:${trimId}`]);
// Log change
await this.logChange(client, 'UPDATE', 'trims', trimId.toString(), oldValue, newValue, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Trim updated', { trimId, yearId, name, changedBy });
return newValue;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error updating trim', { error, trimId, name });
throw error;
} finally {
client.release();
}
}
async deleteTrim(trimId: number, changedBy: string): Promise<void> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Check for dependent engines
const enginesCheck = await client.query(`
SELECT COUNT(*) as count
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:engines:%'
AND (data->>'trimId')::int = $1
`, [trimId]);
if (parseInt(enginesCheck.rows[0].count) > 0) {
throw new Error('Cannot delete trim with existing engines');
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:trims:${trimId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Trim ${trimId} not found`);
}
const oldValue = oldResult.rows[0].data;
// Delete trim
await client.query(`
DELETE FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:trims:${trimId}`]);
// Log change
await this.logChange(client, 'DELETE', 'trims', trimId.toString(), oldValue, null, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Trim deleted', { trimId, changedBy });
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error deleting trim', { error, trimId });
throw error;
} finally {
client.release();
}
}
// ENGINES OPERATIONS
async getEnginesByTrim(trimId: number): Promise<CatalogEngine[]> {
const query = `
SELECT cache_key, data
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:engines:%'
AND (data->>'trimId')::int = $1
ORDER BY (data->>'name')
`;
try {
const result = await this.pool.query(query, [trimId]);
return result.rows.map(row => ({
id: parseInt(row.cache_key.split(':')[2]),
trimId: row.data.trimId,
name: row.data.name,
description: row.data.description
}));
} catch (error) {
logger.error('Error getting engines by trim', { error, trimId });
throw error;
}
}
async createEngine(trimId: number, name: string, description: string | undefined, changedBy: string): Promise<CatalogEngine> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify trim exists
const trimCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:trims:${trimId}`]);
if (trimCheck.rows.length === 0) {
throw new Error(`Trim ${trimId} not found`);
}
// Get next ID
const idResult = await client.query(`
SELECT COALESCE(MAX(CAST(SPLIT_PART(cache_key, ':', 3) AS INTEGER)), 0) + 1 as next_id
FROM vehicle_dropdown_cache
WHERE cache_key LIKE 'catalog:engines:%'
`);
const engineId = idResult.rows[0].next_id;
// Insert engine
const engine: CatalogEngine = { id: engineId, trimId, name, description };
await client.query(`
INSERT INTO vehicle_dropdown_cache (cache_key, data, expires_at)
VALUES ($1, $2, NOW() + INTERVAL '10 years')
`, [`catalog:engines:${engineId}`, JSON.stringify(engine)]);
// Log change
await this.logChange(client, 'CREATE', 'engines', engineId.toString(), null, engine, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Engine created', { engineId, trimId, name, changedBy });
return engine;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error creating engine', { error, trimId, name });
throw error;
} finally {
client.release();
}
}
async updateEngine(engineId: number, trimId: number, name: string, description: string | undefined, changedBy: string): Promise<CatalogEngine> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Verify trim exists
const trimCheck = await client.query(`
SELECT 1 FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:trims:${trimId}`]);
if (trimCheck.rows.length === 0) {
throw new Error(`Trim ${trimId} not found`);
}
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:engines:${engineId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Engine ${engineId} not found`);
}
const oldValue = oldResult.rows[0].data;
const newValue: CatalogEngine = { id: engineId, trimId, name, description };
// Update engine
await client.query(`
UPDATE vehicle_dropdown_cache
SET data = $1, updated_at = NOW()
WHERE cache_key = $2
`, [JSON.stringify(newValue), `catalog:engines:${engineId}`]);
// Log change
await this.logChange(client, 'UPDATE', 'engines', engineId.toString(), oldValue, newValue, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Engine updated', { engineId, trimId, name, changedBy });
return newValue;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error updating engine', { error, engineId, name });
throw error;
} finally {
client.release();
}
}
async deleteEngine(engineId: number, changedBy: string): Promise<void> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Get old value
const oldResult = await client.query(`
SELECT data FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:engines:${engineId}`]);
if (oldResult.rows.length === 0) {
throw new Error(`Engine ${engineId} not found`);
}
const oldValue = oldResult.rows[0].data;
// Delete engine
await client.query(`
DELETE FROM vehicle_dropdown_cache WHERE cache_key = $1
`, [`catalog:engines:${engineId}`]);
// Log change
await this.logChange(client, 'DELETE', 'engines', engineId.toString(), oldValue, null, changedBy);
await client.query('COMMIT');
// Invalidate cache
await this.cacheService.invalidateVehicleData();
logger.info('Engine deleted', { engineId, changedBy });
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error deleting engine', { error, engineId });
throw error;
} finally {
client.release();
}
}
// HELPER METHODS
private async logChange(
client: any,
changeType: 'CREATE' | 'UPDATE' | 'DELETE',
resourceType: 'makes' | 'models' | 'years' | 'trims' | 'engines',
resourceId: string,
oldValue: any,
newValue: any,
changedBy: string
): Promise<void> {
const query = `
INSERT INTO platform_change_log (change_type, resource_type, resource_id, old_value, new_value, changed_by)
VALUES ($1, $2, $3, $4, $5, $6)
`;
await client.query(query, [
changeType,
resourceType,
resourceId,
oldValue ? JSON.stringify(oldValue) : null,
newValue ? JSON.stringify(newValue) : null,
changedBy
]);
}
async getChangeLogs(limit: number = 100, offset: number = 0): Promise<{ logs: PlatformChangeLog[]; total: number }> {
const countQuery = 'SELECT COUNT(*) as total FROM platform_change_log';
const query = `
SELECT id, change_type, resource_type, resource_id, old_value, new_value, changed_by, created_at
FROM platform_change_log
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`;
try {
const [countResult, dataResult] = await Promise.all([
this.pool.query(countQuery),
this.pool.query(query, [limit, offset])
]);
const total = parseInt(countResult.rows[0].total, 10);
const logs = dataResult.rows.map(row => ({
id: row.id,
changeType: row.change_type,
resourceType: row.resource_type,
resourceId: row.resource_id,
oldValue: row.old_value,
newValue: row.new_value,
changedBy: row.changed_by,
createdAt: new Date(row.created_at)
}));
return { logs, total };
} catch (error) {
logger.error('Error fetching change logs', { error });
throw error;
}
}
}