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 { ApiProperty } from "@nestjs/swagger"
|
||||||
import { JsonValue } from "@prisma/client/runtime/library"
|
import { JsonValue } from "@prisma/client/runtime/library"
|
||||||
import { Type } from "class-transformer"
|
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 {
|
export class CreateTrainDto {
|
||||||
@IsString()
|
@IsUUID()
|
||||||
@ApiProperty({ description: "Identifiant du train, donné par l'identifiant de partage Interrail" })
|
@ApiProperty({ description: "Identifiant du train, donné par l'identifiant de partage Interrail" })
|
||||||
id: string
|
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 { TrainsService } from './trains.service'
|
||||||
import { CreateTrainDto } from './dto/create-train.dto'
|
import { CreateTrainDto } from './dto/create-train.dto'
|
||||||
import { UpdateTrainDto } from './dto/update-train.dto'
|
import { UpdateTrainDto } from './dto/update-train.dto'
|
||||||
import { TrainEntity } from './entities/train.entity'
|
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 { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger'
|
||||||
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
|
import { ApiOkResponsePaginated, paginateOutput } from 'src/common/utils/pagination.utils'
|
||||||
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
|
import { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
|
||||||
import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
|
import { PaginateOutputDto } from 'src/common/dto/pagination-output.dto'
|
||||||
|
import { ImportTrainDto } from './dto/import-train.dto'
|
||||||
|
|
||||||
@Controller('trains')
|
@Controller('trains')
|
||||||
export class TrainsController {
|
export class TrainsController {
|
||||||
@ -72,4 +73,16 @@ export class TrainsController {
|
|||||||
async remove(@Param('id') id: string) {
|
async remove(@Param('id') id: string) {
|
||||||
await this.trainsService.remove(id)
|
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 { CreateTrainDto } from './dto/create-train.dto'
|
||||||
import { UpdateTrainDto } from './dto/update-train.dto'
|
import { UpdateTrainDto } from './dto/update-train.dto'
|
||||||
import { PrismaService } from 'src/prisma/prisma.service'
|
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 { QueryPaginationDto } from 'src/common/dto/pagination-query.dto'
|
||||||
import { paginate } from 'src/common/utils/pagination.utils'
|
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()
|
@Injectable()
|
||||||
export class TrainsService {
|
export class TrainsService {
|
||||||
@ -19,7 +23,7 @@ export class TrainsService {
|
|||||||
await this.prisma.trainTrip.findMany({
|
await this.prisma.trainTrip.findMany({
|
||||||
...paginate(queryPagination),
|
...paginate(queryPagination),
|
||||||
}),
|
}),
|
||||||
await this.prisma.challenge.count(),
|
await this.prisma.trainTrip.count(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,4 +45,61 @@ export class TrainsService {
|
|||||||
where: { id },
|
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