Admin User v1
This commit is contained in:
539
backend/src/features/admin/api/catalog.controller.ts
Normal file
539
backend/src/features/admin/api/catalog.controller.ts
Normal file
@@ -0,0 +1,539 @@
|
||||
/**
|
||||
* @ai-summary Catalog API controller for platform vehicle data management
|
||||
* @ai-context Handles HTTP requests for CRUD operations on makes, models, years, trims, engines
|
||||
*/
|
||||
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { VehicleCatalogService } from '../domain/vehicle-catalog.service';
|
||||
import { logger } from '../../../core/logging/logger';
|
||||
|
||||
export class CatalogController {
|
||||
constructor(private catalogService: VehicleCatalogService) {}
|
||||
|
||||
// MAKES ENDPOINTS
|
||||
|
||||
async getMakes(_request: FastifyRequest, reply: FastifyReply): Promise<void> {
|
||||
try {
|
||||
const makes = await this.catalogService.getAllMakes();
|
||||
reply.code(200).send({ makes });
|
||||
} catch (error) {
|
||||
logger.error('Error getting makes', { error });
|
||||
reply.code(500).send({ error: 'Failed to retrieve makes' });
|
||||
}
|
||||
}
|
||||
|
||||
async createMake(
|
||||
request: FastifyRequest<{ Body: { name: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { name } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (!name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Make name is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const make = await this.catalogService.createMake(name.trim(), actorId);
|
||||
reply.code(201).send(make);
|
||||
} catch (error) {
|
||||
logger.error('Error creating make', { error });
|
||||
reply.code(500).send({ error: 'Failed to create make' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateMake(
|
||||
request: FastifyRequest<{ Params: { makeId: string }; Body: { name: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const makeId = parseInt(request.params.makeId);
|
||||
const { name } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(makeId)) {
|
||||
reply.code(400).send({ error: 'Invalid make ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Make name is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const make = await this.catalogService.updateMake(makeId, name.trim(), actorId);
|
||||
reply.code(200).send(make);
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating make', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to update make' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMake(
|
||||
request: FastifyRequest<{ Params: { makeId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const makeId = parseInt(request.params.makeId);
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(makeId)) {
|
||||
reply.code(400).send({ error: 'Invalid make ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.catalogService.deleteMake(makeId, actorId);
|
||||
reply.code(204).send();
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting make', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else if (error.message?.includes('existing models')) {
|
||||
reply.code(409).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to delete make' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MODELS ENDPOINTS
|
||||
|
||||
async getModels(
|
||||
request: FastifyRequest<{ Params: { makeId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const makeId = parseInt(request.params.makeId);
|
||||
|
||||
if (isNaN(makeId)) {
|
||||
reply.code(400).send({ error: 'Invalid make ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const models = await this.catalogService.getModelsByMake(makeId);
|
||||
reply.code(200).send({ models });
|
||||
} catch (error) {
|
||||
logger.error('Error getting models', { error });
|
||||
reply.code(500).send({ error: 'Failed to retrieve models' });
|
||||
}
|
||||
}
|
||||
|
||||
async createModel(
|
||||
request: FastifyRequest<{ Body: { makeId: number; name: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { makeId, name } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (!makeId || !name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Make ID and model name are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const model = await this.catalogService.createModel(makeId, name.trim(), actorId);
|
||||
reply.code(201).send(model);
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating model', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to create model' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateModel(
|
||||
request: FastifyRequest<{ Params: { modelId: string }; Body: { makeId: number; name: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const modelId = parseInt(request.params.modelId);
|
||||
const { makeId, name } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(modelId)) {
|
||||
reply.code(400).send({ error: 'Invalid model ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!makeId || !name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Make ID and model name are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const model = await this.catalogService.updateModel(modelId, makeId, name.trim(), actorId);
|
||||
reply.code(200).send(model);
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating model', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to update model' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteModel(
|
||||
request: FastifyRequest<{ Params: { modelId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const modelId = parseInt(request.params.modelId);
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(modelId)) {
|
||||
reply.code(400).send({ error: 'Invalid model ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.catalogService.deleteModel(modelId, actorId);
|
||||
reply.code(204).send();
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting model', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else if (error.message?.includes('existing years')) {
|
||||
reply.code(409).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to delete model' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// YEARS ENDPOINTS
|
||||
|
||||
async getYears(
|
||||
request: FastifyRequest<{ Params: { modelId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const modelId = parseInt(request.params.modelId);
|
||||
|
||||
if (isNaN(modelId)) {
|
||||
reply.code(400).send({ error: 'Invalid model ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const years = await this.catalogService.getYearsByModel(modelId);
|
||||
reply.code(200).send({ years });
|
||||
} catch (error) {
|
||||
logger.error('Error getting years', { error });
|
||||
reply.code(500).send({ error: 'Failed to retrieve years' });
|
||||
}
|
||||
}
|
||||
|
||||
async createYear(
|
||||
request: FastifyRequest<{ Body: { modelId: number; year: number } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { modelId, year } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (!modelId || !year || year < 1900 || year > 2100) {
|
||||
reply.code(400).send({ error: 'Valid model ID and year (1900-2100) are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const yearData = await this.catalogService.createYear(modelId, year, actorId);
|
||||
reply.code(201).send(yearData);
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating year', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to create year' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateYear(
|
||||
request: FastifyRequest<{ Params: { yearId: string }; Body: { modelId: number; year: number } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const yearId = parseInt(request.params.yearId);
|
||||
const { modelId, year } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(yearId)) {
|
||||
reply.code(400).send({ error: 'Invalid year ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modelId || !year || year < 1900 || year > 2100) {
|
||||
reply.code(400).send({ error: 'Valid model ID and year (1900-2100) are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const yearData = await this.catalogService.updateYear(yearId, modelId, year, actorId);
|
||||
reply.code(200).send(yearData);
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating year', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to update year' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteYear(
|
||||
request: FastifyRequest<{ Params: { yearId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const yearId = parseInt(request.params.yearId);
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(yearId)) {
|
||||
reply.code(400).send({ error: 'Invalid year ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.catalogService.deleteYear(yearId, actorId);
|
||||
reply.code(204).send();
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting year', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else if (error.message?.includes('existing trims')) {
|
||||
reply.code(409).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to delete year' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TRIMS ENDPOINTS
|
||||
|
||||
async getTrims(
|
||||
request: FastifyRequest<{ Params: { yearId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const yearId = parseInt(request.params.yearId);
|
||||
|
||||
if (isNaN(yearId)) {
|
||||
reply.code(400).send({ error: 'Invalid year ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const trims = await this.catalogService.getTrimsByYear(yearId);
|
||||
reply.code(200).send({ trims });
|
||||
} catch (error) {
|
||||
logger.error('Error getting trims', { error });
|
||||
reply.code(500).send({ error: 'Failed to retrieve trims' });
|
||||
}
|
||||
}
|
||||
|
||||
async createTrim(
|
||||
request: FastifyRequest<{ Body: { yearId: number; name: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { yearId, name } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (!yearId || !name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Year ID and trim name are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const trim = await this.catalogService.createTrim(yearId, name.trim(), actorId);
|
||||
reply.code(201).send(trim);
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating trim', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to create trim' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateTrim(
|
||||
request: FastifyRequest<{ Params: { trimId: string }; Body: { yearId: number; name: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const trimId = parseInt(request.params.trimId);
|
||||
const { yearId, name } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(trimId)) {
|
||||
reply.code(400).send({ error: 'Invalid trim ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!yearId || !name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Year ID and trim name are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const trim = await this.catalogService.updateTrim(trimId, yearId, name.trim(), actorId);
|
||||
reply.code(200).send(trim);
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating trim', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to update trim' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTrim(
|
||||
request: FastifyRequest<{ Params: { trimId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const trimId = parseInt(request.params.trimId);
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(trimId)) {
|
||||
reply.code(400).send({ error: 'Invalid trim ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.catalogService.deleteTrim(trimId, actorId);
|
||||
reply.code(204).send();
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting trim', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else if (error.message?.includes('existing engines')) {
|
||||
reply.code(409).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to delete trim' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ENGINES ENDPOINTS
|
||||
|
||||
async getEngines(
|
||||
request: FastifyRequest<{ Params: { trimId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const trimId = parseInt(request.params.trimId);
|
||||
|
||||
if (isNaN(trimId)) {
|
||||
reply.code(400).send({ error: 'Invalid trim ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const engines = await this.catalogService.getEnginesByTrim(trimId);
|
||||
reply.code(200).send({ engines });
|
||||
} catch (error) {
|
||||
logger.error('Error getting engines', { error });
|
||||
reply.code(500).send({ error: 'Failed to retrieve engines' });
|
||||
}
|
||||
}
|
||||
|
||||
async createEngine(
|
||||
request: FastifyRequest<{ Body: { trimId: number; name: string; description?: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { trimId, name, description } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (!trimId || !name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Trim ID and engine name are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const engine = await this.catalogService.createEngine(trimId, name.trim(), description, actorId);
|
||||
reply.code(201).send(engine);
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating engine', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to create engine' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateEngine(
|
||||
request: FastifyRequest<{ Params: { engineId: string }; Body: { trimId: number; name: string; description?: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const engineId = parseInt(request.params.engineId);
|
||||
const { trimId, name, description } = request.body;
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(engineId)) {
|
||||
reply.code(400).send({ error: 'Invalid engine ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trimId || !name || name.trim().length === 0) {
|
||||
reply.code(400).send({ error: 'Trim ID and engine name are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const engine = await this.catalogService.updateEngine(engineId, trimId, name.trim(), description, actorId);
|
||||
reply.code(200).send(engine);
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating engine', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to update engine' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteEngine(
|
||||
request: FastifyRequest<{ Params: { engineId: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const engineId = parseInt(request.params.engineId);
|
||||
const actorId = request.userContext?.userId || 'unknown';
|
||||
|
||||
if (isNaN(engineId)) {
|
||||
reply.code(400).send({ error: 'Invalid engine ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.catalogService.deleteEngine(engineId, actorId);
|
||||
reply.code(204).send();
|
||||
} catch (error: any) {
|
||||
logger.error('Error deleting engine', { error });
|
||||
if (error.message?.includes('not found')) {
|
||||
reply.code(404).send({ error: error.message });
|
||||
} else {
|
||||
reply.code(500).send({ error: 'Failed to delete engine' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHANGE LOG ENDPOINT
|
||||
|
||||
async getChangeLogs(
|
||||
request: FastifyRequest<{ Querystring: { limit?: string; offset?: string } }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
try {
|
||||
const limit = parseInt(request.query.limit || '100');
|
||||
const offset = parseInt(request.query.offset || '0');
|
||||
|
||||
const result = await this.catalogService.getChangeLogs(limit, offset);
|
||||
reply.code(200).send(result);
|
||||
} catch (error) {
|
||||
logger.error('Error getting change logs', { error });
|
||||
reply.code(500).send({ error: 'Failed to retrieve change logs' });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user