feat: Add user data import feature (Fixes #26) #27
@@ -90,6 +90,64 @@ export class DocumentsRepository {
|
||||
return res.rows.map(row => this.mapDocumentRecord(row));
|
||||
}
|
||||
|
||||
async batchInsert(
|
||||
documents: Array<{
|
||||
id: string;
|
||||
userId: string;
|
||||
vehicleId: string;
|
||||
documentType: DocumentType;
|
||||
title: string;
|
||||
notes?: string | null;
|
||||
details?: any;
|
||||
issuedDate?: string | null;
|
||||
expirationDate?: string | null;
|
||||
emailNotifications?: boolean;
|
||||
scanForMaintenance?: boolean;
|
||||
}>,
|
||||
client?: any
|
||||
): Promise<DocumentRecord[]> {
|
||||
if (documents.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Multi-value INSERT for performance (avoids N round-trips)
|
||||
const queryClient = client || this.db;
|
||||
const placeholders: string[] = [];
|
||||
const values: any[] = [];
|
||||
let paramCount = 1;
|
||||
|
||||
documents.forEach((doc) => {
|
||||
const docParams = [
|
||||
doc.id,
|
||||
doc.userId,
|
||||
doc.vehicleId,
|
||||
doc.documentType,
|
||||
doc.title,
|
||||
doc.notes ?? null,
|
||||
doc.details ?? null,
|
||||
doc.issuedDate ?? null,
|
||||
doc.expirationDate ?? null,
|
||||
doc.emailNotifications ?? false,
|
||||
doc.scanForMaintenance ?? false
|
||||
];
|
||||
|
||||
const placeholder = `($${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++})`;
|
||||
placeholders.push(placeholder);
|
||||
values.push(...docParams);
|
||||
});
|
||||
|
||||
const query = `
|
||||
INSERT INTO documents (
|
||||
id, user_id, vehicle_id, document_type, title, notes, details, issued_date, expiration_date, email_notifications, scan_for_maintenance
|
||||
)
|
||||
VALUES ${placeholders.join(', ')}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await queryClient.query(query, values);
|
||||
return result.rows.map((row: any) => this.mapDocumentRecord(row));
|
||||
}
|
||||
|
||||
async softDelete(id: string, userId: string): Promise<void> {
|
||||
await this.db.query(`UPDATE documents SET deleted_at = NOW() WHERE id = $1 AND user_id = $2`, [id, userId]);
|
||||
}
|
||||
|
||||
@@ -148,6 +148,52 @@ export class FuelLogsRepository {
|
||||
return this.mapRow(result.rows[0]);
|
||||
}
|
||||
|
||||
async batchInsert(
|
||||
logs: Array<CreateFuelLogRequest & { userId: string }>,
|
||||
client?: any
|
||||
): Promise<FuelLog[]> {
|
||||
if (logs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Multi-value INSERT for performance (avoids N round-trips)
|
||||
const queryClient = client || this.pool;
|
||||
const placeholders: string[] = [];
|
||||
const values: any[] = [];
|
||||
let paramCount = 1;
|
||||
|
||||
logs.forEach((log) => {
|
||||
const logParams = [
|
||||
log.userId,
|
||||
log.vehicleId,
|
||||
log.date,
|
||||
log.odometer,
|
||||
log.gallons,
|
||||
log.pricePerGallon,
|
||||
log.totalCost,
|
||||
log.station,
|
||||
log.location,
|
||||
log.notes
|
||||
];
|
||||
|
||||
const placeholder = `($${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++})`;
|
||||
placeholders.push(placeholder);
|
||||
values.push(...logParams);
|
||||
});
|
||||
|
||||
const query = `
|
||||
INSERT INTO fuel_logs (
|
||||
user_id, vehicle_id, date, odometer, gallons,
|
||||
price_per_gallon, total_cost, station, location, notes
|
||||
)
|
||||
VALUES ${placeholders.join(', ')}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await queryClient.query(query, values);
|
||||
return result.rows.map((row: any) => this.mapRow(row));
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<boolean> {
|
||||
const query = 'DELETE FROM fuel_logs WHERE id = $1';
|
||||
const result = await this.pool.query(query, [id]);
|
||||
|
||||
@@ -172,6 +172,62 @@ export class MaintenanceRepository {
|
||||
return res.rows[0] ? this.mapMaintenanceRecord(res.rows[0]) : null;
|
||||
}
|
||||
|
||||
async batchInsertRecords(
|
||||
records: Array<{
|
||||
id: string;
|
||||
userId: string;
|
||||
vehicleId: string;
|
||||
category: MaintenanceCategory;
|
||||
subtypes: string[];
|
||||
date: string;
|
||||
odometerReading?: number | null;
|
||||
cost?: number | null;
|
||||
shopName?: string | null;
|
||||
notes?: string | null;
|
||||
}>,
|
||||
client?: any
|
||||
): Promise<MaintenanceRecord[]> {
|
||||
if (records.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Multi-value INSERT for performance (avoids N round-trips)
|
||||
const queryClient = client || this.db;
|
||||
const placeholders: string[] = [];
|
||||
const values: any[] = [];
|
||||
let paramCount = 1;
|
||||
|
||||
records.forEach((record) => {
|
||||
const recordParams = [
|
||||
record.id,
|
||||
record.userId,
|
||||
record.vehicleId,
|
||||
record.category,
|
||||
record.subtypes,
|
||||
record.date,
|
||||
record.odometerReading ?? null,
|
||||
record.cost ?? null,
|
||||
record.shopName ?? null,
|
||||
record.notes ?? null
|
||||
];
|
||||
|
||||
const placeholder = `($${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}::text[], $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++})`;
|
||||
placeholders.push(placeholder);
|
||||
values.push(...recordParams);
|
||||
});
|
||||
|
||||
const query = `
|
||||
INSERT INTO maintenance_records (
|
||||
id, user_id, vehicle_id, category, subtypes, date, odometer_reading, cost, shop_name, notes
|
||||
)
|
||||
VALUES ${placeholders.join(', ')}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await queryClient.query(query, values);
|
||||
return result.rows.map((row: any) => this.mapMaintenanceRecord(row));
|
||||
}
|
||||
|
||||
async deleteRecord(id: string, userId: string): Promise<void> {
|
||||
await this.db.query(
|
||||
`DELETE FROM maintenance_records WHERE id = $1 AND user_id = $2`,
|
||||
@@ -336,6 +392,80 @@ export class MaintenanceRepository {
|
||||
return res.rows[0] ? this.mapMaintenanceSchedule(res.rows[0]) : null;
|
||||
}
|
||||
|
||||
async batchInsertSchedules(
|
||||
schedules: Array<{
|
||||
id: string;
|
||||
userId: string;
|
||||
vehicleId: string;
|
||||
category: MaintenanceCategory;
|
||||
subtypes: string[];
|
||||
intervalMonths?: number | null;
|
||||
intervalMiles?: number | null;
|
||||
lastServiceDate?: string | null;
|
||||
lastServiceMileage?: number | null;
|
||||
nextDueDate?: string | null;
|
||||
nextDueMileage?: number | null;
|
||||
isActive: boolean;
|
||||
emailNotifications?: boolean;
|
||||
scheduleType?: string;
|
||||
fixedDueDate?: string | null;
|
||||
reminderDays1?: number | null;
|
||||
reminderDays2?: number | null;
|
||||
reminderDays3?: number | null;
|
||||
}>,
|
||||
client?: any
|
||||
): Promise<MaintenanceSchedule[]> {
|
||||
if (schedules.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Multi-value INSERT for performance (avoids N round-trips)
|
||||
const queryClient = client || this.db;
|
||||
const placeholders: string[] = [];
|
||||
const values: any[] = [];
|
||||
let paramCount = 1;
|
||||
|
||||
schedules.forEach((schedule) => {
|
||||
const scheduleParams = [
|
||||
schedule.id,
|
||||
schedule.userId,
|
||||
schedule.vehicleId,
|
||||
schedule.category,
|
||||
schedule.subtypes,
|
||||
schedule.intervalMonths ?? null,
|
||||
schedule.intervalMiles ?? null,
|
||||
schedule.lastServiceDate ?? null,
|
||||
schedule.lastServiceMileage ?? null,
|
||||
schedule.nextDueDate ?? null,
|
||||
schedule.nextDueMileage ?? null,
|
||||
schedule.isActive,
|
||||
schedule.emailNotifications ?? false,
|
||||
schedule.scheduleType ?? 'interval',
|
||||
schedule.fixedDueDate ?? null,
|
||||
schedule.reminderDays1 ?? null,
|
||||
schedule.reminderDays2 ?? null,
|
||||
schedule.reminderDays3 ?? null
|
||||
];
|
||||
|
||||
const placeholder = `($${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}::text[], $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++})`;
|
||||
placeholders.push(placeholder);
|
||||
values.push(...scheduleParams);
|
||||
});
|
||||
|
||||
const 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, email_notifications,
|
||||
schedule_type, fixed_due_date, reminder_days_1, reminder_days_2, reminder_days_3
|
||||
)
|
||||
VALUES ${placeholders.join(', ')}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await queryClient.query(query, values);
|
||||
return result.rows.map((row: any) => this.mapMaintenanceSchedule(row));
|
||||
}
|
||||
|
||||
async deleteSchedule(id: string, userId: string): Promise<void> {
|
||||
await this.db.query(
|
||||
`DELETE FROM maintenance_schedules WHERE id = $1 AND user_id = $2`,
|
||||
|
||||
@@ -164,6 +164,57 @@ export class VehiclesRepository {
|
||||
return this.mapRow(result.rows[0]);
|
||||
}
|
||||
|
||||
async batchInsert(
|
||||
vehicles: Array<CreateVehicleRequest & { userId: string, make?: string, model?: string, year?: number }>,
|
||||
client?: any
|
||||
): Promise<Vehicle[]> {
|
||||
if (vehicles.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Multi-value INSERT for performance (avoids N round-trips)
|
||||
const queryClient = client || this.pool;
|
||||
const placeholders: string[] = [];
|
||||
const values: any[] = [];
|
||||
let paramCount = 1;
|
||||
|
||||
vehicles.forEach((vehicle) => {
|
||||
const vehicleParams = [
|
||||
vehicle.userId,
|
||||
(vehicle.vin && vehicle.vin.trim().length > 0) ? vehicle.vin.trim() : null,
|
||||
vehicle.make,
|
||||
vehicle.model,
|
||||
vehicle.year,
|
||||
vehicle.engine,
|
||||
vehicle.transmission,
|
||||
vehicle.trimLevel,
|
||||
vehicle.driveType,
|
||||
vehicle.fuelType,
|
||||
vehicle.nickname,
|
||||
vehicle.color,
|
||||
vehicle.licensePlate,
|
||||
vehicle.odometerReading || 0
|
||||
];
|
||||
|
||||
const placeholder = `($${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++}, $${paramCount++})`;
|
||||
placeholders.push(placeholder);
|
||||
values.push(...vehicleParams);
|
||||
});
|
||||
|
||||
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 ${placeholders.join(', ')}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await queryClient.query(query, values);
|
||||
return result.rows.map((row: any) => this.mapRow(row));
|
||||
}
|
||||
|
||||
async softDelete(id: string): Promise<boolean> {
|
||||
const query = `
|
||||
UPDATE vehicles
|
||||
|
||||
@@ -12,9 +12,8 @@ import fastifyPlugin from 'fastify-plugin';
|
||||
|
||||
// Mock auth plugin to bypass JWT validation in tests
|
||||
jest.mock('../../../../core/plugins/auth.plugin', () => {
|
||||
const fp = require('fastify-plugin');
|
||||
return {
|
||||
default: fp(async function(fastify: any) {
|
||||
default: fastifyPlugin(async function(fastify: any) {
|
||||
fastify.decorate('authenticate', async function(request: any, _reply: any) {
|
||||
request.user = { sub: 'test-user-123' };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user