Modernization Project Complete. Updated to latest versions of frameworks.
This commit is contained in:
@@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,80 @@
|
||||
/**
|
||||
* @ai-summary Express routes for vehicles API
|
||||
* @ai-context Defines REST endpoints with auth middleware
|
||||
* @ai-summary Fastify routes for vehicles API
|
||||
* @ai-context Route definitions with TypeBox validation and authentication
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import {
|
||||
CreateVehicleBody,
|
||||
UpdateVehicleBody,
|
||||
VehicleParams
|
||||
} from '../domain/vehicles.types';
|
||||
import { VehiclesController } from './vehicles.controller';
|
||||
import { authMiddleware } from '../../../core/security/auth.middleware';
|
||||
|
||||
export function registerVehiclesRoutes(): Router {
|
||||
const router = Router();
|
||||
const controller = new VehiclesController();
|
||||
export const vehiclesRoutes: FastifyPluginAsync = async (
|
||||
fastify: FastifyInstance,
|
||||
_opts: FastifyPluginOptions
|
||||
) => {
|
||||
const vehiclesController = new VehiclesController();
|
||||
|
||||
// Dropdown Data Routes (no auth required for form population)
|
||||
router.get('/api/vehicles/dropdown/makes', controller.getDropdownMakes);
|
||||
router.get('/api/vehicles/dropdown/models/:make', controller.getDropdownModels);
|
||||
router.get('/api/vehicles/dropdown/transmissions', controller.getDropdownTransmissions);
|
||||
router.get('/api/vehicles/dropdown/engines', controller.getDropdownEngines);
|
||||
router.get('/api/vehicles/dropdown/trims', controller.getDropdownTrims);
|
||||
// GET /api/vehicles - Get user's vehicles
|
||||
fastify.get('/vehicles', {
|
||||
preHandler: fastify.authenticate,
|
||||
handler: vehiclesController.getUserVehicles.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// All other vehicle routes require authentication
|
||||
router.use(authMiddleware);
|
||||
// POST /api/vehicles - Create new vehicle
|
||||
fastify.post<{ Body: CreateVehicleBody }>('/vehicles', {
|
||||
preHandler: fastify.authenticate,
|
||||
handler: vehiclesController.createVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// CRUD Routes
|
||||
router.post('/api/vehicles', controller.createVehicle);
|
||||
router.get('/api/vehicles', controller.getUserVehicles);
|
||||
router.get('/api/vehicles/:id', controller.getVehicle);
|
||||
router.put('/api/vehicles/:id', controller.updateVehicle);
|
||||
router.delete('/api/vehicles/:id', controller.deleteVehicle);
|
||||
// GET /api/vehicles/:id - Get specific vehicle
|
||||
fastify.get<{ Params: VehicleParams }>('/vehicles/:id', {
|
||||
preHandler: fastify.authenticate,
|
||||
handler: vehiclesController.getVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
return router;
|
||||
// PUT /api/vehicles/:id - Update vehicle
|
||||
fastify.put<{ Params: VehicleParams; Body: UpdateVehicleBody }>('/vehicles/:id', {
|
||||
preHandler: fastify.authenticate,
|
||||
handler: vehiclesController.updateVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// DELETE /api/vehicles/:id - Delete vehicle
|
||||
fastify.delete<{ Params: VehicleParams }>('/vehicles/:id', {
|
||||
preHandler: fastify.authenticate,
|
||||
handler: vehiclesController.deleteVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/makes - Get vehicle makes
|
||||
fastify.get('/vehicles/dropdown/makes', {
|
||||
handler: vehiclesController.getDropdownMakes.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/models/:make - Get models for make
|
||||
fastify.get<{ Params: { make: string } }>('/vehicles/dropdown/models/:make', {
|
||||
handler: vehiclesController.getDropdownModels.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/transmissions - Get transmission types
|
||||
fastify.get('/vehicles/dropdown/transmissions', {
|
||||
handler: vehiclesController.getDropdownTransmissions.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/engines - Get engine configurations
|
||||
fastify.get('/vehicles/dropdown/engines', {
|
||||
handler: vehiclesController.getDropdownEngines.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/trims - Get trim levels
|
||||
fastify.get('/vehicles/dropdown/trims', {
|
||||
handler: vehiclesController.getDropdownTrims.bind(vehiclesController)
|
||||
});
|
||||
};
|
||||
|
||||
// For backward compatibility during migration
|
||||
export function registerVehiclesRoutes() {
|
||||
throw new Error('registerVehiclesRoutes is deprecated - use vehiclesRoutes Fastify plugin instead');
|
||||
}
|
||||
Reference in New Issue
Block a user