/** * @ai-summary HTTP request handlers for admin station oversight * @ai-context Handles admin operations on global stations and user-saved stations */ import { FastifyRequest, FastifyReply } from 'fastify'; import { StationOversightService } from '../domain/station-oversight.service'; import { logger } from '../../../core/logging/logger'; interface StationListQuery { limit?: string; offset?: string; search?: string; } interface CreateStationBody { placeId: string; name: string; address: string; latitude: number; longitude: number; priceRegular?: number; pricePremium?: number; priceDiesel?: number; rating?: number; photoUrl?: string; } interface UpdateStationBody { name?: string; address?: string; latitude?: number; longitude?: number; priceRegular?: number; pricePremium?: number; priceDiesel?: number; rating?: number; photoUrl?: string; } interface StationParams { stationId: string; } interface UserStationParams { userId: string; stationId: string; } interface DeleteQuery { force?: string; } export class StationsController { constructor(private service: StationOversightService) {} /** * GET /api/admin/stations * List all stations globally with pagination and search */ async listAllStations( request: FastifyRequest<{ Querystring: StationListQuery }>, reply: FastifyReply ): Promise { try { const actorId = request.userContext?.userId; if (!actorId) { return reply.code(401).send({ error: 'Unauthorized' }); } const limit = request.query.limit ? parseInt(request.query.limit, 10) : 100; const offset = request.query.offset ? parseInt(request.query.offset, 10) : 0; const search = request.query.search; const result = await this.service.listAllStations(limit, offset, search); return reply.code(200).send(result); } catch (error) { logger.error('Error listing stations', { error }); return reply.code(500).send({ error: 'Failed to list stations' }); } } /** * POST /api/admin/stations * Create a new station */ async createStation( request: FastifyRequest<{ Body: CreateStationBody }>, reply: FastifyReply ): Promise { try { const actorId = request.userContext?.userId; if (!actorId) { return reply.code(401).send({ error: 'Unauthorized' }); } // Validate required fields const { placeId, name, address, latitude, longitude } = request.body; if (!placeId || !name || !address || latitude === undefined || longitude === undefined) { return reply.code(400).send({ error: 'Missing required fields: placeId, name, address, latitude, longitude' }); } const station = await this.service.createStation(actorId, request.body); return reply.code(201).send(station); } catch (error: any) { logger.error('Error creating station', { error }); if (error.message?.includes('duplicate key')) { return reply.code(409).send({ error: 'Station with this placeId already exists' }); } return reply.code(500).send({ error: 'Failed to create station' }); } } /** * PUT /api/admin/stations/:stationId * Update an existing station */ async updateStation( request: FastifyRequest<{ Params: StationParams; Body: UpdateStationBody }>, reply: FastifyReply ): Promise { try { const actorId = request.userContext?.userId; if (!actorId) { return reply.code(401).send({ error: 'Unauthorized' }); } const { stationId } = request.params; // Validate at least one field to update if (Object.keys(request.body).length === 0) { return reply.code(400).send({ error: 'No fields to update' }); } const station = await this.service.updateStation(actorId, stationId, request.body); return reply.code(200).send(station); } catch (error: any) { logger.error('Error updating station', { error }); if (error.message === 'Station not found') { return reply.code(404).send({ error: 'Station not found' }); } return reply.code(500).send({ error: 'Failed to update station' }); } } /** * DELETE /api/admin/stations/:stationId * Delete a station (soft delete by default, hard delete with ?force=true) */ async deleteStation( request: FastifyRequest<{ Params: StationParams; Querystring: DeleteQuery }>, reply: FastifyReply ): Promise { try { const actorId = request.userContext?.userId; if (!actorId) { return reply.code(401).send({ error: 'Unauthorized' }); } const { stationId } = request.params; const force = request.query.force === 'true'; await this.service.deleteStation(actorId, stationId, force); return reply.code(204).send(); } catch (error: any) { logger.error('Error deleting station', { error }); if (error.message === 'Station not found') { return reply.code(404).send({ error: 'Station not found' }); } return reply.code(500).send({ error: 'Failed to delete station' }); } } /** * GET /api/admin/users/:userId/stations * Get user's saved stations */ async getUserSavedStations( request: FastifyRequest<{ Params: { userId: string } }>, reply: FastifyReply ): Promise { try { const actorId = request.userContext?.userId; if (!actorId) { return reply.code(401).send({ error: 'Unauthorized' }); } const { userId } = request.params; const stations = await this.service.getUserSavedStations(userId); return reply.code(200).send(stations); } catch (error) { logger.error('Error getting user saved stations', { error }); return reply.code(500).send({ error: 'Failed to get user saved stations' }); } } /** * DELETE /api/admin/users/:userId/stations/:stationId * Remove user's saved station (soft delete by default, hard delete with ?force=true) */ async removeUserSavedStation( request: FastifyRequest<{ Params: UserStationParams; Querystring: DeleteQuery }>, reply: FastifyReply ): Promise { try { const actorId = request.userContext?.userId; if (!actorId) { return reply.code(401).send({ error: 'Unauthorized' }); } const { userId, stationId } = request.params; const force = request.query.force === 'true'; await this.service.removeUserSavedStation(actorId, userId, stationId, force); return reply.code(204).send(); } catch (error: any) { logger.error('Error removing user saved station', { error }); if (error.message?.includes('not found')) { return reply.code(404).send({ error: error.message }); } return reply.code(500).send({ error: 'Failed to remove user saved station' }); } } }