Admin User v1
This commit is contained in:
231
backend/src/features/admin/api/stations.controller.ts
Normal file
231
backend/src/features/admin/api/stations.controller.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* @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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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' });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user