Importation des trajets depuis Interrail et signal.eu.org
This commit is contained in:
parent
e052b06c83
commit
99bd7a88a5
@ -1,10 +1,10 @@
|
||||
import { ApiProperty } from "@nestjs/swagger"
|
||||
import { JsonValue } from "@prisma/client/runtime/library"
|
||||
import { Type } from "class-transformer"
|
||||
import { IsDate, IsInt, IsJSON, IsNumber, IsString } from "class-validator"
|
||||
import { IsDate, IsInt, IsJSON, IsNumber, IsString, IsUUID } from "class-validator"
|
||||
|
||||
export class CreateTrainDto {
|
||||
@IsString()
|
||||
@IsUUID()
|
||||
@ApiProperty({ description: "Identifiant du train, donné par l'identifiant de partage Interrail" })
|
||||
id: string
|
||||
|
||||
|
8
server/src/trains/dto/import-train.dto.ts
Normal file
8
server/src/trains/dto/import-train.dto.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from "@nestjs/swagger"
|
||||
import { IsUUID } from "class-validator"
|
||||
|
||||
export class ImportTrainDto {
|
||||
@IsUUID()
|
||||
@ApiProperty({ description: "Identifiant de partage Interrail" })
|
||||
id: string
|
||||
}
|
75
server/src/trains/dto/interrail-api.dto.ts
Normal file
75
server/src/trains/dto/interrail-api.dto.ts
Normal file
@ -0,0 +1,75 @@
|
||||
export interface InterrailLeg {
|
||||
infoJson: string
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
export interface InterrailTravel {
|
||||
date: string
|
||||
infoJson: string
|
||||
from: string
|
||||
to: string
|
||||
type: number
|
||||
legs: InterrailLeg[]
|
||||
}
|
||||
|
||||
export interface InterrailJourneyData {
|
||||
travels: InterrailTravel[]
|
||||
}
|
||||
|
||||
export interface InterrailJourney {
|
||||
data: InterrailJourneyData
|
||||
}
|
||||
|
||||
export interface InterrailTime {
|
||||
hours: number
|
||||
minutes: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
export interface InterrailDate {
|
||||
day: number
|
||||
month: number
|
||||
year: number
|
||||
}
|
||||
|
||||
export interface InterrailTravelInfo {
|
||||
arrivalTime: InterrailTime
|
||||
date: InterrailDate
|
||||
departureTime: InterrailTime
|
||||
haconVersion: number
|
||||
dataSource: number
|
||||
}
|
||||
|
||||
export interface InterrailStopExtraInfo {
|
||||
departureTime: InterrailTime
|
||||
index: number
|
||||
}
|
||||
|
||||
export interface InterrailStopCoordinates {
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
|
||||
export interface InterrailStopStation {
|
||||
coordinates: InterrailStopCoordinates
|
||||
country: string
|
||||
name: string
|
||||
stationId: number
|
||||
}
|
||||
|
||||
export interface InterrailLegInfo {
|
||||
attributeCodes: string[]
|
||||
attributes: object
|
||||
duration: InterrailTime
|
||||
directionStation: string
|
||||
endTime: InterrailTime
|
||||
isSeparateTicket: boolean
|
||||
operationDays: string
|
||||
operator: object
|
||||
dataSource: number
|
||||
startTime: InterrailTime
|
||||
stopExtraInfo: InterrailStopExtraInfo[]
|
||||
trainName: string
|
||||
trainStopStations: InterrailStopStation[]
|
||||
trainType: number
|
||||
}
|
71
server/src/trains/dto/osmr-api.dto.ts
Normal file
71
server/src/trains/dto/osmr-api.dto.ts
Normal file
@ -0,0 +1,71 @@
|
||||
export enum DrivingSide {
|
||||
LEFT = "left",
|
||||
RIGHT = "right",
|
||||
}
|
||||
|
||||
export type OSMRLocation = number[]
|
||||
|
||||
export interface OSMRWaypoint {
|
||||
location: OSMRLocation
|
||||
distance: number
|
||||
hint: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface OSMRIntersection {
|
||||
location: OSMRLocation
|
||||
bearings: number[]
|
||||
entry: boolean[]
|
||||
in?: number
|
||||
out?: number
|
||||
classes: string[]
|
||||
}
|
||||
|
||||
export interface OSMRManeuver {
|
||||
type: string
|
||||
location: OSMRLocation
|
||||
bearing_before: number
|
||||
bearing_after: number
|
||||
}
|
||||
|
||||
export interface OSMRLegStep {
|
||||
distance: number
|
||||
duration: number
|
||||
weight: number
|
||||
driving_side: DrivingSide
|
||||
intersections: OSMRIntersection[]
|
||||
ref: string
|
||||
name: string
|
||||
mode: string
|
||||
maneuver: OSMRManeuver
|
||||
geometry: string
|
||||
}
|
||||
|
||||
export interface OSMRLeg {
|
||||
distance: number
|
||||
steps: OSMRLegStep[]
|
||||
duration: number
|
||||
weight: number
|
||||
summary: string
|
||||
}
|
||||
|
||||
export interface OSMRRoute {
|
||||
distance: number
|
||||
weight_name: SVGStringList
|
||||
legs: OSMRLeg[]
|
||||
duration: number
|
||||
weight: number
|
||||
geometry: string
|
||||
}
|
||||
|
||||
export interface OSMRTrain {
|
||||
waypoints: OSMRWaypoint[]
|
||||
routes: OSMRRoute[]
|
||||
}
|
||||
|
||||
export interface OSMRError {
|
||||
code: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type OSMRResponse = OSMRTrain & OSMRError
|
@ -1,13 +1,14 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, HttpCode, UseGuards, Query, ParseIntPipe, NotFoundException } from '@nestjs/common'
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, HttpCode, UseGuards, Query, ParseIntPipe, NotFoundException, Req } from '@nestjs/common'
|
||||
import { TrainsService } from './trains.service'
|
||||
import { CreateTrainDto } from './dto/create-train.dto'
|
||||
import { UpdateTrainDto } from './dto/update-train.dto'
|
||||
import { TrainEntity } from './entities/train.entity'
|
||||
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'
|
||||
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
|
||||
import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger'
|
||||
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
|
||||
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
|
||||
import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
|
||||
import { ImportTrainDto } from './dto/import-train.dto'
|
||||
|
||||
@Controller('trains')
|
||||
export class TrainsController {
|
||||
@ -72,4 +73,16 @@ export class TrainsController {
|
||||
async remove(@Param('id') id: string) {
|
||||
await this.trainsService.remove(id)
|
||||
}
|
||||
|
||||
@Post("/import")
|
||||
@HttpCode(201)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiCreatedResponse({ type: TrainEntity, description: "Train importé avec succès" })
|
||||
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
|
||||
@ApiForbiddenResponse({ description: "Permission refusée" })
|
||||
async import(@Req() request: AuthenticatedRequest, @Body() importTrainDto: ImportTrainDto): Promise<TrainEntity> {
|
||||
const train = await this.trainsService.import(request.user, importTrainDto)
|
||||
return new TrainEntity(train)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Injectable, NotAcceptableException } from '@nestjs/common'
|
||||
import { CreateTrainDto } from './dto/create-train.dto'
|
||||
import { UpdateTrainDto } from './dto/update-train.dto'
|
||||
import { PrismaService } from 'src/prisma/prisma.service'
|
||||
import { TrainTrip } from '@prisma/client'
|
||||
import { TrainTrip, User } from '@prisma/client'
|
||||
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
|
||||
import { paginate } from 'src/common/utils/pagination.utils'
|
||||
import { ImportTrainDto } from './dto/import-train.dto'
|
||||
import { InterrailJourney, InterrailLegInfo, InterrailTravelInfo } from './dto/interrail-api.dto'
|
||||
import { JsonObject } from '@prisma/client/runtime/library'
|
||||
import { OSMRResponse } from './dto/osmr-api.dto'
|
||||
|
||||
@Injectable()
|
||||
export class TrainsService {
|
||||
@ -19,7 +23,7 @@ export class TrainsService {
|
||||
await this.prisma.trainTrip.findMany({
|
||||
...paginate(queryPagination),
|
||||
}),
|
||||
await this.prisma.challenge.count(),
|
||||
await this.prisma.trainTrip.count(),
|
||||
]
|
||||
}
|
||||
|
||||
@ -41,4 +45,61 @@ export class TrainsService {
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
|
||||
async import(user: User, { id: trainId }: ImportTrainDto): Promise<TrainTrip> {
|
||||
const interrailResult: InterrailJourney = await fetch(`https://3uiwjsimnh.execute-api.eu-central-1.amazonaws.com/Prod/journey-import?id=${trainId}`)
|
||||
.then(data => data.json())
|
||||
if (interrailResult.data.travels.length !== 1)
|
||||
throw new NotAcceptableException(`Ce voyage contient ${interrailResult.data.travels.length} trajets. Merci d'ajouter les trajets un à un.`)
|
||||
const travel = interrailResult.data.travels[0]
|
||||
if (travel.legs.length !== 1)
|
||||
throw new NotAcceptableException(`Ce trajet contient ${travel.legs.length} trains. Merci d'ajouter les trajets un à un.`)
|
||||
const leg = travel.legs[0]
|
||||
|
||||
const travelInfoJson: InterrailTravelInfo = JSON.parse(travel.infoJson)
|
||||
const departure = new Date(`${travelInfoJson.date.year}-${travelInfoJson.date.month.toString().padStart(2, "0")}-${travelInfoJson.date.day.toString().padStart(2, "0")}` +
|
||||
`T${travelInfoJson.departureTime.hours.toString().padStart(2, "0")}:${travelInfoJson.departureTime.minutes.toString().padStart(2, "0")}:00+0100`)
|
||||
departure.setDate(departure.getDate() + travelInfoJson.departureTime.offset)
|
||||
const arrival = new Date(`${travelInfoJson.date.year}-${travelInfoJson.date.month.toString().padStart(2, "0")}-${travelInfoJson.date.day.toString().padStart(2, "0")}` +
|
||||
`T${travelInfoJson.arrivalTime.hours.toString().padStart(2, "0")}:${travelInfoJson.arrivalTime.minutes.toString().padStart(2, "0")}:00+0100`)
|
||||
arrival.setDate(arrival.getDate() + travelInfoJson.arrivalTime.offset)
|
||||
|
||||
const legInfoJson: InterrailLegInfo = JSON.parse(leg.infoJson)
|
||||
const coordinatesString = legInfoJson.trainStopStations.map(trainStopStation => {
|
||||
return `${trainStopStation.coordinates.longitude},${trainStopStation.coordinates.latitude}`
|
||||
}).join(';')
|
||||
const osmrResult: OSMRResponse = await fetch(`https://signal.eu.org/osm/eu/route/v1/train/${coordinatesString}?overview=full&steps=true&exclude=highspeed`).then(result => result.json())
|
||||
if (osmrResult?.code === "NoRoute")
|
||||
throw new NotAcceptableException("Aucune route n'a été trouvée avec https://signal.eu.org/osm/")
|
||||
const route = osmrResult.routes[0]
|
||||
const distance = route.distance
|
||||
const geometry = route.geometry
|
||||
|
||||
return this.prisma.trainTrip.upsert({
|
||||
where: {
|
||||
id: trainId,
|
||||
},
|
||||
create: {
|
||||
id: trainId,
|
||||
userId: user.id,
|
||||
distance: distance,
|
||||
from: travel.from,
|
||||
to: travel.to,
|
||||
departureTime: departure,
|
||||
arrivalTime: arrival,
|
||||
infoJson: leg.infoJson,
|
||||
geometry: geometry,
|
||||
},
|
||||
update: {
|
||||
userId: user.id,
|
||||
distance: distance,
|
||||
from: travel.from,
|
||||
to: travel.to,
|
||||
departureTime: departure,
|
||||
arrivalTime: arrival,
|
||||
infoJson: leg.infoJson,
|
||||
geometry: geometry,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user