Initial commit of edr-passenger-api alpha version

This commit is contained in:
Stephanos A
2026-05-13 16:58:49 +03:00
parent 199a3eba11
commit 39ba561d8f
113 changed files with 3602 additions and 1035 deletions

View File

@@ -0,0 +1,16 @@
import { Body, Controller, Get, Param, Patch, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { LiveService } from './live.service';
import { UpdateLiveStatusDto } from './live.dto';
import { JwtGuard } from '../../common/jwt.guard';
@ApiTags('Live Tracking')
@Controller('live')
export class LiveController {
constructor(private service: LiveService) {}
@Get('trips/:tripId') @ApiOperation({ summary: 'Get live status for a trip' }) getTripLiveStatus(@Param('tripId') id: string) { return this.service.getTripLiveStatus(id); }
@Get('trips/:tripId/stops') @ApiOperation({ summary: 'Get stop timeline for a trip' }) getStopTimeline(@Param('tripId') id: string) { return this.service.getStopTimeline(id); }
@Patch('trips/:tripId/status')@UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Update live trip status (staff/system)' }) updateLiveStatus(@Param('tripId') id: string, @Body() dto: UpdateLiveStatusDto) { return this.service.updateLiveStatus(id, dto); }
@Get('crowd-signals') @ApiOperation({ summary: 'Get station crowd signals' }) getCrowdSignals() { return this.service.getStationCrowdSignals(); }
@Get('weather-alerts') @ApiOperation({ summary: 'Get active weather alerts' }) getWeatherAlerts() { return this.service.getWeatherAlerts(); }
}

View File

@@ -0,0 +1,11 @@
import { IsString, IsOptional, IsInt, Min, Max } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
export class UpdateLiveStatusDto {
@ApiPropertyOptional({ example: 'EN_ROUTE' }) @IsOptional() @IsString() state?: string;
@ApiPropertyOptional({ example: 'Between Dire Dawa and Dewele' }) @IsOptional() @IsString() currentLocationLabel?: string;
@ApiPropertyOptional({ example: 45 }) @IsOptional() @IsInt() @Min(0) @Max(100) progressPercent?: number;
@ApiPropertyOptional({ example: 10 }) @IsOptional() @IsInt() @Min(0) delayMinutes?: number;
@ApiPropertyOptional({ example: 120 }) @IsOptional() @IsInt() @Min(0) currentSpeedKph?: number;
@ApiPropertyOptional({ example: 'Platform 2' }) @IsOptional() @IsString() platformLabel?: string;
}

View File

@@ -0,0 +1,6 @@
import { Module } from '@nestjs/common';
import { LiveController } from './live.controller';
import { LiveService } from './live.service';
@Module({ controllers: [LiveController], providers: [LiveService] })
export class LiveModule {}

View File

@@ -0,0 +1,35 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
@Injectable()
export class LiveService {
constructor(private prisma: PrismaService) {}
async getTripLiveStatus(tripId: string) {
const trip = await this.prisma.trip.findUnique({
where: { id: tripId },
include: { service: true, originStation: true, destinationStation: true, liveStatus: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } },
});
if (!trip) throw new NotFoundException('Trip not found');
const live = trip.liveStatus;
const nextStop = trip.stopTimes.find((s) => s.status === 'UPCOMING' || s.status === 'APPROACHING');
return {
tripId: trip.id, trainName: trip.service.name,
fromStationName: trip.originStation.name, toStationName: trip.destinationStation.name,
state: live?.state ?? trip.status, currentLocationLabel: live?.currentLocationLabel,
progressPercent: live?.progressPercent ?? 0, delayMinutes: live?.delayMinutes ?? 0,
currentSpeedKph: live?.currentSpeedKph, platformLabel: live?.platformLabel,
nextStopStationName: nextStop?.station.name, updatedAt: live?.updatedAt ?? trip.departureAt,
};
}
updateLiveStatus(tripId: string, data: any) {
return this.prisma.tripLiveStatus.upsert({ where: { tripId }, update: data, create: { tripId, state: data.state ?? 'SCHEDULED', ...data } });
}
getStopTimeline(tripId: string) { return this.prisma.tripStopTime.findMany({ where: { tripId }, include: { station: true }, orderBy: { sequence: 'asc' } }); }
getStationCrowdSignals() { return this.prisma.stationCrowdSignal.findMany({ include: { station: true } }); }
getWeatherAlerts() { return this.prisma.weatherAlert.findMany({ where: { validUntil: { gte: new Date() } }, orderBy: { createdAt: 'desc' } }); }
}