Initial Commit
This commit is contained in:
@@ -35,6 +35,18 @@ export class VehiclesController {
|
||||
|
||||
async createVehicle(request: FastifyRequest<{ Body: CreateVehicleBody }>, reply: FastifyReply) {
|
||||
try {
|
||||
// Require either a valid 17-char VIN or a non-empty license plate
|
||||
const vin = request.body?.vin?.trim();
|
||||
const plate = request.body?.licensePlate?.trim();
|
||||
const hasValidVin = !!vin && vin.length === 17;
|
||||
const hasPlate = !!plate && plate.length > 0;
|
||||
if (!hasValidVin && !hasPlate) {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: 'Either a valid 17-character VIN or a license plate is required'
|
||||
});
|
||||
}
|
||||
|
||||
const userId = (request as any).user.sub;
|
||||
const vehicle = await this.vehiclesService.createVehicle(request.body, userId);
|
||||
|
||||
@@ -138,12 +150,20 @@ export class VehiclesController {
|
||||
}
|
||||
}
|
||||
|
||||
async getDropdownMakes(_request: FastifyRequest, reply: FastifyReply) {
|
||||
async getDropdownMakes(request: FastifyRequest<{ Querystring: { year: number } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const makes = await this.vehiclesService.getDropdownMakes();
|
||||
const { year } = request.query;
|
||||
if (!year || year < 1980 || year > new Date().getFullYear() + 1) {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: 'Valid year parameter is required (1980-' + (new Date().getFullYear() + 1) + ')'
|
||||
});
|
||||
}
|
||||
|
||||
const makes = await this.vehiclesService.getDropdownMakes(year);
|
||||
return reply.code(200).send(makes);
|
||||
} catch (error) {
|
||||
logger.error('Error getting dropdown makes', { error });
|
||||
logger.error('Error getting dropdown makes', { error, year: request.query?.year });
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to get makes'
|
||||
@@ -151,13 +171,20 @@ export class VehiclesController {
|
||||
}
|
||||
}
|
||||
|
||||
async getDropdownModels(request: FastifyRequest<{ Params: { make: string } }>, reply: FastifyReply) {
|
||||
async getDropdownModels(request: FastifyRequest<{ Querystring: { year: number; make_id: number } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const { make } = request.params;
|
||||
const models = await this.vehiclesService.getDropdownModels(make);
|
||||
const { year, make_id } = request.query;
|
||||
if (!year || !make_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1) {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: 'Valid year and make_id parameters are required'
|
||||
});
|
||||
}
|
||||
|
||||
const models = await this.vehiclesService.getDropdownModels(year, make_id);
|
||||
return reply.code(200).send(models);
|
||||
} catch (error) {
|
||||
logger.error('Error getting dropdown models', { error, make: request.params.make });
|
||||
logger.error('Error getting dropdown models', { error, year: request.query?.year, make_id: request.query?.make_id });
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to get models'
|
||||
@@ -165,12 +192,20 @@ export class VehiclesController {
|
||||
}
|
||||
}
|
||||
|
||||
async getDropdownTransmissions(_request: FastifyRequest, reply: FastifyReply) {
|
||||
async getDropdownTransmissions(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const transmissions = await this.vehiclesService.getDropdownTransmissions();
|
||||
const { year, make_id, model_id } = request.query;
|
||||
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: 'Valid year, make_id, and model_id parameters are required'
|
||||
});
|
||||
}
|
||||
|
||||
const transmissions = await this.vehiclesService.getDropdownTransmissions(year, make_id, model_id);
|
||||
return reply.code(200).send(transmissions);
|
||||
} catch (error) {
|
||||
logger.error('Error getting dropdown transmissions', { error });
|
||||
logger.error('Error getting dropdown transmissions', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id });
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to get transmissions'
|
||||
@@ -178,12 +213,20 @@ export class VehiclesController {
|
||||
}
|
||||
}
|
||||
|
||||
async getDropdownEngines(_request: FastifyRequest, reply: FastifyReply) {
|
||||
async getDropdownEngines(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number; trim_id: number } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const engines = await this.vehiclesService.getDropdownEngines();
|
||||
const { year, make_id, model_id, trim_id } = request.query;
|
||||
if (!year || !make_id || !model_id || !trim_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1 || trim_id < 1) {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: 'Valid year, make_id, model_id, and trim_id parameters are required'
|
||||
});
|
||||
}
|
||||
|
||||
const engines = await this.vehiclesService.getDropdownEngines(year, make_id, model_id, trim_id);
|
||||
return reply.code(200).send(engines);
|
||||
} catch (error) {
|
||||
logger.error('Error getting dropdown engines', { error });
|
||||
logger.error('Error getting dropdown engines', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id, trim_id: request.query?.trim_id });
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to get engines'
|
||||
@@ -191,16 +234,62 @@ export class VehiclesController {
|
||||
}
|
||||
}
|
||||
|
||||
async getDropdownTrims(_request: FastifyRequest, reply: FastifyReply) {
|
||||
async getDropdownTrims(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const trims = await this.vehiclesService.getDropdownTrims();
|
||||
const { year, make_id, model_id } = request.query;
|
||||
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: 'Valid year, make_id, and model_id parameters are required'
|
||||
});
|
||||
}
|
||||
|
||||
const trims = await this.vehiclesService.getDropdownTrims(year, make_id, model_id);
|
||||
return reply.code(200).send(trims);
|
||||
} catch (error) {
|
||||
logger.error('Error getting dropdown trims', { error });
|
||||
logger.error('Error getting dropdown trims', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id });
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to get trims'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDropdownYears(_request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
// Use platform client through VehiclesService's integration
|
||||
const years = await this.vehiclesService.getDropdownYears();
|
||||
return reply.code(200).send(years);
|
||||
} catch (error) {
|
||||
logger.error('Error getting dropdown years', { error });
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to get years'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async decodeVIN(request: FastifyRequest<{ Body: { vin: string } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const { vin } = request.body;
|
||||
|
||||
if (!vin || vin.length !== 17) {
|
||||
return reply.code(400).send({
|
||||
vin: vin || '',
|
||||
success: false,
|
||||
error: 'VIN must be exactly 17 characters'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await this.vehiclesService.decodeVIN(vin);
|
||||
return reply.code(200).send(result);
|
||||
} catch (error: any) {
|
||||
logger.error('Error decoding VIN', { error, vin: request.body?.vin });
|
||||
return reply.code(500).send({
|
||||
vin: request.body?.vin || '',
|
||||
success: false,
|
||||
error: 'VIN decode failed'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
VehicleParams
|
||||
} from '../domain/vehicles.types';
|
||||
import { VehiclesController } from './vehicles.controller';
|
||||
import { tenantMiddleware } from '../../../core/middleware/tenant';
|
||||
|
||||
export const vehiclesRoutes: FastifyPluginAsync = async (
|
||||
fastify: FastifyInstance,
|
||||
@@ -20,61 +21,80 @@ export const vehiclesRoutes: FastifyPluginAsync = async (
|
||||
|
||||
// GET /api/vehicles - Get user's vehicles
|
||||
fastify.get('/vehicles', {
|
||||
preHandler: fastify.authenticate,
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.getUserVehicles.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// POST /api/vehicles - Create new vehicle
|
||||
fastify.post<{ Body: CreateVehicleBody }>('/vehicles', {
|
||||
preHandler: fastify.authenticate,
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.createVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/:id - Get specific vehicle
|
||||
fastify.get<{ Params: VehicleParams }>('/vehicles/:id', {
|
||||
preHandler: fastify.authenticate,
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.getVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// PUT /api/vehicles/:id - Update vehicle
|
||||
fastify.put<{ Params: VehicleParams; Body: UpdateVehicleBody }>('/vehicles/:id', {
|
||||
preHandler: fastify.authenticate,
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.updateVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// DELETE /api/vehicles/:id - Delete vehicle
|
||||
fastify.delete<{ Params: VehicleParams }>('/vehicles/:id', {
|
||||
preHandler: fastify.authenticate,
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.deleteVehicle.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/makes - Get vehicle makes
|
||||
fastify.get('/vehicles/dropdown/makes', {
|
||||
// Hierarchical Vehicle API - mirrors MVP Platform Vehicles Service structure
|
||||
|
||||
// GET /api/vehicles/dropdown/years - Available model years
|
||||
fastify.get('/vehicles/dropdown/years', {
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.getDropdownYears.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/makes?year=2024 - Get makes for year (Level 1)
|
||||
fastify.get<{ Querystring: { year: number } }>('/vehicles/dropdown/makes', {
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.getDropdownMakes.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/models/:make - Get models for make
|
||||
fastify.get<{ Params: { make: string } }>('/vehicles/dropdown/models/:make', {
|
||||
// GET /api/vehicles/dropdown/models?year=2024&make_id=1 - Get models for year/make (Level 2)
|
||||
fastify.get<{ Querystring: { year: number; make_id: number } }>('/vehicles/dropdown/models', {
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
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/trims?year=2024&make_id=1&model_id=1 - Get trims (Level 3)
|
||||
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number } }>('/vehicles/dropdown/trims', {
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.getDropdownTrims.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/engines - Get engine configurations
|
||||
fastify.get('/vehicles/dropdown/engines', {
|
||||
// GET /api/vehicles/dropdown/engines?year=2024&make_id=1&model_id=1&trim_id=1 - Get engines (Level 4)
|
||||
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number; trim_id: number } }>('/vehicles/dropdown/engines', {
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.getDropdownEngines.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// GET /api/vehicles/dropdown/trims - Get trim levels
|
||||
fastify.get('/vehicles/dropdown/trims', {
|
||||
handler: vehiclesController.getDropdownTrims.bind(vehiclesController)
|
||||
// GET /api/vehicles/dropdown/transmissions?year=2024&make_id=1&model_id=1 - Get transmissions (Level 3)
|
||||
fastify.get<{ Querystring: { year: number; make_id: number; model_id: number } }>('/vehicles/dropdown/transmissions', {
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.getDropdownTransmissions.bind(vehiclesController)
|
||||
});
|
||||
|
||||
// POST /api/vehicles/decode-vin - Decode VIN and return vehicle information
|
||||
fastify.post<{ Body: { vin: string } }>('/vehicles/decode-vin', {
|
||||
preHandler: [fastify.authenticate, tenantMiddleware],
|
||||
handler: vehiclesController.decodeVIN.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