TimetableAPI-Upgrade (#64)

Partial implementation of: #47

The 2022.2.1 release currently live is based off of the TimetableAPI-Upgrade branch.

Partial PIS code matching is now implemented in cases where x number of stops need skipping at the start of a route.

Reviewed-on: #64
This commit is contained in:
Fred Boniface
2024-02-11 15:53:12 +00:00
parent 90baf1b13a
commit c147d9c50c
40 changed files with 717 additions and 505 deletions

View File

@@ -4,8 +4,8 @@ interface versions {
}
const version: versions = {
api: ["/api/v1/", "/api/v2"],
app: "2023.10.1",
api: ["/api/v2"],
app: "2024.2.1",
};
module.exports = version;

View File

@@ -1,58 +0,0 @@
const find = require("../services/find.services");
async function findName(req, res, next) {
try {
var id = req.params.id;
res.json(await find.name(id));
} catch (err) {
console.error("Unknown Error", err.message);
next(err);
}
}
async function findCrs(req, res, next) {
try {
var id = req.params.id;
res.json(await find.crs(id));
} catch (err) {
console.error("Unknown Error", err.message);
next(err);
}
}
async function findNlc(req, res, next) {
try {
var id = req.params.id;
res.json(await find.nlc(id));
} catch (err) {
console.error("Unknown Error", err.message);
next(err);
}
}
async function findTiploc(req, res, next) {
try {
var id = req.params.id;
res.json(await find.tiploc(id));
} catch (err) {
console.error("Unknown Error", err.message);
next(err);
}
}
async function findStanox(req, res, next) {
try {
var id = req.params.id;
res.json(await find.stanox(id));
} catch (err) {
console.error("Unknown Error", err.message);
next(err);
}
}
module.exports = {
findName,
findCrs,
findNlc,
findTiploc,
findStanox,
};

View File

@@ -1,35 +0,0 @@
const kube = require("../services/kube.services");
async function getAlive(req, res, next) {
try {
var state = kube.getAlive();
res.status((await state).code).send((await state).state);
} catch (err) {
res.status("503").send({ state: "error" });
}
}
async function getReady(req, res, next) {
try {
res.json(await kube.getReady(req.body));
} catch (err) {
console.error("Unknown Error", err.message);
next(err);
}
}
async function getTime(req, res, next) {
try {
res.json(await kube.getTime(req.body));
} catch (err) {
console.error("Unknown Error", err.message);
err.status = 503;
next(err);
}
}
module.exports = {
getAlive,
getReady,
getTime,
};

View File

@@ -1,17 +1,5 @@
const ldb = require("../services/ldb.services");
async function get(req, res, next) {
// API v1 only
try {
var id = req.params.id;
res.json(await ldb.get(id));
} catch (err) {
console.error("Unknown Error", err.message);
err.status = 500;
next(err);
}
}
async function getTrain(req, res, next) {
// API v2 Only
if (!req.isAuthed) {
@@ -66,7 +54,6 @@ async function getStation(req, res, next) {
}
module.exports = {
get,
getTrain,
getStation,
};

View File

@@ -1,51 +0,0 @@
const ldb = require("../services/ldb.services");
async function get(req, res, next) {
try {
var id = req.params.id;
res.json(await ldb.get(id, true));
} catch (err) {
console.error("Unknown Error", err.message);
err.status = 500;
next(err);
}
}
async function getReasonCodeList(req, res, next) {
try {
res.json(await ldb.getReasonCodeList());
} catch (err) {
console.error("ERROR", err.message);
err.status = 500;
next(err);
}
}
async function getReasonCode(req, res, next) {
try {
const code = req.params.code;
res.json(await ldb.getReasonCode(code));
} catch (err) {
console.error("ERROR", err.message);
err.status = 500;
next(err);
}
}
async function getTrainByRID(req, res, next) {
try {
const rid = req.params.rid;
res.json(await ldb.getServiceByRID(rid));
} catch (err) {
console.error("ERROR", err);
err.status = 500;
next(err);
}
}
module.exports = {
get,
getReasonCodeList,
getReasonCode,
getTrainByRID,
};

View File

@@ -1,37 +0,0 @@
const list = require("../services/list.services");
async function getStations(req, res, next) {
try {
res.json(await list.getStations(req.body));
} catch (err) {
console.error("Controller Error", err.message);
err.status = 500;
next(err);
}
}
async function getCorpus(req, res, next) {
try {
res.json(await list.getCorpus(req.body));
} catch (err) {
console.error("Controller Error", err.message);
err.status = 500;
next(err);
}
}
async function hits(req, res, next) {
try {
res.json(await list.hits());
} catch (err) {
console.error("Controller Error", err);
err.status = 500;
next(err);
}
}
module.exports = {
getStations,
getCorpus,
hits,
};

View File

@@ -1,21 +1,5 @@
const pis = require("../services/pis.services");
async function byOrigDest(req, res, next) {
if (!req.isAuthed) {
const err = new Error("Unauthorized");
err.status = 401;
throw err;
}
try {
let start = req.params.start;
let end = req.params.end;
res.json(await pis.findPisByOrigDest(start, end));
} catch (err) {
console.error("Unknown Error", err.message);
next(err);
}
}
/* Used in /api/v2 */
async function byStartEndCRS(req, res, next) {
if (!req.isAuthed) {
@@ -64,7 +48,6 @@ async function random(req, res, next) {
}
module.exports = {
byOrigDest,
byStartEndCRS,
byCode,
random,

View File

@@ -1,16 +1,5 @@
const stat = require("../services/stats.services");
async function get(req, res, next) {
// API V1
try {
res.json(await stat.hits());
} catch (err) {
console.error("Controller Error", err);
err.status = 500;
next(err);
}
}
async function versions(req, res, next) {
// API v2
try {
@@ -34,7 +23,6 @@ async function statistics(req, res, next) {
}
module.exports = {
get,
versions,
statistics,
};

View File

@@ -1,6 +1,10 @@
import { logger } from "../utils/logger.utils";
const train = require("../services/trainService.services");
async function getByHeadcodeToday(req, res, next) {
// Deprecated - for future removal.
logger.warn("Deprecated Function Called - trainService.services-getByHeadcodeToday")
if (!req.isAuthed) {
const err = new Error("Unauthorized");
err.status = 401;
@@ -28,7 +32,7 @@ async function get(req, res, next) {
try {
switch (searchType) {
case "headcode":
res.json(await train.findByHeadcode(date, id));
res.json(await train.findByHeadcode(id, date));
break;
case "byTrainUid":
res.json(await train.findByTrainUid(id, date));

View File

@@ -1,35 +0,0 @@
const utils = require("../utils/auth.utils");
const logger = require("../utils/logger.utils");
module.exports = async function authCheck(req, res, next) {
//log.out("authMiddlewares: Checking authentication", "dbug");
logger.logger.debug("Auth check starting");
try {
var uuid = req.headers.uuid;
} catch (err) {
logger.logger.warn("Unable to read UUID header - Not authenticated");
req.isAuthed = false;
return next();
}
try {
var result = (await utils.isAuthed(uuid)) || false;
if (!result) {
req.isAuthed = false;
//log.out("authMiddlewares: User !isAuthed", "dbug");
logger.logger.debug("Auth denied");
} else {
req.isAuthed = true;
//log.out("authMiddlewares: User isAuthed", "dbug");
logger.logger.debug("Auth successful");
}
return next();
} catch (err) {
/*log.out(
"authMiddlewares: Unable to check auth, default to !isAuthed",
"warn"
);*/
logger.logger.error(err, `Auth check failed`);
req.isAuthed = false;
return next(err);
}
};

View File

@@ -0,0 +1,50 @@
import { NextFunction, Request, Response } from "express";
const utils = require("../utils/auth.utils");
const logger = require("../utils/logger.utils");
module.exports = async function authCheck(
req: Request,
res: Response,
next: NextFunction
) {
logger.logger.debug("auth.middleware: Auth check begun");
if (process.env.NODE_ENV === "development") {
req.isAuthed = true;
logger.logger.warn("auth.middleware: DEV MODE - Access Granted");
next();
} else {
const id: string | string[] | undefined = req.headers.uuid;
if (typeof id === "undefined") {
req.isAuthed = false;
logger.logger.info("auth.middleware: Authentication failed");
next();
} else if (typeof id === "string") {
const authCheck = (await utils.isAuthed(id)) || false;
if (authCheck) {
req.isAuthed = true;
logger.logger.info("auth.middleware: Authentication Successful");
next();
} else {
req.isAuthed = false;
logger.logger.info("auth.middleware: Authentication Failed");
next();
}
} else if (Array.isArray(id)) {
const authCheck = (await utils.isAuthed(id[0])) || false;
if (authCheck) {
req.isAuthed = true;
logger.logger.warn(
"auth.middleware: UUID Passed as Array - Authentication Successful"
);
next();
} else {
req.isAuthed = false;
logger.logger.warn(
"auth.middleware: UUID Passed as Array - Authentication Failed"
);
next();
}
}
}
};

View File

@@ -1,23 +0,0 @@
const express = require("express");
const router = express.Router();
const findController = require("../controllers/find.controllers");
/* GET programming languages. */
//router.get('/', programmingLanguagesController.get);
/* POST programming language */
//router.post('/', programmingLanguagesController.create);
/* PUT programming language */
//router.put('/:id', programmingLanguagesController.update);
/* DELETE programming language */
//router.delete('/:id', programmingLanguagesController.remove);
router.get("/name/:id", findController.findName);
router.get("/crs/:id", findController.findCrs);
router.get("/nlc/:id", findController.findNlc);
router.get("/tiploc/:id", findController.findTiploc);
router.get("/stanox/:id", findController.findStanox);
module.exports = router;

View File

@@ -1,7 +0,0 @@
const express = require("express");
const router = express.Router();
const issueController = require("../controllers/issue.controllers");
router.post("/", issueController.post);
module.exports = router;

View File

@@ -1,9 +0,0 @@
const express = require("express");
const router = express.Router();
const kubeController = require("../controllers/kube.controllers");
router.get("/alive", kubeController.getAlive);
router.get("/ready", kubeController.getReady);
router.get("/time", kubeController.getTime);
module.exports = router;

View File

@@ -1,19 +0,0 @@
const express = require("express");
const router = express.Router();
const ldbController = require("../controllers/ldb.controllers");
/* GET programming languages. */
//router.get('/', programmingLanguagesController.get);
/* POST programming language */
//router.post('/', programmingLanguagesController.create);
/* PUT programming language */
//router.put('/:id', programmingLanguagesController.update);
/* DELETE programming language */
//router.delete('/:id', programmingLanguagesController.remove);
router.get("/:id", ldbController.get);
module.exports = router;

View File

@@ -1,22 +0,0 @@
const express = require("express");
const router = express.Router();
const ldbsController = require("../controllers/ldbs.controllers");
/* GET programming languages. */
//router.get('/', programmingLanguagesController.get);
/* POST programming language */
//router.post('/', programmingLanguagesController.create);
/* PUT programming language */
//router.put('/:id', programmingLanguagesController.update);
/* DELETE programming language */
//router.delete('/:id', programmingLanguagesController.remove);
router.get("/arrdep/:id", ldbsController.get);
router.get("/reasonCode", ldbsController.getReasonCodeList);
router.get("/reasonCode/:code", ldbsController.getReasonCode);
router.get("/service/rid/:rid", ldbsController.getTrainByRID);
module.exports = router;

View File

@@ -1,20 +0,0 @@
const express = require("express");
const router = express.Router();
const listController = require("../controllers/list.controllers");
/* GET programming languages. */
//router.get('/', programmingLanguagesController.get);
/* POST programming language */
//router.post('/', programmingLanguagesController.create);
/* PUT programming language */
//router.put('/:id', programmingLanguagesController.update);
/* DELETE programming language */
//router.delete('/:id', programmingLanguagesController.remove);
router.get("/stations", listController.getStations);
router.get("/corpus", listController.getCorpus);
module.exports = router;

View File

@@ -1,9 +1,9 @@
const express = require("express");
const router = express.Router();
const pisController = require("../controllers/pis.controllers");
const pisCtr = require("../controllers/pis.controllers");
router.get("/origdest/:start/:end", pisController.byOrigDest);
router.get("/code/:code", pisController.byCode);
router.get("/code/random", pisController.random);
// PIS
router.get("/byCode/:code", pisCtr.byCode);
router.get("/byStartEnd/:startCrs/:endCrs", pisCtr.byStartEndCRS);
module.exports = router;

View File

@@ -1,9 +0,0 @@
const express = require("express");
const router = express.Router();
const pisCtr = require("../controllers/pis.controllers");
// PIS
router.get("/byCode/:code", pisCtr.byCode);
router.get("/byStartEnd/:startCrs/:endCrs", pisCtr.byStartEndCRS);
module.exports = router;

View File

@@ -1,8 +0,0 @@
const express = require("express");
const router = express.Router();
const regController = require("../controllers/registration.controllers");
router.post("/request", regController.request);
router.post("/register", regController.register);
module.exports = router;

View File

@@ -1,7 +0,0 @@
const express = require("express");
const router = express.Router();
const statsController = require("../controllers/stats.controllers");
router.get("/", statsController.get);
module.exports = router;

View File

@@ -1,19 +0,0 @@
const express = require("express");
const router = express.Router();
const trainController = require("../controllers/train.controllers");
/* GET programming languages. */
//router.get('/', programmingLanguagesController.get);
/* POST programming language */
//router.post('/', programmingLanguagesController.create);
/* PUT programming language */
//router.put('/:id', programmingLanguagesController.update);
/* DELETE programming language */
//router.delete('/:id', programmingLanguagesController.remove);
router.get("/headcode/today/:id", trainController.getByHeadcodeToday);
module.exports = router;

View File

@@ -4,7 +4,11 @@ const pis = require("../services/pis.services");
import { logger } from "../utils/logger.utils";
async function findByHeadcodeToday(headcode) {
import type { TrainServices, Service, Stop } from "@owlboard/ts-types";
// This function is deprecated and should no longer be used.
// It will be removed in a later version
async function findByHeadcodeToday(headcode: string) {
const sanitizedHeadcode = clean.removeNonAlphanumeric(headcode).toUpperCase();
logger.debug(
`trainServiceServices.findByHeadcode: Searching for headcode ${sanitizedHeadcode}`
@@ -39,7 +43,11 @@ async function findByHeadcodeToday(headcode) {
return preparedData;
}
async function findByHeadcode(date, headcode) {
// Finds a train by its headcode value
async function findByHeadcode(
date: string | Date,
headcode: string
): Promise<TrainServices[]> {
const sanitizedHeadcode = clean.removeNonAlphanumeric(headcode).toUpperCase();
logger.debug(
`trainServiceServices.findByHeadcode: Searching for headcode ${sanitizedHeadcode}`
@@ -77,12 +85,16 @@ async function findByHeadcode(date, headcode) {
},
},
];
const queryData = await db.queryAggregate("timetable", pipeline);
const queryData: Service[] = await db.queryAggregate("timetable", pipeline);
let filteredData = filterServices(queryData);
return await filteredData;
}
async function findByTrainUid(uid, date = new Date()) {
// Finds a train by its trainUid value
async function findByTrainUid(
uid: string,
date: Date | string = new Date()
): Promise<TrainServices | null> {
let queryDate;
if (date === "now") {
queryDate = new Date();
@@ -96,7 +108,7 @@ async function findByTrainUid(uid, date = new Date()) {
};
const queryData = await db.query("timetable", query);
if (queryData.length === 0) {
return [];
return queryData;
}
let services;
services = await filterServices(queryData);
@@ -124,7 +136,7 @@ module.exports = {
/* Accepts the 'stops' array from a db query and produces an
array of only public stops as TIPLOCs. */
async function getPublicStops(data) {
async function getPublicStops(data: Stop[]): Promise<string[]> {
let tiplocList = [];
for (const publicStop in data) {
if (data[publicStop]["isPublic"]) {
@@ -137,23 +149,26 @@ async function getPublicStops(data) {
/* Takes a single days data from a headcode query and requeries
using the trainUid, required to ensure any cancellations are
accounted for */
async function parseTrains(data) {
let trainUids = [];
async function parseTrains(data: TrainServices[]): Promise<TrainServices[]> {
let trainUids: string[] = [];
for (const i of data) {
const trainUid = i["trainUid"];
if (!trainUids.includes(trainUid)) {
trainUids.push(trainUid);
}
}
let parsedData = [];
let parsedData: TrainServices[] = [];
for (const i in trainUids) {
const result = await findByTrainUid(trainUids[i]);
parsedData.push(result);
const result: TrainServices | null = await findByTrainUid(trainUids[i]);
if (result) {
parsedData.push(result);
}
}
return parsedData;
}
async function filterServices(data) {
// Filters services based on their STP Indicator
async function filterServices(data: Service[]): Promise<TrainServices[]> {
let stpIndicators = {},
filteredServices = [];
for (const serviceDetail of data) {
@@ -180,7 +195,7 @@ async function filterServices(data) {
stpIndicators[trainUid].hasP = true;
}
}
let preparedData;
for (const serviceDetail of data) {
const trainUid = serviceDetail["trainUid"];
const thisStpIndicators = stpIndicators[trainUid];

View File

@@ -7,7 +7,7 @@ const db = require("../services/dbAccess.services");
import { logger } from "../utils/logger.utils";
import { transform as staffStationTransform } from "../utils/translators/ldb/staffStation";
import { transform as staffStationTransform } from "../utils/processors/ldb/staffStation";
import { msgCodes } from "../configs/errorCodes.configs";
const ldbKey = process.env.OWL_LDB_KEY;

View File

@@ -3,9 +3,17 @@
const db = require("../services/dbAccess.services");
const clean = require("../utils/sanitizer.utils");
import { logger } from "../utils/logger.utils";
import type { OB_Pis_SimpleObject } from "@owlboard/ts-types";
const supported = ["GW", "UK"];
import { logger } from "../utils/logger.utils";
import { queryAggregate } from "./dbAccess.services";
import {
getPartialEndTiplocMatchPipeline,
getFullTiplocMatchPipeline,
} from "../utils/pis.utils";
import { Document } from "mongodb";
export const supported = ["GW", "UK"];
async function findPisByOrigDest(start: string, end: string) {
logger.debug(
@@ -27,11 +35,14 @@ async function findPisByOrigDest(start: string, end: string) {
],
},
};
const search = db.query("pis", query);
return await search;
const search = await db.query("pis", query);
// Check for results, if none then try partial match
return search;
}
async function findPisByCode(code: string) {
async function findPisByCode(
code: string
): Promise<OB_Pis_SimpleObject | null> {
logger.debug(`pisServices.findPisByCode: Searching for PIS code: ${code}`);
const cleanCode = clean.removeNonNumeric(code);
const query = {
@@ -41,25 +52,74 @@ async function findPisByCode(code: string) {
return await search;
}
async function findByTiplocArray(tiplocArray: string[]) {
const query = {
tiplocs: tiplocArray,
};
const res = await db.query("pis", query);
// If res is empty then try to find partial match
/* if (res.length) {
return res;
} else {
let partial = await findPartialMatchByTiploc(tiplocArray);
return partial
} */
return await res;
// Queries the database for PIS codes that match the given TIPLOC array
export async function findByTiplocArray(
tiplocArray: string[]
): Promise<OB_Pis_SimpleObject | null> {
// Firstly fix errant TIPLOCS such as RDNG4AB which will never return a result
// currently only works with RDNG4AB - checks for presence of RDNG4AB first to
// avoid iterating over every array ever searched for.
if (tiplocArray.includes("RDNG4AB")) {
fixErrantTiplocs(tiplocArray);
}
// PERFORMANCE NOTE:
// The majority of queries will return a full match,
// this means that a more performant pipeline is used
// to find a full match and only then are more
// complicated pipelines used to find partial matches
// if the first pipeline returns nothing.
try {
const exactMatch = await findExactMatchByTiploc(tiplocArray);
if (exactMatch) {
return convertDocument(exactMatch, "none");
} else {
const partialEnd = await findPartialEndMatchByTiploc(tiplocArray);
if (partialEnd) {
return convertDocument(partialEnd, "first");
} else {
// Here, I should search for a partialStart match. For now return null.
return null;
}
}
} catch (err) {
logger.error(err, "Error in findByTiplocArray");
return null;
}
}
async function findPartialMatchByTiploc(tiplocArray: string[]) {
// Do some magic here, similar to findPisByOrigDest but
// ensuring that the stops in the array match the last
// x number of items in the array.
// Uses a pipeline to find an exact match
async function findExactMatchByTiploc(array: string[]): Promise<Document> {
const pipeline = getFullTiplocMatchPipeline(array);
const res = await queryAggregate("pis", pipeline);
return res[0];
}
// Uses a pipeline to find a partial match - only supporting
// codes ending with the correct stops for now.
async function findPartialEndMatchByTiploc(array: string[]): Promise<Document> {
const pipeline = getPartialEndTiplocMatchPipeline(array);
const res = await queryAggregate("pis", pipeline);
return res[0];
}
function convertDocument(doc: Document, skipType: string): OB_Pis_SimpleObject {
return {
code: doc.code.toString(),
toc: doc.toc,
skipCount: doc.skipStops,
skipType: skipType,
};
}
// Changes any instance of 'RDNG4AB' to 'RDNGSTN'
function fixErrantTiplocs(input: string[]): void {
input.forEach((value, index, array) => {
if (value === "RDNG4AB") {
array[index] = "RDNGSTN";
}
// Additional substitutions can be applied here
});
}
module.exports = {

View File

@@ -0,0 +1,195 @@
import { logger } from "../utils/logger.utils";
import { findByTiplocArray, supported } from "./pis.services";
import { queryAggregate } from "./dbAccess.services";
import {
getFindByHeadcodePipeline,
getFindByTrainUidPipeline,
} from "../utils/trainService.utils";
import { removeNonAlphanumeric } from "../utils/sanitizer.utils";
import { formatTimetableDetail } from "../utils/processors/timetable/timetableProcessor.utils";
import type {
TrainServices,
Service,
Stop,
SimpleService,
OB_Pis_SimpleObject,
} from "@owlboard/ts-types";
export async function findByHeadcode(
headcode: string,
date: Date | string
): Promise<SimpleService[]> {
const sanitizedHeadcode = removeNonAlphanumeric(headcode);
logger.debug(
`trainServices.findByHeadcode: Searching for trains by headcode: ${headcode}`
);
// If 'now' then generate a new Date now, else use the provided date, then set time to 1200.
const searchDate = date === "now" ? new Date() : new Date(date);
searchDate.setHours(12, 0, 0);
// Get the 'shortDay'
const shortDay = getShortDay(searchDate);
const query = {
headcode: sanitizedHeadcode.toUpperCase(),
scheduleStartDate: { $lte: searchDate },
scheduleEndDate: { $gte: searchDate },
daysRun: { $in: [shortDay] },
};
const pipeline = getFindByHeadcodePipeline(query);
const result: SimpleService[] = (await queryAggregate(
"timetable",
pipeline
)) as SimpleService[];
const services = filterServices(result);
return services;
}
export async function findByTrainUid(
uid: string,
date: Date | string = new Date()
) {
// Set the correct date - whether a date or "now" was passed to function
let queryDate: Date;
if (date instanceof Date) {
queryDate = date;
} else {
queryDate = new Date();
}
// Build query
const query = {
trainUid: uid.toUpperCase(),
scheduleStartDate: { $lte: queryDate },
scheduleEndDate: { $gte: queryDate },
daysRun: { $in: [getShortDay(queryDate)] },
};
const pipeline = getFindByTrainUidPipeline(query);
const result = (await queryAggregate("timetable", pipeline)) as Service[];
let services = filterServices(result) as Service[];
// Check if the operator is on the supported TOC list for PIS Codes - if so, call the fetchPisCode function.
let pis: OB_Pis_SimpleObject | null;
if (supported.includes(services[0]?.operator)) {
pis = await fetchPisCode(services[0]?.stops);
} else {
pis = null;
}
// TODO: Format and return data, the function called is not yet complete
return formatTimetableDetail(services[0], pis);
}
// Internal Functions:
// Filters out non-passenger stops and then uses the stop array to request a PIS code for the service
async function fetchPisCode(
stops: Stop[]
): Promise<OB_Pis_SimpleObject | null> {
let tiplocList: string[] = [];
for (const stop in stops) {
if (stops[stop]["isPublic"]) tiplocList.push(stops[stop]["tiploc"]);
}
// Check if no public stops - then it should use an ECS headcode
let pisData: OB_Pis_SimpleObject | null;
if (tiplocList.length) {
pisData = await findByTiplocArray(tiplocList);
} else {
pisData = {
toc: "GW",
skipCount: 0,
code: randomEcsPis(),
};
}
if (!pisData) {
logger.debug(tiplocList, "No PIS found for service")
}
return pisData;
}
// Picks a random choice of the ECS PIS Codes
function randomEcsPis(): string {
const options = ["0015", "9997"];
const randomValue = Math.floor(Math.random() * 2);
return options[randomValue];
}
// Outputs the standard 'shortday' string from a Date.
function getShortDay(day: Date): string {
const dayMap = ["su", "m", "t", "w", "th", "f", "s"];
const shortDay = dayMap[day.getDay()];
return shortDay;
}
// Filters services using their STP indicator so that over-riding entries are returned correctly
function filterServices(services: SimpleService[]): SimpleService[] {
let stpIndicators: Record<
string,
{ hasC: boolean; hasN: boolean; hasO: boolean; hasP: boolean }
> = {};
let filteredServices: SimpleService[] = [];
for (const service of services) {
const trainUid = service["trainUid"],
stpIndicator = service["stpIndicator"];
// Creates the stpIndicators array:
if (!stpIndicators[trainUid]) {
stpIndicators[trainUid] = {
hasC: false,
hasN: false,
hasO: false,
hasP: false,
};
}
if (stpIndicator === "C") {
stpIndicators[trainUid].hasC = true;
}
if (stpIndicator === "N") {
stpIndicators[trainUid].hasN = true;
}
if (stpIndicator === "O") {
stpIndicators[trainUid].hasO = true;
}
if (stpIndicator === "P") {
stpIndicators[trainUid].hasP = true;
}
}
// Iterate each service, and only output one service matching each trainUid,
// C > N > O > P is the order, with C being prioritised over other STP types.
for (const service of services) {
const trainUid = service["trainUid"];
const thisStpIndicators = stpIndicators[trainUid];
const stpIndicator = service["stpIndicator"];
if (stpIndicator === "C") {
filteredServices.push(service);
} else if (stpIndicator === "N" && !thisStpIndicators.hasC) {
filteredServices.push(service);
} else if (
stpIndicator === "O" &&
!thisStpIndicators.hasC &&
!thisStpIndicators.hasN
) {
filteredServices.push(service);
} else if (
stpIndicator === "P" &&
!thisStpIndicators.hasC &&
!thisStpIndicators.hasN &&
!thisStpIndicators.hasO
) {
filteredServices.push(service);
}
}
return filteredServices;
}
// Local Types:

12
src/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
// src/types/express/index.d.ts
// to make the file a module and avoid the TypeScript error
export {};
declare global {
namespace Express {
export interface Request {
isAuthed: boolean;
}
}
}

102
src/utils/pis.utils.ts Normal file
View File

@@ -0,0 +1,102 @@
export function getPartialEndTiplocMatchPipeline(query: string[]) {
return [
{
$match: {
tiplocs: {
$all: query,
},
},
},
{
$addFields: {
reversedTiplocs: {
$reverseArray: "$tiplocs",
},
query: {
$literal: query,
},
},
},
{
$addFields: {
reversedQuery: {
$reverseArray: "$query",
},
},
},
{
$match: {
$expr: {
$eq: [
{
$slice: [
"$reversedTiplocs",
0,
{
$size: "$reversedQuery",
},
],
},
"$reversedQuery",
],
},
},
},
{
$addFields: {
skipStops: {
$subtract: [
{
$size: "$tiplocs",
},
{
$size: "$reversedQuery",
},
],
},
},
},
{
$sort: {
skipStops: 1,
},
},
{
$limit: 1,
},
{
$project: {
code: 1,
skipStops: 1,
toc: 1,
_id: 0,
},
},
];
}
export function getFullTiplocMatchPipeline(query: string[]) {
return [
{
$match: {
tiplocs: query,
},
},
{
$limit: 1,
},
{
$addFields: {
skipStops: 0,
},
},
{
$project: {
code: 1,
toc: 1,
skipStops: 1,
_id: 0,
},
},
];
}

View File

@@ -111,7 +111,10 @@ function transformTrainServices(input: any): TrainServices[] {
function checkIsSupressed(service: TrainServices): string | undefined {
logger.trace("utils/translators/ldb/staffStation.checkIsSupressed: Running");
if (service.serviceIsSupressed === "true" || service.isPassengerService === "false") {
if (
service.serviceIsSupressed === "true" ||
service.isPassengerService === "false"
) {
return "true";
} else {
return undefined;

View File

@@ -0,0 +1,76 @@
import type {
Service,
OB_TrainTT_service,
OB_Pis_SimpleObject,
OB_TrainTT_stopDetail,
Stop,
} from "@owlboard/ts-types";
import { logger } from "../../logger.utils";
export function formatTimetableDetail(
service: Service,
pis: OB_Pis_SimpleObject | null
): OB_TrainTT_service {
const formattedService: OB_TrainTT_service = {
stpIndicator: service.stpIndicator,
operator: service.operator,
trainUid: service.trainUid,
headcode: service.headcode,
powerType: service.powerType,
planSpeed: convertStringToNumber(service.planSpeed),
scheduleStart: service.scheduleStartDate,
scheduleEnd: service.scheduleEndDate,
daysRun: service.daysRun,
stops: formatStops(service.stops),
vstp: service.vstp,
};
if (pis) {
formattedService.pis = pis;
}
return formattedService;
}
function formatStops(stops: Stop[]): OB_TrainTT_stopDetail[] {
// Cleanly coerce Stop[] to OB_TrainTT_stopDetail[]
const formattedStops: OB_TrainTT_stopDetail[] = [];
for (const stop of stops) {
formattedStops.push(formatStopTimes(stop));
}
return formattedStops;
}
function formatStopTimes(stop: Stop): OB_TrainTT_stopDetail {
// Cleanly converts a single stop to a stopdetail object
let formattedStop: OB_TrainTT_stopDetail = {
tiploc: stop.tiploc,
isPublic: false,
};
if (stop.publicArrival) {
formattedStop.publicArrival = stop.publicArrival;
formattedStop.isPublic = true;
}
if (stop.publicDeparture) {
formattedStop.publicDeparture = stop.publicDeparture;
formattedStop.isPublic = true;
}
if (stop.wttArrival) {
formattedStop.wttArrival = stop.wttArrival;
}
if (stop.wttDeparture) {
formattedStop.wttDeparture = stop.wttDeparture;
}
return formattedStop;
}
function convertStringToNumber(str: string): number {
const number = parseFloat(str);
if (isNaN(number)) {
return 0;
} else {
return number;
}
}

View File

@@ -0,0 +1,29 @@
export function getFindByHeadcodePipeline(query: any) {
return [
{
$match: query,
},
{
$project: {
operator: 1,
stops: {
$concatArrays: [
[{ $first: "$stops" }],
[{ $arrayElemAt: ["$stops", -1] }],
],
},
trainUid: 1,
stpIndicator: 1,
},
},
];
}
export function getFindByTrainUidPipeline(query: any) {
return [
{
$match: query,
},
{ $project: { _id: 0 } },
];
}