Modernization Project Complete. Updated to latest versions of frameworks.

This commit is contained in:
Eric Gullickson
2025-08-24 09:49:21 -05:00
parent 673fe7ce91
commit b534e92636
46 changed files with 2341 additions and 5267 deletions

View File

@@ -1,235 +1,206 @@
/**
* @ai-summary HTTP request handlers for vehicles API
* @ai-context Handles validation, auth, and delegates to service layer
* @ai-summary Fastify route handlers for vehicles API
* @ai-context HTTP request/response handling with Fastify reply methods
*/
import { Request, Response, NextFunction } from 'express';
import { FastifyRequest, FastifyReply } from 'fastify';
import { VehiclesService } from '../domain/vehicles.service';
import { VehiclesRepository } from '../data/vehicles.repository';
import pool from '../../../core/config/database';
import { pool } from '../../../core/config/database';
import { logger } from '../../../core/logging/logger';
import { ZodError } from 'zod';
import {
createVehicleSchema,
updateVehicleSchema,
vehicleIdSchema,
CreateVehicleInput,
UpdateVehicleInput,
} from './vehicles.validation';
import { CreateVehicleBody, UpdateVehicleBody, VehicleParams } from '../domain/vehicles.types';
export class VehiclesController {
private service: VehiclesService;
private vehiclesService: VehiclesService;
constructor() {
const repository = new VehiclesRepository(pool);
this.service = new VehiclesService(repository);
this.vehiclesService = new VehiclesService(repository);
}
createVehicle = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
async getUserVehicles(request: FastifyRequest, reply: FastifyReply) {
try {
// Validate request body
const data = createVehicleSchema.parse(req.body) as CreateVehicleInput;
const userId = (request as any).user.sub;
const vehicles = await this.vehiclesService.getUserVehicles(userId);
// Get user ID from JWT token
const userId = req.user?.sub;
if (!userId) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
const vehicle = await this.service.createVehicle(data, userId);
logger.info('Vehicle created successfully', { vehicleId: vehicle.id, userId });
res.status(201).json(vehicle);
} catch (error: any) {
if (error instanceof ZodError) {
res.status(400).json({ error: error.errors[0].message });
return;
}
if (error.message === 'Invalid VIN format' ||
error.message === 'Vehicle with this VIN already exists') {
res.status(400).json({ error: error.message });
return;
}
next(error);
}
};
getUserVehicles = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const userId = req.user?.sub;
if (!userId) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
const vehicles = await this.service.getUserVehicles(userId);
res.json(vehicles);
return reply.code(200).send(vehicles);
} catch (error) {
next(error);
logger.error('Error getting user vehicles', { error, userId: (request as any).user?.sub });
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to get vehicles'
});
}
};
}
getVehicle = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
async createVehicle(request: FastifyRequest<{ Body: CreateVehicleBody }>, reply: FastifyReply) {
try {
const { id } = vehicleIdSchema.parse(req.params);
const userId = req.user?.sub;
const userId = (request as any).user.sub;
const vehicle = await this.vehiclesService.createVehicle(request.body, userId);
if (!userId) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
const vehicle = await this.service.getVehicle(id, userId);
res.json(vehicle);
return reply.code(201).send(vehicle);
} catch (error: any) {
if (error instanceof ZodError) {
res.status(400).json({ error: error.errors[0].message });
return;
}
if (error.message === 'Vehicle not found') {
res.status(404).json({ error: 'Vehicle not found' });
return;
}
if (error.message === 'Unauthorized') {
res.status(403).json({ error: 'Access denied' });
return;
}
next(error);
}
};
updateVehicle = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { id } = vehicleIdSchema.parse(req.params);
const data = updateVehicleSchema.parse(req.body) as UpdateVehicleInput;
const userId = req.user?.sub;
if (!userId) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
const vehicle = await this.service.updateVehicle(id, data, userId);
logger.error('Error creating vehicle', { error, userId: (request as any).user?.sub });
logger.info('Vehicle updated successfully', { vehicleId: id, userId });
res.json(vehicle);
} catch (error: any) {
if (error instanceof ZodError) {
res.status(400).json({ error: error.errors[0].message });
return;
if (error.message === 'Invalid VIN format') {
return reply.code(400).send({
error: 'Bad Request',
message: error.message
});
}
if (error.message === 'Vehicle not found') {
res.status(404).json({ error: 'Vehicle not found' });
return;
}
if (error.message === 'Unauthorized') {
res.status(403).json({ error: 'Access denied' });
return;
}
next(error);
}
};
deleteVehicle = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { id } = vehicleIdSchema.parse(req.params);
const userId = req.user?.sub;
if (!userId) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
await this.service.deleteVehicle(id, userId);
logger.info('Vehicle deleted successfully', { vehicleId: id, userId });
res.status(204).send();
} catch (error: any) {
if (error instanceof ZodError) {
res.status(400).json({ error: error.errors[0].message });
return;
if (error.message === 'Vehicle with this VIN already exists') {
return reply.code(400).send({
error: 'Bad Request',
message: error.message
});
}
if (error.message === 'Vehicle not found') {
res.status(404).json({ error: 'Vehicle not found' });
return;
}
if (error.message === 'Unauthorized') {
res.status(403).json({ error: 'Access denied' });
return;
}
next(error);
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to create vehicle'
});
}
};
}
getDropdownMakes = async (_req: Request, res: Response, next: NextFunction): Promise<void> => {
async getVehicle(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
try {
const makes = await this.service.getDropdownMakes();
res.json(makes);
const userId = (request as any).user.sub;
const { id } = request.params;
const vehicle = await this.vehiclesService.getVehicle(id, userId);
return reply.code(200).send(vehicle);
} catch (error: any) {
if (error.message === 'Failed to load makes') {
res.status(503).json({ error: 'Unable to load makes data' });
return;
logger.error('Error getting vehicle', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });
if (error.message === 'Vehicle not found' || error.message === 'Unauthorized') {
return reply.code(404).send({
error: 'Not Found',
message: 'Vehicle not found'
});
}
next(error);
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to get vehicle'
});
}
};
}
getDropdownModels = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
async updateVehicle(request: FastifyRequest<{ Params: VehicleParams; Body: UpdateVehicleBody }>, reply: FastifyReply) {
try {
const { make } = req.params;
if (!make) {
res.status(400).json({ error: 'Make parameter is required' });
return;
}
const models = await this.service.getDropdownModels(make);
res.json(models);
const userId = (request as any).user.sub;
const { id } = request.params;
const vehicle = await this.vehiclesService.updateVehicle(id, request.body, userId);
return reply.code(200).send(vehicle);
} catch (error: any) {
if (error.message === 'Failed to load models') {
res.status(503).json({ error: 'Unable to load models data' });
return;
logger.error('Error updating vehicle', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });
if (error.message === 'Vehicle not found' || error.message === 'Unauthorized') {
return reply.code(404).send({
error: 'Not Found',
message: 'Vehicle not found'
});
}
next(error);
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to update vehicle'
});
}
};
}
getDropdownTransmissions = async (_req: Request, res: Response, next: NextFunction): Promise<void> => {
async deleteVehicle(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
try {
const transmissions = await this.service.getDropdownTransmissions();
res.json(transmissions);
const userId = (request as any).user.sub;
const { id } = request.params;
await this.vehiclesService.deleteVehicle(id, userId);
return reply.code(204).send();
} catch (error: any) {
if (error.message === 'Failed to load transmissions') {
res.status(503).json({ error: 'Unable to load transmissions data' });
return;
logger.error('Error deleting vehicle', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });
if (error.message === 'Vehicle not found' || error.message === 'Unauthorized') {
return reply.code(404).send({
error: 'Not Found',
message: 'Vehicle not found'
});
}
next(error);
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to delete vehicle'
});
}
};
}
getDropdownEngines = async (_req: Request, res: Response, next: NextFunction): Promise<void> => {
async getDropdownMakes(_request: FastifyRequest, reply: FastifyReply) {
try {
const engines = await this.service.getDropdownEngines();
res.json(engines);
} catch (error: any) {
if (error.message === 'Failed to load engines') {
res.status(503).json({ error: 'Unable to load engines data' });
return;
}
next(error);
const makes = await this.vehiclesService.getDropdownMakes();
return reply.code(200).send(makes);
} catch (error) {
logger.error('Error getting dropdown makes', { error });
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to get makes'
});
}
};
}
getDropdownTrims = async (_req: Request, res: Response, next: NextFunction): Promise<void> => {
async getDropdownModels(request: FastifyRequest<{ Params: { make: string } }>, reply: FastifyReply) {
try {
const trims = await this.service.getDropdownTrims();
res.json(trims);
} catch (error: any) {
if (error.message === 'Failed to load trims') {
res.status(503).json({ error: 'Unable to load trims data' });
return;
}
next(error);
const { make } = request.params;
const models = await this.vehiclesService.getDropdownModels(make);
return reply.code(200).send(models);
} catch (error) {
logger.error('Error getting dropdown models', { error, make: request.params.make });
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to get models'
});
}
};
}
async getDropdownTransmissions(_request: FastifyRequest, reply: FastifyReply) {
try {
const transmissions = await this.vehiclesService.getDropdownTransmissions();
return reply.code(200).send(transmissions);
} catch (error) {
logger.error('Error getting dropdown transmissions', { error });
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to get transmissions'
});
}
}
async getDropdownEngines(_request: FastifyRequest, reply: FastifyReply) {
try {
const engines = await this.vehiclesService.getDropdownEngines();
return reply.code(200).send(engines);
} catch (error) {
logger.error('Error getting dropdown engines', { error });
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to get engines'
});
}
}
async getDropdownTrims(_request: FastifyRequest, reply: FastifyReply) {
try {
const trims = await this.vehiclesService.getDropdownTrims();
return reply.code(200).send(trims);
} catch (error) {
logger.error('Error getting dropdown trims', { error });
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to get trims'
});
}
}
}