import { Controller, Get, Route, Tags, Post, Body, Path, Delete, Patch, Response, Produces, } from "tsoa"; import { Visitor, CreateVisitorRequest, VisitorWithBooks } from "../models/Visitor"; import { readMyData, writeMyData } from "../utils/fileStorage"; import { wrapResponse, ApiResponse } from "../utils/responseWrapper"; import { ValidationError, NotFoundError } from "../utils/apiErrors"; import { v4 as uuidv4 } from "uuid"; import { VisitorValidator } from "../validators/visitorValidator"; import { visitorLinkManager } from "../models/Visitor"; @Route("visitors") @Tags("Visitors") export class VisitorController extends Controller { private readonly file = "visitors.sea"; @Get("/") public async getVisitors(): Promise> { const visitors = await readMyData(this.file); const enriched = await Promise.all( visitors.map((v) => visitorLinkManager.enrichWithBooks(v)) ); return wrapResponse(enriched); } @Get("/{id}") public async getVisitor(@Path() id: string): Promise> { const visitors = await readMyData(this.file); const visitor = visitors.find((v) => v.id === id); if (!visitor) { throw new NotFoundError(`Visitor with id ${id} not found`); } const enriched = await visitorLinkManager.enrichWithBooks(visitor); return wrapResponse(enriched); } @Post("/") public async addVisitor( @Body() body: CreateVisitorRequest ): Promise> { VisitorValidator.validate(body); const visitors = await readMyData(this.file); const newVisitor: Visitor = { id: uuidv4(), name: body.name.trim(), surname: body.surname.trim(), registrationDate: body.registrationDate, currentBooks: [], history: [], }; visitors.push(newVisitor); await writeMyData(this.file, visitors); return wrapResponse(newVisitor); } @Patch("/{id}") public async updateVisitor( @Path() id: string, @Body() body: Partial ): Promise> { VisitorValidator.validateForUpdate(body); const visitors = await readMyData(this.file); const index = visitors.findIndex((v) => v.id === id); if (index === -1) { throw new NotFoundError(`Visitor with id ${id} not found`); } visitors[index] = { ...visitors[index], ...body }; await writeMyData(this.file, visitors); const enriched = await visitorLinkManager.enrichWithBooks(visitors[index]); return wrapResponse(enriched); } @Delete("/{id}") public async deleteVisitor(@Path() id: string): Promise> { const visitors = await readMyData(this.file); const index = visitors.findIndex((v) => v.id === id); if (index === -1) { throw new NotFoundError(`Visitor with id ${id} not found`); } if ( visitors[index].currentBooks && visitors[index].currentBooks.length > 0 ) { throw new ValidationError( "Cannot delete visitor with unreturned books" ); } visitors.splice(index, 1); await writeMyData(this.file, visitors); return wrapResponse(); } @Get("/{id}/download") @Produces("application/json") @Response(200, "File download") public async downloadVisitor(@Path() id: string): Promise { const visitor = await this.getVisitor(id); return JSON.stringify(visitor.data, null, 2); } }