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:
209
backend/src/features/fuel-logs/data/fuel-logs.repository.ts
Normal file
209
backend/src/features/fuel-logs/data/fuel-logs.repository.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* @ai-summary Data access layer for fuel logs
|
||||
* @ai-context Handles database operations and MPG calculations
|
||||
*/
|
||||
|
||||
import { Pool } from 'pg';
|
||||
import { FuelLog, CreateFuelLogRequest, FuelStats } from '../domain/fuel-logs.types';
|
||||
|
||||
export class FuelLogsRepository {
|
||||
constructor(private pool: Pool) {}
|
||||
|
||||
async create(data: CreateFuelLogRequest & { userId: string, mpg?: number }): Promise<FuelLog> {
|
||||
const query = `
|
||||
INSERT INTO fuel_logs (
|
||||
user_id, vehicle_id, date, odometer, gallons,
|
||||
price_per_gallon, total_cost, station, location, notes, mpg
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const values = [
|
||||
data.userId,
|
||||
data.vehicleId,
|
||||
data.date,
|
||||
data.odometer,
|
||||
data.gallons,
|
||||
data.pricePerGallon,
|
||||
data.totalCost,
|
||||
data.station,
|
||||
data.location,
|
||||
data.notes,
|
||||
data.mpg
|
||||
];
|
||||
|
||||
const result = await this.pool.query(query, values);
|
||||
return this.mapRow(result.rows[0]);
|
||||
}
|
||||
|
||||
async findByVehicleId(vehicleId: string): Promise<FuelLog[]> {
|
||||
const query = `
|
||||
SELECT * FROM fuel_logs
|
||||
WHERE vehicle_id = $1
|
||||
ORDER BY date DESC, created_at DESC
|
||||
`;
|
||||
|
||||
const result = await this.pool.query(query, [vehicleId]);
|
||||
return result.rows.map(row => this.mapRow(row));
|
||||
}
|
||||
|
||||
async findByUserId(userId: string): Promise<FuelLog[]> {
|
||||
const query = `
|
||||
SELECT * FROM fuel_logs
|
||||
WHERE user_id = $1
|
||||
ORDER BY date DESC, created_at DESC
|
||||
`;
|
||||
|
||||
const result = await this.pool.query(query, [userId]);
|
||||
return result.rows.map(row => this.mapRow(row));
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<FuelLog | null> {
|
||||
const query = 'SELECT * FROM fuel_logs 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 getPreviousLog(vehicleId: string, date: string, odometer: number): Promise<FuelLog | null> {
|
||||
const query = `
|
||||
SELECT * FROM fuel_logs
|
||||
WHERE vehicle_id = $1
|
||||
AND (date < $2 OR (date = $2 AND odometer < $3))
|
||||
ORDER BY date DESC, odometer DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const result = await this.pool.query(query, [vehicleId, date, odometer]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapRow(result.rows[0]);
|
||||
}
|
||||
|
||||
async update(id: string, data: Partial<FuelLog>): Promise<FuelLog | null> {
|
||||
const fields = [];
|
||||
const values = [];
|
||||
let paramCount = 1;
|
||||
|
||||
// Build dynamic update query
|
||||
if (data.date !== undefined) {
|
||||
fields.push(`date = $${paramCount++}`);
|
||||
values.push(data.date);
|
||||
}
|
||||
if (data.odometer !== undefined) {
|
||||
fields.push(`odometer = $${paramCount++}`);
|
||||
values.push(data.odometer);
|
||||
}
|
||||
if (data.gallons !== undefined) {
|
||||
fields.push(`gallons = $${paramCount++}`);
|
||||
values.push(data.gallons);
|
||||
}
|
||||
if (data.pricePerGallon !== undefined) {
|
||||
fields.push(`price_per_gallon = $${paramCount++}`);
|
||||
values.push(data.pricePerGallon);
|
||||
}
|
||||
if (data.totalCost !== undefined) {
|
||||
fields.push(`total_cost = $${paramCount++}`);
|
||||
values.push(data.totalCost);
|
||||
}
|
||||
if (data.station !== undefined) {
|
||||
fields.push(`station = $${paramCount++}`);
|
||||
values.push(data.station);
|
||||
}
|
||||
if (data.location !== undefined) {
|
||||
fields.push(`location = $${paramCount++}`);
|
||||
values.push(data.location);
|
||||
}
|
||||
if (data.notes !== undefined) {
|
||||
fields.push(`notes = $${paramCount++}`);
|
||||
values.push(data.notes);
|
||||
}
|
||||
if (data.mpg !== undefined) {
|
||||
fields.push(`mpg = $${paramCount++}`);
|
||||
values.push(data.mpg);
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return this.findById(id);
|
||||
}
|
||||
|
||||
values.push(id);
|
||||
const query = `
|
||||
UPDATE fuel_logs
|
||||
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 delete(id: string): Promise<boolean> {
|
||||
const query = 'DELETE FROM fuel_logs WHERE id = $1';
|
||||
const result = await this.pool.query(query, [id]);
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
}
|
||||
|
||||
async getStats(vehicleId: string): Promise<FuelStats | null> {
|
||||
const query = `
|
||||
SELECT
|
||||
COUNT(*) as log_count,
|
||||
SUM(gallons) as total_gallons,
|
||||
SUM(total_cost) as total_cost,
|
||||
AVG(price_per_gallon) as avg_price_per_gallon,
|
||||
AVG(mpg) as avg_mpg,
|
||||
MAX(odometer) - MIN(odometer) as total_miles
|
||||
FROM fuel_logs
|
||||
WHERE vehicle_id = $1
|
||||
`;
|
||||
|
||||
const result = await this.pool.query(query, [vehicleId]);
|
||||
|
||||
if (result.rows.length === 0 || result.rows[0].log_count === '0') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const row = result.rows[0];
|
||||
return {
|
||||
logCount: parseInt(row.log_count),
|
||||
totalGallons: parseFloat(row.total_gallons) || 0,
|
||||
totalCost: parseFloat(row.total_cost) || 0,
|
||||
averagePricePerGallon: parseFloat(row.avg_price_per_gallon) || 0,
|
||||
averageMPG: parseFloat(row.avg_mpg) || 0,
|
||||
totalMiles: parseInt(row.total_miles) || 0,
|
||||
};
|
||||
}
|
||||
|
||||
private mapRow(row: any): FuelLog {
|
||||
return {
|
||||
id: row.id,
|
||||
userId: row.user_id,
|
||||
vehicleId: row.vehicle_id,
|
||||
date: row.date,
|
||||
odometer: row.odometer,
|
||||
gallons: parseFloat(row.gallons),
|
||||
pricePerGallon: parseFloat(row.price_per_gallon),
|
||||
totalCost: parseFloat(row.total_cost),
|
||||
station: row.station,
|
||||
location: row.location,
|
||||
notes: row.notes,
|
||||
mpg: row.mpg ? parseFloat(row.mpg) : undefined,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user