Importation des trajets depuis Interrail et signal.eu.org
This commit is contained in:
		@@ -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,
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user