Fix GitHub Actions build by adding missing repository files
The build was failing because repository files were ignored by .gitignore: - backend/src/features/*/data/*.repository.ts files were excluded by 'data/' pattern - These files exist locally but were missing in CI, causing TS2307 module errors - Controllers and services import these repositories, causing cascade failures Changes: - Updated .gitignore to allow TypeScript files in feature data directories - Added fuel-logs.repository.ts, stations.repository.ts, vehicles.repository.ts - Docker build now succeeds (tested with --no-cache) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
177
backend/src/features/vehicles/data/vehicles.repository.ts
Normal file
177
backend/src/features/vehicles/data/vehicles.repository.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @ai-summary Data access layer for vehicles
|
||||
* @ai-context All database operations, no business logic
|
||||
*/
|
||||
|
||||
import { Pool } from 'pg';
|
||||
import { Vehicle, CreateVehicleRequest } from '../domain/vehicles.types';
|
||||
|
||||
export class VehiclesRepository {
|
||||
constructor(private pool: Pool) {}
|
||||
|
||||
async create(data: CreateVehicleRequest & { userId: string, make?: string, model?: string, year?: number }): Promise<Vehicle> {
|
||||
const query = `
|
||||
INSERT INTO vehicles (
|
||||
user_id, vin, make, model, year,
|
||||
nickname, color, license_plate, odometer_reading
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const values = [
|
||||
data.userId,
|
||||
data.vin,
|
||||
data.make,
|
||||
data.model,
|
||||
data.year,
|
||||
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<Vehicle[]> {
|
||||
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<Vehicle | null> {
|
||||
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<Vehicle | null> {
|
||||
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<Vehicle>): Promise<Vehicle | null> {
|
||||
const fields = [];
|
||||
const values = [];
|
||||
let paramCount = 1;
|
||||
|
||||
// Build dynamic update query
|
||||
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<boolean> {
|
||||
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;
|
||||
}
|
||||
|
||||
// Cache VIN decode results
|
||||
async cacheVINDecode(vin: string, data: any): Promise<void> {
|
||||
const query = `
|
||||
INSERT INTO vin_cache (vin, make, model, year, engine_type, body_type, raw_data)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (vin) DO UPDATE
|
||||
SET make = $2, model = $3, year = $4,
|
||||
engine_type = $5, body_type = $6, raw_data = $7,
|
||||
cached_at = NOW()
|
||||
`;
|
||||
|
||||
await this.pool.query(query, [
|
||||
vin,
|
||||
data.make,
|
||||
data.model,
|
||||
data.year,
|
||||
data.engineType,
|
||||
data.bodyType,
|
||||
JSON.stringify(data.rawData)
|
||||
]);
|
||||
}
|
||||
|
||||
async getVINFromCache(vin: string): Promise<any | null> {
|
||||
const query = 'SELECT * FROM vin_cache WHERE vin = $1';
|
||||
const result = await this.pool.query(query, [vin]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user