This commit is contained in:
Eric Gullickson
2025-10-16 19:20:30 -05:00
parent 225520ad30
commit 5638d3960b
68 changed files with 4164 additions and 18995 deletions

View File

@@ -0,0 +1,262 @@
import { Pool } from 'pg';
import pool from '../../../core/config/database';
import type { MaintenanceRecord, MaintenanceSchedule, MaintenanceCategory } from '../domain/maintenance.types';
export class MaintenanceRepository {
constructor(private readonly db: Pool = pool) {}
// ========================
// Maintenance Records
// ========================
async insertRecord(record: {
id: string;
user_id: string;
vehicle_id: string;
category: MaintenanceCategory;
subtypes: string[];
date: string;
odometer_reading?: number | null;
cost?: number | null;
shop_name?: string | null;
notes?: string | null;
}): Promise<MaintenanceRecord> {
const res = await this.db.query(
`INSERT INTO maintenance_records (
id, user_id, vehicle_id, category, subtypes, date, odometer_reading, cost, shop_name, notes
) VALUES ($1, $2, $3, $4, $5::text[], $6, $7, $8, $9, $10)
RETURNING *`,
[
record.id,
record.user_id,
record.vehicle_id,
record.category,
record.subtypes,
record.date,
record.odometer_reading ?? null,
record.cost ?? null,
record.shop_name ?? null,
record.notes ?? null,
]
);
return res.rows[0] as MaintenanceRecord;
}
async findRecordById(id: string, userId: string): Promise<MaintenanceRecord | null> {
const res = await this.db.query(
`SELECT * FROM maintenance_records WHERE id = $1 AND user_id = $2`,
[id, userId]
);
return res.rows[0] || null;
}
async findRecordsByUserId(
userId: string,
filters?: { vehicleId?: string; category?: MaintenanceCategory }
): Promise<MaintenanceRecord[]> {
const conds: string[] = ['user_id = $1'];
const params: any[] = [userId];
let i = 2;
if (filters?.vehicleId) {
conds.push(`vehicle_id = $${i++}`);
params.push(filters.vehicleId);
}
if (filters?.category) {
conds.push(`category = $${i++}`);
params.push(filters.category);
}
const sql = `SELECT * FROM maintenance_records WHERE ${conds.join(' AND ')} ORDER BY date DESC`;
const res = await this.db.query(sql, params);
return res.rows as MaintenanceRecord[];
}
async findRecordsByVehicleId(vehicleId: string, userId: string): Promise<MaintenanceRecord[]> {
const res = await this.db.query(
`SELECT * FROM maintenance_records WHERE vehicle_id = $1 AND user_id = $2 ORDER BY date DESC`,
[vehicleId, userId]
);
return res.rows as MaintenanceRecord[];
}
async updateRecord(
id: string,
userId: string,
patch: Partial<Pick<MaintenanceRecord, 'category' | 'subtypes' | 'date' | 'odometer_reading' | 'cost' | 'shop_name' | 'notes'>>
): Promise<MaintenanceRecord | null> {
const fields: string[] = [];
const params: any[] = [];
let i = 1;
if (patch.category !== undefined) {
fields.push(`category = $${i++}`);
params.push(patch.category);
}
if (patch.subtypes !== undefined) {
fields.push(`subtypes = $${i++}::text[]`);
params.push(patch.subtypes);
}
if (patch.date !== undefined) {
fields.push(`date = $${i++}`);
params.push(patch.date);
}
if (patch.odometer_reading !== undefined) {
fields.push(`odometer_reading = $${i++}`);
params.push(patch.odometer_reading);
}
if (patch.cost !== undefined) {
fields.push(`cost = $${i++}`);
params.push(patch.cost);
}
if (patch.shop_name !== undefined) {
fields.push(`shop_name = $${i++}`);
params.push(patch.shop_name);
}
if (patch.notes !== undefined) {
fields.push(`notes = $${i++}`);
params.push(patch.notes);
}
if (!fields.length) return this.findRecordById(id, userId);
params.push(id, userId);
const sql = `UPDATE maintenance_records SET ${fields.join(', ')} WHERE id = $${i++} AND user_id = $${i++} RETURNING *`;
const res = await this.db.query(sql, params);
return res.rows[0] || null;
}
async deleteRecord(id: string, userId: string): Promise<void> {
await this.db.query(
`DELETE FROM maintenance_records WHERE id = $1 AND user_id = $2`,
[id, userId]
);
}
// ========================
// Maintenance Schedules
// ========================
async insertSchedule(schedule: {
id: string;
user_id: string;
vehicle_id: string;
category: MaintenanceCategory;
subtypes: string[];
interval_months?: number | null;
interval_miles?: number | null;
last_service_date?: string | null;
last_service_mileage?: number | null;
next_due_date?: string | null;
next_due_mileage?: number | null;
is_active: boolean;
}): Promise<MaintenanceSchedule> {
const res = await this.db.query(
`INSERT INTO maintenance_schedules (
id, user_id, vehicle_id, category, subtypes, interval_months, interval_miles,
last_service_date, last_service_mileage, next_due_date, next_due_mileage, is_active
) VALUES ($1, $2, $3, $4, $5::text[], $6, $7, $8, $9, $10, $11, $12)
RETURNING *`,
[
schedule.id,
schedule.user_id,
schedule.vehicle_id,
schedule.category,
schedule.subtypes,
schedule.interval_months ?? null,
schedule.interval_miles ?? null,
schedule.last_service_date ?? null,
schedule.last_service_mileage ?? null,
schedule.next_due_date ?? null,
schedule.next_due_mileage ?? null,
schedule.is_active,
]
);
return res.rows[0] as MaintenanceSchedule;
}
async findScheduleById(id: string, userId: string): Promise<MaintenanceSchedule | null> {
const res = await this.db.query(
`SELECT * FROM maintenance_schedules WHERE id = $1 AND user_id = $2`,
[id, userId]
);
return res.rows[0] || null;
}
async findSchedulesByVehicleId(vehicleId: string, userId: string): Promise<MaintenanceSchedule[]> {
const res = await this.db.query(
`SELECT * FROM maintenance_schedules WHERE vehicle_id = $1 AND user_id = $2 ORDER BY created_at DESC`,
[vehicleId, userId]
);
return res.rows as MaintenanceSchedule[];
}
async findActiveSchedulesByVehicleId(vehicleId: string, userId: string): Promise<MaintenanceSchedule[]> {
const res = await this.db.query(
`SELECT * FROM maintenance_schedules WHERE vehicle_id = $1 AND user_id = $2 AND is_active = true ORDER BY created_at DESC`,
[vehicleId, userId]
);
return res.rows as MaintenanceSchedule[];
}
async updateSchedule(
id: string,
userId: string,
patch: Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'interval_months' | 'interval_miles' | 'last_service_date' | 'last_service_mileage' | 'next_due_date' | 'next_due_mileage' | 'is_active'>>
): Promise<MaintenanceSchedule | null> {
const fields: string[] = [];
const params: any[] = [];
let i = 1;
if (patch.category !== undefined) {
fields.push(`category = $${i++}`);
params.push(patch.category);
}
if (patch.subtypes !== undefined) {
fields.push(`subtypes = $${i++}::text[]`);
params.push(patch.subtypes);
}
if (patch.interval_months !== undefined) {
fields.push(`interval_months = $${i++}`);
params.push(patch.interval_months);
}
if (patch.interval_miles !== undefined) {
fields.push(`interval_miles = $${i++}`);
params.push(patch.interval_miles);
}
if (patch.last_service_date !== undefined) {
fields.push(`last_service_date = $${i++}`);
params.push(patch.last_service_date);
}
if (patch.last_service_mileage !== undefined) {
fields.push(`last_service_mileage = $${i++}`);
params.push(patch.last_service_mileage);
}
if (patch.next_due_date !== undefined) {
fields.push(`next_due_date = $${i++}`);
params.push(patch.next_due_date);
}
if (patch.next_due_mileage !== undefined) {
fields.push(`next_due_mileage = $${i++}`);
params.push(patch.next_due_mileage);
}
if (patch.is_active !== undefined) {
fields.push(`is_active = $${i++}`);
params.push(patch.is_active);
}
if (!fields.length) return this.findScheduleById(id, userId);
params.push(id, userId);
const sql = `UPDATE maintenance_schedules SET ${fields.join(', ')} WHERE id = $${i++} AND user_id = $${i++} RETURNING *`;
const res = await this.db.query(sql, params);
return res.rows[0] || null;
}
async deleteSchedule(id: string, userId: string): Promise<void> {
await this.db.query(
`DELETE FROM maintenance_schedules WHERE id = $1 AND user_id = $2`,
[id, userId]
);
}
}