import { Controller, Get, Post, Patch, Delete, Body, Param, Query, UseGuards, NotFoundException, ForbiddenException, } from '@nestjs/common'; import { GpsService } from './gps.service'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../auth/guards/roles.guard'; import { Roles } from '../auth/decorators/roles.decorator'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import { Role } from '@prisma/client'; import { EnrollDriverDto, ConfirmConsentDto } from './dto/enroll-driver.dto'; import { UpdateGpsSettingsDto } from './dto/update-gps-settings.dto'; import { PrismaService } from '../prisma/prisma.service'; @Controller('gps') @UseGuards(JwtAuthGuard, RolesGuard) export class GpsController { constructor( private readonly gpsService: GpsService, private readonly prisma: PrismaService, ) {} // ============================================ // Admin-only endpoints // ============================================ /** * Get GPS system status */ @Get('status') @Roles(Role.ADMINISTRATOR) async getStatus() { return this.gpsService.getStatus(); } /** * Get GPS settings */ @Get('settings') @Roles(Role.ADMINISTRATOR) async getSettings() { const settings = await this.gpsService.getSettings(); // Don't return the password return { ...settings, traccarAdminPassword: settings.traccarAdminPassword ? '********' : null, }; } /** * Update GPS settings */ @Patch('settings') @Roles(Role.ADMINISTRATOR) async updateSettings(@Body() dto: UpdateGpsSettingsDto) { const settings = await this.gpsService.updateSettings(dto); return { ...settings, traccarAdminPassword: settings.traccarAdminPassword ? '********' : null, }; } /** * Get all enrolled devices */ @Get('devices') @Roles(Role.ADMINISTRATOR) async getEnrolledDevices() { return this.gpsService.getEnrolledDevices(); } /** * Get QR code info for an enrolled device */ @Get('devices/:driverId/qr') @Roles(Role.ADMINISTRATOR) async getDeviceQr(@Param('driverId') driverId: string) { return this.gpsService.getDeviceQrInfo(driverId); } /** * Enroll a driver for GPS tracking */ @Post('enroll/:driverId') @Roles(Role.ADMINISTRATOR) async enrollDriver( @Param('driverId') driverId: string, @Body() dto: EnrollDriverDto, ) { return this.gpsService.enrollDriver(driverId, dto.sendSignalMessage ?? true); } /** * Unenroll a driver from GPS tracking */ @Delete('devices/:driverId') @Roles(Role.ADMINISTRATOR) async unenrollDriver(@Param('driverId') driverId: string) { return this.gpsService.unenrollDriver(driverId); } /** * Get all active driver locations (Admin map view) */ @Get('locations') @Roles(Role.ADMINISTRATOR) async getActiveDriverLocations() { return this.gpsService.getActiveDriverLocations(); } /** * Get a specific driver's location */ @Get('locations/:driverId') @Roles(Role.ADMINISTRATOR) async getDriverLocation(@Param('driverId') driverId: string) { const location = await this.gpsService.getDriverLocation(driverId); if (!location) { throw new NotFoundException('Driver not found or not enrolled for GPS tracking'); } return location; } /** * Get a driver's location history (for route trail display) * Query param 'matched=true' returns OSRM road-snapped route */ @Get('locations/:driverId/history') @Roles(Role.ADMINISTRATOR) async getDriverLocationHistory( @Param('driverId') driverId: string, @Query('from') fromStr?: string, @Query('to') toStr?: string, @Query('matched') matched?: string, ) { const from = fromStr ? new Date(fromStr) : undefined; const to = toStr ? new Date(toStr) : undefined; // If matched=true, return OSRM road-matched route if (matched === 'true') { return this.gpsService.getMatchedRoute(driverId, from, to); } // Otherwise return raw GPS points return this.gpsService.getDriverLocationHistory(driverId, from, to); } /** * Get a driver's stats (Admin viewing any driver) */ @Get('stats/:driverId') @Roles(Role.ADMINISTRATOR) async getDriverStats( @Param('driverId') driverId: string, @Query('from') fromStr?: string, @Query('to') toStr?: string, ) { const from = fromStr ? new Date(fromStr) : undefined; const to = toStr ? new Date(toStr) : undefined; return this.gpsService.getDriverStats(driverId, from, to); } // ============================================ // Traccar Admin Access // ============================================ /** * Check Traccar setup status */ @Get('traccar/status') @Roles(Role.ADMINISTRATOR) async getTraccarSetupStatus() { return this.gpsService.checkTraccarSetup(); } /** * Perform initial Traccar setup */ @Post('traccar/setup') @Roles(Role.ADMINISTRATOR) async performTraccarSetup(@CurrentUser() user: any) { const success = await this.gpsService.performTraccarSetup(user.email); if (!success) { throw new NotFoundException('Failed to setup Traccar. It may already be configured.'); } return { success: true, message: 'Traccar setup complete' }; } /** * Sync all VIP admins to Traccar */ @Post('traccar/sync-admins') @Roles(Role.ADMINISTRATOR) async syncAdminsToTraccar() { return this.gpsService.syncAllAdminsToTraccar(); } /** * Get Traccar admin URL (auto-login for current user) */ @Get('traccar/admin-url') @Roles(Role.ADMINISTRATOR) async getTraccarAdminUrl(@CurrentUser() user: any) { // Get full user from database const fullUser = await this.prisma.user.findUnique({ where: { id: user.id }, }); if (!fullUser) { throw new NotFoundException('User not found'); } return this.gpsService.getTraccarAutoLoginUrl(fullUser); } /** * Get Traccar session for iframe/proxy access */ @Get('traccar/session') @Roles(Role.ADMINISTRATOR) async getTraccarSession(@CurrentUser() user: any) { const fullUser = await this.prisma.user.findUnique({ where: { id: user.id }, }); if (!fullUser) { throw new NotFoundException('User not found'); } const session = await this.gpsService.getTraccarSessionForUser(fullUser); if (!session) { throw new NotFoundException('Could not create Traccar session'); } return { session }; } // ============================================ // Driver self-service endpoints // ============================================ /** * Get my GPS enrollment status */ @Get('me') @Roles(Role.DRIVER, Role.ADMINISTRATOR, Role.COORDINATOR) async getMyGpsStatus(@CurrentUser() user: any) { // Find driver linked to this user const driver = await this.prisma.driver.findFirst({ where: { userId: user.id, deletedAt: null, }, include: { gpsDevice: true, }, }); if (!driver) { return { enrolled: false, message: 'No driver profile linked to your account' }; } if (!driver.gpsDevice) { return { enrolled: false, driverId: driver.id }; } return { enrolled: true, driverId: driver.id, deviceIdentifier: driver.gpsDevice.deviceIdentifier, consentGiven: driver.gpsDevice.consentGiven, consentGivenAt: driver.gpsDevice.consentGivenAt, isActive: driver.gpsDevice.isActive, lastActive: driver.gpsDevice.lastActive, }; } /** * Confirm GPS tracking consent (Driver accepting tracking) */ @Post('me/consent') @Roles(Role.DRIVER) async confirmMyConsent( @CurrentUser() user: any, @Body() dto: ConfirmConsentDto, ) { const driver = await this.prisma.driver.findFirst({ where: { userId: user.id, deletedAt: null, }, }); if (!driver) { throw new NotFoundException('No driver profile linked to your account'); } await this.gpsService.confirmConsent(driver.id, dto.consentGiven); return { success: true, message: dto.consentGiven ? 'GPS tracking consent confirmed. Your location will be tracked during shift hours.' : 'GPS tracking consent revoked. Your location will not be tracked.', }; } /** * Get my GPS stats (Driver viewing own stats) */ @Get('me/stats') @Roles(Role.DRIVER, Role.ADMINISTRATOR, Role.COORDINATOR) async getMyStats( @CurrentUser() user: any, @Query('from') fromStr?: string, @Query('to') toStr?: string, ) { const driver = await this.prisma.driver.findFirst({ where: { userId: user.id, deletedAt: null, }, }); if (!driver) { throw new NotFoundException('No driver profile linked to your account'); } const from = fromStr ? new Date(fromStr) : undefined; const to = toStr ? new Date(toStr) : undefined; return this.gpsService.getDriverStats(driver.id, from, to); } /** * Get my current location */ @Get('me/location') @Roles(Role.DRIVER, Role.ADMINISTRATOR, Role.COORDINATOR) async getMyLocation(@CurrentUser() user: any) { const driver = await this.prisma.driver.findFirst({ where: { userId: user.id, deletedAt: null, }, }); if (!driver) { throw new NotFoundException('No driver profile linked to your account'); } const location = await this.gpsService.getDriverLocation(driver.id); if (!location) { throw new NotFoundException('You are not enrolled for GPS tracking'); } return location; } }