Compare commits
2 Commits
e052b06c83
...
11ab6f66f7
Author | SHA1 | Date | |
---|---|---|---|
11ab6f66f7 | |||
99bd7a88a5 |
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `geometry` on the `TrainTrip` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "TrainTrip" DROP COLUMN "geometry";
|
@ -65,7 +65,6 @@ model TrainTrip {
|
|||||||
departureTime DateTime @db.Timestamptz(3)
|
departureTime DateTime @db.Timestamptz(3)
|
||||||
arrivalTime DateTime @db.Timestamptz(3)
|
arrivalTime DateTime @db.Timestamptz(3)
|
||||||
infoJson Json
|
infoJson Json
|
||||||
geometry String
|
|
||||||
moneyUpdate MoneyUpdate?
|
moneyUpdate MoneyUpdate?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
server/src/common/utils/calculus.utils.ts
Normal file
23
server/src/common/utils/calculus.utils.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const EARTH_RADIUS = 6378137
|
||||||
|
|
||||||
|
type Coordinates = {
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseDistanceTypes = {
|
||||||
|
from: Coordinates
|
||||||
|
to: Coordinates
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toRadians(degrees: number) {
|
||||||
|
return (degrees * Math.PI) / 180
|
||||||
|
}
|
||||||
|
|
||||||
|
export function distanceCoordinates({ from, to }: UseDistanceTypes) {
|
||||||
|
const distance = EARTH_RADIUS * Math.acos(
|
||||||
|
Math.sin(toRadians(to.latitude)) * Math.sin(toRadians(from.latitude)) +
|
||||||
|
Math.cos(toRadians(to.latitude)) * Math.cos(toRadians(from.latitude)) * Math.cos(toRadians(from.longitude) - toRadians(to.longitude)),
|
||||||
|
)
|
||||||
|
return distance
|
||||||
|
}
|
@ -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
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ export class CreateTrainDto {
|
|||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@ApiProperty({ description: "Distance en mètres du trajet, calculé sur https://signal.eu.org/osm/" })
|
@ApiProperty({ description: "Distance estimée en mètres du trajet" })
|
||||||
distance: number
|
distance: number
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -39,8 +39,4 @@ export class CreateTrainDto {
|
|||||||
@IsJSON()
|
@IsJSON()
|
||||||
@ApiProperty({ description: "Informations JSON supplémentaires transmises par Interrail" })
|
@ApiProperty({ description: "Informations JSON supplémentaires transmises par Interrail" })
|
||||||
infoJson: JsonValue
|
infoJson: JsonValue
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@ApiProperty({ description: "Géométrie de la course, obtenue par https://signal.eu.org/osm/" })
|
|
||||||
geometry: 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
|
||||||
|
}
|
@ -13,7 +13,7 @@ export class TrainEntity implements TrainTrip {
|
|||||||
@ApiProperty({ description: "Identifiant de l'utilisateur⋅rice effectuant le trajet" })
|
@ApiProperty({ description: "Identifiant de l'utilisateur⋅rice effectuant le trajet" })
|
||||||
userId: number
|
userId: number
|
||||||
|
|
||||||
@ApiProperty({ description: "Distance en mètres du trajet, calculé sur https://signal.eu.org/osm/" })
|
@ApiProperty({ description: "Distance estimée en mètres du trajet" })
|
||||||
distance: number
|
distance: number
|
||||||
|
|
||||||
@ApiProperty({ description: "Nom de la gare de départ" })
|
@ApiProperty({ description: "Nom de la gare de départ" })
|
||||||
@ -30,7 +30,4 @@ export class TrainEntity implements TrainTrip {
|
|||||||
|
|
||||||
@ApiProperty({ description: "Informations JSON supplémentaires transmises par Interrail" })
|
@ApiProperty({ description: "Informations JSON supplémentaires transmises par Interrail" })
|
||||||
infoJson: JsonValue
|
infoJson: JsonValue
|
||||||
|
|
||||||
@ApiProperty({ description: "Géométrie de la course, obtenue par https://signal.eu.org/osm/" })
|
|
||||||
geometry: string
|
|
||||||
}
|
}
|
||||||
|
@ -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,13 @@
|
|||||||
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 { distanceCoordinates } from 'src/common/utils/calculus.utils'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TrainsService {
|
export class TrainsService {
|
||||||
@ -19,7 +22,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 +44,58 @@ 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 distance = legInfoJson.trainStopStations
|
||||||
|
.map(trainStopStation => trainStopStation.coordinates)
|
||||||
|
.reduce((distance, coordinates, index) => {
|
||||||
|
if (index === 0)
|
||||||
|
return distance
|
||||||
|
const oldCoordinates = legInfoJson.trainStopStations.at(index - 1).coordinates
|
||||||
|
return distance + distanceCoordinates({ from: oldCoordinates, to: coordinates })
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
userId: user.id,
|
||||||
|
distance: distance,
|
||||||
|
from: travel.from,
|
||||||
|
to: travel.to,
|
||||||
|
departureTime: departure,
|
||||||
|
arrivalTime: arrival,
|
||||||
|
infoJson: leg.infoJson,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user