/** * @ai-summary Data access layer for vehicles * @ai-context All database operations, no business logic */ import { Pool } from 'pg'; import { Vehicle, CreateVehicleRequest, VehicleImageMeta } from '../domain/vehicles.types'; export class VehiclesRepository { constructor(private pool: Pool) {} async create(data: CreateVehicleRequest & { userId: string, make?: string, model?: string, year?: number }): Promise { const query = ` INSERT INTO vehicles ( user_id, vin, make, model, year, engine, transmission, trim_level, drive_type, fuel_type, nickname, color, license_plate, odometer_reading ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING * `; const values = [ data.userId, (data.vin && data.vin.trim().length > 0) ? data.vin.trim() : null, data.make, data.model, data.year, data.engine, data.transmission, data.trimLevel, data.driveType, data.fuelType, data.nickname, data.color, data.licensePlate, data.odometerReading || 0 ]; const result = await this.pool.query(query, values); return this.mapRow(result.rows[0]); } async findByUserId(userId: string): Promise { const query = ` SELECT * FROM vehicles WHERE user_id = $1 AND is_active = true ORDER BY created_at DESC `; const result = await this.pool.query(query, [userId]); return result.rows.map(row => this.mapRow(row)); } async findById(id: string): Promise { const query = 'SELECT * FROM vehicles WHERE id = $1'; const result = await this.pool.query(query, [id]); if (result.rows.length === 0) { return null; } return this.mapRow(result.rows[0]); } async findByUserAndVIN(userId: string, vin: string): Promise { const query = 'SELECT * FROM vehicles WHERE user_id = $1 AND vin = $2'; const result = await this.pool.query(query, [userId, vin]); if (result.rows.length === 0) { return null; } return this.mapRow(result.rows[0]); } async update(id: string, data: Partial): Promise { const fields = []; const values = []; let paramCount = 1; // Build dynamic update query if (data.make !== undefined) { fields.push(`make = $${paramCount++}`); values.push(data.make); } if (data.model !== undefined) { fields.push(`model = $${paramCount++}`); values.push(data.model); } if (data.year !== undefined) { fields.push(`year = $${paramCount++}`); values.push(data.year); } if (data.engine !== undefined) { fields.push(`engine = $${paramCount++}`); values.push(data.engine); } if (data.transmission !== undefined) { fields.push(`transmission = $${paramCount++}`); values.push(data.transmission); } if (data.trimLevel !== undefined) { fields.push(`trim_level = $${paramCount++}`); values.push(data.trimLevel); } if (data.driveType !== undefined) { fields.push(`drive_type = $${paramCount++}`); values.push(data.driveType); } if (data.fuelType !== undefined) { fields.push(`fuel_type = $${paramCount++}`); values.push(data.fuelType); } if (data.nickname !== undefined) { fields.push(`nickname = $${paramCount++}`); values.push(data.nickname); } if (data.color !== undefined) { fields.push(`color = $${paramCount++}`); values.push(data.color); } if (data.licensePlate !== undefined) { fields.push(`license_plate = $${paramCount++}`); values.push(data.licensePlate); } if (data.odometerReading !== undefined) { fields.push(`odometer_reading = $${paramCount++}`); values.push(data.odometerReading); } if (fields.length === 0) { return this.findById(id); } values.push(id); const query = ` UPDATE vehicles SET ${fields.join(', ')} WHERE id = $${paramCount} RETURNING * `; const result = await this.pool.query(query, values); if (result.rows.length === 0) { return null; } return this.mapRow(result.rows[0]); } async softDelete(id: string): Promise { const query = ` UPDATE vehicles SET is_active = false, deleted_at = NOW() WHERE id = $1 `; const result = await this.pool.query(query, [id]); return (result.rowCount ?? 0) > 0; } async updateImageMeta(id: string, userId: string, meta: VehicleImageMeta | null): Promise { if (meta === null) { const query = ` UPDATE vehicles SET image_storage_bucket = NULL, image_storage_key = NULL, image_file_name = NULL, image_content_type = NULL, image_file_size = NULL, updated_at = NOW() WHERE id = $1 AND user_id = $2 RETURNING * `; const result = await this.pool.query(query, [id, userId]); return result.rows[0] ? this.mapRow(result.rows[0]) : null; } const query = ` UPDATE vehicles SET image_storage_bucket = $1, image_storage_key = $2, image_file_name = $3, image_content_type = $4, image_file_size = $5, updated_at = NOW() WHERE id = $6 AND user_id = $7 RETURNING * `; const result = await this.pool.query(query, [ meta.imageStorageBucket, meta.imageStorageKey, meta.imageFileName, meta.imageContentType, meta.imageFileSize, id, userId ]); return result.rows[0] ? this.mapRow(result.rows[0]) : null; } private mapRow(row: any): Vehicle { return { id: row.id, userId: row.user_id, vin: row.vin, make: row.make, model: row.model, year: row.year, engine: row.engine, transmission: row.transmission, trimLevel: row.trim_level, driveType: row.drive_type, fuelType: row.fuel_type, nickname: row.nickname, color: row.color, licensePlate: row.license_plate, odometerReading: row.odometer_reading, isActive: row.is_active, deletedAt: row.deleted_at, createdAt: row.created_at, updatedAt: row.updated_at, imageStorageBucket: row.image_storage_bucket, imageStorageKey: row.image_storage_key, imageFileName: row.image_file_name, imageContentType: row.image_content_type, imageFileSize: row.image_file_size, }; } }