From 92fc2d99b3ed11b9dcd49919b8327241e7209896 Mon Sep 17 00:00:00 2001 From: benoitdemaegdt Date: Sat, 7 Mar 2020 16:14:30 +0100 Subject: [PATCH 1/2] remove trainline connector --- server/package-lock.json | 11 -- server/package.json | 4 +- server/src/core/SncfMobile.ts | 192 -------------------- server/src/core/Trainline.ts | 172 ------------------ server/src/core/{ => connectors}/SncfWeb.ts | 4 +- server/src/core/connectors/Trainline.ts | 172 ------------------ server/src/types.ts | 25 +-- 7 files changed, 4 insertions(+), 576 deletions(-) delete mode 100644 server/src/core/SncfMobile.ts delete mode 100644 server/src/core/Trainline.ts rename server/src/core/{ => connectors}/SncfWeb.ts (98%) delete mode 100644 server/src/core/connectors/Trainline.ts diff --git a/server/package-lock.json b/server/package-lock.json index 7d5e0aa8..c2840a54 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -580,12 +580,6 @@ "integrity": "sha512-XLD/llTSB6EBe3thkN+/I0L+yCTB6sjrcVovQdx2Cnl6N6bTzHmwe/J8mWnsXFgxLrj/emzdv8IR4evKYG2qxQ==", "dev": true }, - "@types/uuid": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.0.tgz", - "integrity": "sha512-RiX1I0lK9WFLFqy2xOxke396f0wKIzk5sAll0tL4J4XDYJXURI7JOs96XQb3nP+2gEpQ/LutBb66jgiT5oQshQ==", - "dev": true - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4000,11 +3994,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "uuid": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.1.tgz", - "integrity": "sha512-yqjRXZzSJm9Dbl84H2VDHpM3zMjzSJQ+hn6C4zqd5ilW+7P4ZmLEEqwho9LjP+tGuZlF4xrHQXT0h9QZUS/pWA==" - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/server/package.json b/server/package.json index c0124103..7004b4c6 100644 --- a/server/package.json +++ b/server/package.json @@ -40,8 +40,7 @@ "moment-timezone": "^0.5.28", "mongodb": "^3.5.3", "node-cron": "^2.0.3", - "nodemailer": "^6.4.2", - "uuid": "^7.0.1" + "nodemailer": "^6.4.2" }, "devDependencies": { "@types/ajv": "^1.0.0", @@ -65,7 +64,6 @@ "@types/node-cron": "^2.0.3", "@types/nodemailer": "^6.4.0", "@types/supertest": "^2.0.8", - "@types/uuid": "^7.0.0", "chai": "^4.2.0", "mocha": "^7.0.1", "nock": "^12.0.2", diff --git a/server/src/core/SncfMobile.ts b/server/src/core/SncfMobile.ts deleted file mode 100644 index d68a821c..00000000 --- a/server/src/core/SncfMobile.ts +++ /dev/null @@ -1,192 +0,0 @@ -import Axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; -import * as httpsProxyAgent from 'https-proxy-agent'; -import { filter, get, isEmpty, isNil, map, random, uniq } from 'lodash'; -import * as moment from 'moment-timezone'; -import Config from '../Config'; -import { IAvailability, ISncfMobileTrain } from '../types'; - -/** - * SncfMobile - * Fetch Tgvmax availabilities from oui.sncf mobile API - */ -export class SncfMobile { - /** - * departure station - */ - private readonly origin: string; - - /** - * destination station - */ - private readonly destination: string; - - /** - * earliest train on which we want to set up an alert - */ - private readonly fromTime: string; - - /** - * latest train on which we want to set up an alert - */ - private readonly toTime: string; - - /** - * TGVmax number - */ - private readonly tgvmaxNumber: string; - - constructor(origin: string, destination: string, fromTime: Date, toTime: Date, tgvmaxNumber: string) { - this.origin = origin; - this.destination = destination; - this.fromTime = moment(fromTime).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - this.toTime = moment(toTime).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - this.tgvmaxNumber = tgvmaxNumber; - } - - /** - * Check if there is a tgvmax seat available on a train : - * - leaving train station : origin - * - going to train station : destination - * - leaving between fromTime and toTime - */ - public async isTgvmaxAvailable(): Promise { - const tgvmaxHours: string[] = await this.getTgvmaxHours(); - /** - * If previous call returns an empty array, there is no TGVmax available - */ - if (isEmpty(tgvmaxHours)) { - return { - isTgvmaxAvailable: false, - hours: [], - }; - } - - return { - isTgvmaxAvailable: true, - hours: uniq(tgvmaxHours), - }; - } - - /** - * Get Train with a TGVmax seat available from Sncf mobile API - */ - private async getTgvmaxHours(): Promise { - - const results: ISncfMobileTrain[] = []; - let keepSearching: boolean = true; - let fromTime: string = this.fromTime; - - try { - while (keepSearching) { - const config: AxiosRequestConfig = { - url: `${Config.baseSncfMobileUrl}/m700/vmd/maq/v3/proposals/train`, - method: 'POST', - headers: { - Accept: 'application/json', - 'User-Agent': 'OUI.sncf/65.1.1 CFNetwork/1107.1 Darwin/19.0.0', - 'Accept-Language': 'fr-FR ', - 'Content-Type': 'application/json;charset=UTF8', - Host: 'wshoraires.oui.sncf', - 'x-vsc-locale': 'fr_FR', - 'X-Device-Type': 'IOS', - }, - data: { - departureTown: { - codes: { - resarail: this.origin, - }, - }, - destinationTown: { - codes: { - resarail: this.destination, - }, - }, - features: [ - 'TRAIN_AND_BUS', - 'DIRECT_TRAVEL', - ], - outwardDate: moment(fromTime).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'), - passengers: [ - { - age: 25, // random - ageRank: 'YOUNG', - birthday: '1995-03-06', // random - commercialCard: { - number: this.tgvmaxNumber, - type: 'HAPPY_CARD', - }, - type: 'HUMAN', - }, - ], - travelClass: 'SECOND', - }, - }; - - /** - * split load between multiple servers - */ - if (process.env.NODE_ENV === 'production' && !isNil(Config.proxyUrl) && random(0, 1) === 0) { - config.httpsAgent = new httpsProxyAgent(Config.proxyUrl); - } - - /** - * interceptor for handling sncf 200 ok that should be 500 or 301 - */ - Axios.interceptors.response.use(async(res: AxiosResponse) => { - const data: {exceptionType?: string} = res.data as {exceptionType?: string}; - if (!isNil(data.exceptionType)) { - return Promise.reject({ - response: { - status: 500, - statusText: data.exceptionType, - }, - }); - } - - return res; - }); - - /** - * get data from oui.sncf - */ - const response: AxiosResponse = await Axios.request(config); - - const pageResults: {journeys: ISncfMobileTrain[]} = response.data as {journeys: ISncfMobileTrain[]}; - const pageJourneys: ISncfMobileTrain[] = pageResults.journeys; - - results.push(...pageJourneys); - - const pageLastTripDeparture: string = moment(pageJourneys[pageJourneys.length - 1].departureDate) - .tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - - if (moment(this.toTime).isSameOrBefore(pageLastTripDeparture) - || moment(fromTime).isSame(pageLastTripDeparture)) { - keepSearching = false; - } - - fromTime = pageLastTripDeparture; - } - } catch (error) { - const status: number = get(error, 'response.status', ''); // tslint:disable-line - const statusText: string = get(error, 'response.statusText', ''); // tslint:disable-line - const label: string = get(error, 'response.data.label', ''); // tslint:disable-line - console.log(`SNCF API ERROR : ${status} ${statusText} ${label}`); // tslint:disable-line - } - - /** - * 1/ filter out trains with no TGVmax seat available - * 2/ filter out trains leaving after toTime - */ - const tgvmaxTravels: ISncfMobileTrain[] = filter(results, (item: ISncfMobileTrain) => { - const departureDate: string = moment(item.departureDate).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - - return isNil(item.unsellableReason) - && item.price.value === 0 - && moment(departureDate).isSameOrBefore(this.toTime); - }); - - return map(tgvmaxTravels, (tgvmaxTravel: ISncfMobileTrain) => { - return moment(tgvmaxTravel.departureDate).tz('Europe/Paris').format('HH:mm'); - }); - } -} diff --git a/server/src/core/Trainline.ts b/server/src/core/Trainline.ts deleted file mode 100644 index 845ea8c7..00000000 --- a/server/src/core/Trainline.ts +++ /dev/null @@ -1,172 +0,0 @@ -import Axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; -import * as httpsProxyAgent from 'https-proxy-agent'; -import { filter, isEmpty, isNil, map, random, uniq } from 'lodash'; -import * as moment from 'moment-timezone'; -import * as uuidv4 from 'uuid/v4'; -import Config from '../Config'; -import { IAvailability, ITrainlineTrain } from '../types'; -/** - * Trainline - * Fetch Tgvmax availabilities from trainline.fr - */ -export class Trainline { - /** - * departure station - */ - private readonly origin: string; - - /** - * destination station - */ - private readonly destination: string; - - /** - * earliest train on which we want to set up an alert - */ - private readonly fromTime: string; - - /** - * latest train on which we want to set up an alert - */ - private readonly toTime: string; - - /** - * TGVmax number - */ - private readonly tgvmaxNumber: string; - - constructor(origin: string, destination: string, fromTime: Date, toTime: Date, tgvmaxNumber: string) { - this.origin = origin; - this.destination = destination; - this.fromTime = moment(fromTime).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - this.toTime = moment(toTime).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - this.tgvmaxNumber = tgvmaxNumber; - } - - /** - * Check if there is a tgvmax seat available on a train : - * - leaving train station : origin - * - going to train station : destination - * - leaving between fromTime and toTime - */ - public async isTgvmaxAvailable(): Promise { - const tgvmaxHours: string[] = await this.getTgvmaxHours(); - /** - * If previous call returns an empty array, there is no TGVmax available - */ - if (isEmpty(tgvmaxHours)) { - return { - isTgvmaxAvailable: false, - hours: [], - }; - } - - return { - isTgvmaxAvailable: true, - hours: uniq(tgvmaxHours), - }; - } - - /** - * Get Train with a TGVmax seat available from Trainline API - */ - private async getTgvmaxHours(): Promise { - - const results: ITrainlineTrain[] = []; - let keepSearching: boolean = true; - let fromTime: string = this.fromTime; - - try { - while (keepSearching) { - const config: AxiosRequestConfig = { - url: `${Config.baseTrainlineUrl}/api/v5_1/search`, - method: 'POST', - headers: { - Accept: 'application/json', - 'User-Agent': 'CaptainTrain/5221(d109181b0) (iPhone8,4; iOS 13.1.2; Scale/2.00)', - 'Accept-Language': 'fr', - 'Content-Type': 'application/json; charset=UTF-8', - Host: 'www.trainline.eu', - }, - data: { - local_currency: 'EUR', - search: { - passengers: [ - { - age: 25, // random - id: uuidv4(), // random uuid - label: uuidv4(), // random uuid - cards: [{ - reference: 'SNCF.HappyCard', - number: this.tgvmaxNumber, - }], - }, - ], - departure_station_id: this.origin, - arrival_station_id: this.destination, - departure_date: this.getTrainlineDate(fromTime), - systems: [ - 'sncf', - ], - }, - }, - }; - - /** - * split load between multiple servers - */ - if (process.env.NODE_ENV === 'production' && !isNil(Config.proxyUrl) && random(0, 1) === 0) { - config.httpsAgent = new httpsProxyAgent(Config.proxyUrl); - } - - /** - * get data from trainline - */ - const response: AxiosResponse = await Axios.request(config); - - const pageResults: {trips: ITrainlineTrain[]} = response.data as {trips: ITrainlineTrain[]}; - const pageTrips: ITrainlineTrain[] = pageResults.trips; - - results.push(...pageTrips); - - const pageLastTripDeparture: string = moment(pageTrips[pageTrips.length - 1].departure_date) - .tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - - if (moment(this.toTime).isSameOrBefore(pageLastTripDeparture) - || moment(fromTime).isSame(pageLastTripDeparture)) { - keepSearching = false; - } - - fromTime = pageLastTripDeparture; - } - } catch (error) { - console.log(`TRAINLINE API ERROR : ${error.response.status} ${error.response.statusText} | ${JSON.stringify(error.response.data.error, null, 2)}`); // tslint:disable-line - } - - /** - * 1/ filter out trains with no TGVmax seat available - * 2/ filter out trains leaving after toTime - */ - const tgvmaxTravels: ITrainlineTrain[] = filter(results, (item: ITrainlineTrain) => { - const departureDate: string = moment(item.departure_date).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - - return item.cents === 0 - && moment(departureDate).isSameOrBefore(this.toTime) - && isNil(item.short_unsellable_reason); - }); - - return map(tgvmaxTravels, (tgvmaxTravel: ITrainlineTrain) => { - return moment(tgvmaxTravel.departure_date).tz('Europe/Paris').format('HH:mm'); - }); - } - - /** - * trainline input date is GMT + current UTC/GMT offset - */ - private readonly getTrainlineDate = (time: string): string => { - const minInHour: number = 60; - const utcOffset: number = moment(new Date()).tz('Europe/Paris').utcOffset() / minInHour; - - return moment(time).add(utcOffset, 'hours').format('YYYY-MM-DD[T]HH:mm:ss'); - } -} diff --git a/server/src/core/SncfWeb.ts b/server/src/core/connectors/SncfWeb.ts similarity index 98% rename from server/src/core/SncfWeb.ts rename to server/src/core/connectors/SncfWeb.ts index e8de5e39..213522cc 100644 --- a/server/src/core/SncfWeb.ts +++ b/server/src/core/connectors/SncfWeb.ts @@ -2,8 +2,8 @@ import Axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import * as httpsProxyAgent from 'https-proxy-agent'; import { filter, get, isEmpty, isNil, map, pick, random } from 'lodash'; import * as moment from 'moment-timezone'; -import Config from '../Config'; -import { IAvailability, ITrain } from '../types'; +import Config from '../../Config'; +import { IAvailability, ITrain } from '../../types'; /** * Tgvmax Travel diff --git a/server/src/core/connectors/Trainline.ts b/server/src/core/connectors/Trainline.ts deleted file mode 100644 index 9224bd06..00000000 --- a/server/src/core/connectors/Trainline.ts +++ /dev/null @@ -1,172 +0,0 @@ -import Axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; -import * as httpsProxyAgent from 'https-proxy-agent'; -import { filter, isEmpty, isNil, map, random, uniq } from 'lodash'; -import * as moment from 'moment-timezone'; -import * as uuidv4 from 'uuid/v4'; -import Config from '../../Config'; -import { IAvailability, ITrainlineTrain } from '../../types'; -/** - * Trainline - * Fetch Tgvmax availabilities from trainline.fr - */ -export class Trainline { - /** - * departure station - */ - private readonly origin: string; - - /** - * destination station - */ - private readonly destination: string; - - /** - * earliest train on which we want to set up an alert - */ - private readonly fromTime: string; - - /** - * latest train on which we want to set up an alert - */ - private readonly toTime: string; - - /** - * TGVmax number - */ - private readonly tgvmaxNumber: string; - - constructor(origin: string, destination: string, fromTime: Date, toTime: Date, tgvmaxNumber: string) { - this.origin = origin; - this.destination = destination; - this.fromTime = moment(fromTime).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - this.toTime = moment(toTime).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - this.tgvmaxNumber = tgvmaxNumber; - } - - /** - * Check if there is a tgvmax seat available on a train : - * - leaving train station : origin - * - going to train station : destination - * - leaving between fromTime and toTime - */ - public async isTgvmaxAvailable(): Promise { - const tgvmaxHours: string[] = await this.getTgvmaxHours(); - /** - * If previous call returns an empty array, there is no TGVmax available - */ - if (isEmpty(tgvmaxHours)) { - return { - isTgvmaxAvailable: false, - hours: [], - }; - } - - return { - isTgvmaxAvailable: true, - hours: uniq(tgvmaxHours), - }; - } - - /** - * Get Train with a TGVmax seat available from Trainline API - */ - private async getTgvmaxHours(): Promise { - - const results: ITrainlineTrain[] = []; - let keepSearching: boolean = true; - let fromTime: string = this.fromTime; - - try { - while (keepSearching) { - const config: AxiosRequestConfig = { - url: `${Config.baseTrainlineUrl}/api/v5_1/search`, - method: 'POST', - headers: { - Accept: 'application/json', - 'User-Agent': 'CaptainTrain/5221(d109181b0) (iPhone8,4; iOS 13.1.2; Scale/2.00)', - 'Accept-Language': 'fr', - 'Content-Type': 'application/json; charset=UTF-8', - Host: 'www.trainline.eu', - }, - data: { - local_currency: 'EUR', - search: { - passengers: [ - { - age: 25, // random - id: uuidv4(), // random uuid - label: uuidv4(), // random uuid - cards: [{ - reference: 'SNCF.HappyCard', - number: this.tgvmaxNumber, - }], - }, - ], - departure_station_id: this.origin, - arrival_station_id: this.destination, - departure_date: this.getTrainlineDate(fromTime), - systems: [ - 'sncf', - ], - }, - }, - }; - - /** - * split load between multiple servers - */ - if (process.env.NODE_ENV === 'production' && !isNil(Config.proxyUrl) && random(0, 1) === 0) { - config.httpsAgent = new httpsProxyAgent(Config.proxyUrl); - } - - /** - * get data from trainline - */ - const response: AxiosResponse = await Axios.request(config); - - const pageResults: {trips: ITrainlineTrain[]} = response.data as {trips: ITrainlineTrain[]}; - const pageTrips: ITrainlineTrain[] = pageResults.trips; - - results.push(...pageTrips); - - const pageLastTripDeparture: string = moment(pageTrips[pageTrips.length - 1].departure_date) - .tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - - if (moment(this.toTime).isSameOrBefore(pageLastTripDeparture) - || moment(fromTime).isSame(pageLastTripDeparture)) { - keepSearching = false; - } - - fromTime = pageLastTripDeparture; - } - } catch (error) { - console.log(`TRAINLINE API ERROR : ${error.response.status} ${error.response.statusText} | ${JSON.stringify(error.response.data.error, null, 2)}`); // tslint:disable-line - } - - /** - * 1/ filter out trains with no TGVmax seat available - * 2/ filter out trains leaving after toTime - */ - const tgvmaxTravels: ITrainlineTrain[] = filter(results, (item: ITrainlineTrain) => { - const departureDate: string = moment(item.departure_date).tz('Europe/Paris').format('YYYY-MM-DD[T]HH:mm:ss'); - - return item.cents === 0 - && moment(departureDate).isSameOrBefore(this.toTime) - && isNil(item.short_unsellable_reason); - }); - - return map(tgvmaxTravels, (tgvmaxTravel: ITrainlineTrain) => { - return moment(tgvmaxTravel.departure_date).tz('Europe/Paris').format('HH:mm'); - }); - } - - /** - * trainline input date is GMT + current UTC/GMT offset - */ - private readonly getTrainlineDate = (time: string): string => { - const minInHour: number = 60; - const utcOffset: number = moment(new Date()).tz('Europe/Paris').utcOffset() / minInHour; - - return moment(time).add(utcOffset, 'hours').format('YYYY-MM-DD[T]HH:mm:ss'); - } -} diff --git a/server/src/types.ts b/server/src/types.ts index 1b49c13a..c7b9f005 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -10,30 +10,7 @@ export interface ITrain { } /** - * Trainline Train interface - */ -export interface ITrainlineTrain { - id?: string; - arrival_date?: string; - arrival_station_id?: string; - departure_date: string; - departure_station_id?: string; - cents?: number; - currency?: string; - local_amount?: { - subunit: number; - subunit_to_unit: number; - }; - local_currency?: string; - digest?: string; - segment_ids?: string[]; - passenger_id?: string; - folder_id?: string; - short_unsellable_reason?: string; -} - -/** - * Trainline Train interface + * SncfMobile Train interface */ export interface ISncfMobileTrain { departureDate: string; From f1e13684aee8fd3888fc4f6f6f1e81657ec1875d Mon Sep 17 00:00:00 2001 From: benoitdemaegdt Date: Sat, 7 Mar 2020 16:15:40 +0100 Subject: [PATCH 2/2] bump app version from 1.10.0 to 1.10.1 --- client/package-lock.json | 2 +- client/package.json | 2 +- server/package-lock.json | 2 +- server/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 7ecf8134..4fbcc50e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "maxplorateur-client", - "version": "1.10.0", + "version": "1.10.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/client/package.json b/client/package.json index dfd30be1..9c54a278 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "maxplorateur-client", - "version": "1.10.0", + "version": "1.10.1", "private": true, "scripts": { "serve": "node_modules/.bin/vue-cli-service serve", diff --git a/server/package-lock.json b/server/package-lock.json index c2840a54..c9af7c66 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,6 +1,6 @@ { "name": "maxplorateur-server", - "version": "1.10.0", + "version": "1.10.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/server/package.json b/server/package.json index 7004b4c6..32fb2485 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "maxplorateur-server", - "version": "1.10.0", + "version": "1.10.1", "description": "find a tgvmax seat", "scripts": { "clean": "rm -fR ./node_modules ./.nyc_output ./coverage ./dist",