Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
11f72679a0 | |||
e12b809d04 | |||
9ab5a7be99 | |||
8b00a9afe5 | |||
c4646bc654 | |||
989d14ff95 | |||
a561357fbe | |||
4d2262f349 | |||
eecafee7cf | |||
d54e223369 | |||
78c5c02c0e | |||
a793862aa2 | |||
be1ee0b285 | |||
196251eeb6 | |||
e913db5d57 | |||
fdcb43b5c2 | |||
9031eb53c6 | |||
2a9050940d | |||
e1fc4b1db2 | |||
6cfc42f22e | |||
9d51d4e45e | |||
fde37814a7 | |||
8fa0cf775f | |||
afa4ad7915 | |||
d49a5ae034 | |||
e7b8208edf | |||
dad9f46d86 | |||
c698187cdf | |||
90500b88af | |||
77ca61e178 | |||
2ff822d8eb | |||
09f883a461 | |||
d98b560584 | |||
f02ae3c7cd | |||
1f0a39adc6 | |||
f4b5e9ce37 | |||
a5a73812a9 | |||
87532b001d | |||
236d85648d | |||
91e2657d66 | |||
874b236f09 | |||
5904ee37cd | |||
70c9aa2b1e | |||
8e0b928f27 | |||
ac9372515f | |||
4cc6856a76 | |||
d15b7c3c7a |
10
app.js
10
app.js
@ -1,5 +1,4 @@
|
||||
// OwlBoard - © Fred Boniface 2022-2023 - Licensed under GPLv3 (or later)
|
||||
|
||||
// Please see the included LICENSE file
|
||||
|
||||
const mode = process.env.NODE_ENV || "development";
|
||||
@ -13,7 +12,6 @@ const express = require("express");
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
const compression = require("compression");
|
||||
const rateLimit = require("express-rate-limit");
|
||||
const cors = require("cors");
|
||||
const authenticate = require("./src/middlewares/auth.middlewares");
|
||||
@ -52,15 +50,14 @@ app.use((err, req, res, next) => {
|
||||
return;
|
||||
});
|
||||
|
||||
// Global Middleware:
|
||||
// Pre Middleware:
|
||||
app.use(
|
||||
cors({
|
||||
origin: "*", //[/\.owlboard\.info$/, 'localhost:5173', 'localhost:4173']
|
||||
})
|
||||
);
|
||||
app.use(express.json()); //JSON Parsing for POST Requests
|
||||
app.use(compression()); // Compress API Data if supported by client
|
||||
app.use(limiter);
|
||||
//app.use(limiter);
|
||||
app.use(authenticate);
|
||||
|
||||
// 2023 Rationalisation Routes (/api/v2, /misc)
|
||||
@ -84,6 +81,9 @@ mode === "development"
|
||||
? app.get("/api/v1/ip", (req, res) => res.send(req.ip))
|
||||
: null;
|
||||
|
||||
// Disable etags
|
||||
app.set('etag', false)
|
||||
|
||||
// Start Express
|
||||
app.listen(srvPort, srvListen, (error) => {
|
||||
if (!error) {
|
||||
|
@ -6,6 +6,10 @@
|
||||
html {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #404c55;
|
||||
background-image: radial-gradient(#2b343c, #404c55);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
@ -25,6 +29,10 @@
|
||||
background-color: #404c55;
|
||||
background-image: radial-gradient(#2b343c, #404c55);
|
||||
}
|
||||
p {
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
#title {
|
||||
height: 100px;
|
||||
padding-top: 0px;
|
||||
@ -43,16 +51,24 @@
|
||||
text-decoration: none;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.digits {
|
||||
color: azure;
|
||||
font-size: xx-large;
|
||||
font-weight: bolder;
|
||||
letter-spacing: 0.75ch;
|
||||
margin-left: 0.75ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br /><br />
|
||||
<br><br>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img
|
||||
src="https://owlboard.info/images/logo/wide_logo.svg"
|
||||
id="title"
|
||||
alt="OwlBoard Logo"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -61,27 +77,16 @@
|
||||
<h1>Register for OwlBoard</h1>
|
||||
<br />
|
||||
<p>
|
||||
Tap the button to register this device, or scan the barcode with
|
||||
another device.
|
||||
You'll need to type your registration code in to the OwlBoard app
|
||||
</p>
|
||||
<br />
|
||||
<a
|
||||
href="https://owlboard.info/more/reg/submit?key=>>ACCESSCODE<<"
|
||||
id="button"
|
||||
>Register this device</a
|
||||
>
|
||||
<br /><br /><br />
|
||||
<p>Or scan with the device you want to register</p>
|
||||
<br />
|
||||
<img
|
||||
src="https://barcodes.fjla.uk/generate?type=qr&text=https%3A%2F%2Fowlboard.info%2Fmore%2Freg%2Fsubmit%3Fkey%3D>>ACCESSCODE<<"
|
||||
alt="Scan barcode to register on another device"
|
||||
title="Scan to register on another device"
|
||||
/>
|
||||
<br /><br /><br />
|
||||
<h2>Your Code:</h2>
|
||||
<span class="digits">987654</span>
|
||||
<br><br>
|
||||
<p>
|
||||
Alternatively copy and paste the link:<br />https://owlboard.info/more/reg/submit?key=>>ACCESSCODE<<
|
||||
Go back to OwlBoard and enter your code. Go to the registration page and click the link at the top.
|
||||
</p>
|
||||
<br /><br /><br />
|
||||
<p>
|
||||
This registration is for one device only, you can register again
|
||||
using the same email address for other devices and access OwlBoard
|
||||
@ -92,7 +97,7 @@
|
||||
can safely ignore this email. Your email address has not been stored
|
||||
by us.
|
||||
</p>
|
||||
<p>The registration link will expire after 30 minutes.</p>
|
||||
<p>The registration link will expire after 1 hour.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -1,12 +1,10 @@
|
||||
Complete your OwlBoard (Staff) Registration using the link below.
|
||||
Complete your OwlBoard (Staff) Registration by entering your six digit code.
|
||||
|
||||
https://owlboard.info/more/reg/submit?key=>>ACCESSCODE<<
|
||||
987654
|
||||
|
||||
Alternatively you can copy and paste the above link into your browser.
|
||||
|
||||
You can also view a QR code to register on another device: https://barcodes.fjla.uk/generate?type=qr&text=https%3A%2F%2Fowlboard.info%2Fmore%2Freg%2Fsubmit%3Fkey%3D>>ACCESSCODE<<
|
||||
Go back to the OwlBoard app, goto "Menu > Registration" and click on the link at the top to enter your code.
|
||||
|
||||
If you did not request to register to OwlBoard then you can safely ignore this email.
|
||||
Your email address has not been stored by us and will not be required unless you wish to register again.
|
||||
|
||||
The link will expire after 30 minutes.
|
||||
The link will expire after 1 hour.
|
2312
package-lock.json
generated
2312
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "owlboard",
|
||||
"version": "2024.2.1",
|
||||
"description": "OwlBoard is an API and PWA for live rail departure board in the UK.",
|
||||
"name": "owlboard-backend",
|
||||
"version": "2024.2.2",
|
||||
"description": "Provides LDB, PIS and live train details for the OwlBoard web client",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.fjla.uk/owlboard/backend.git"
|
||||
@ -27,13 +27,13 @@
|
||||
"ldbs-json": "^1.2.1",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"mongodb": "^4.13.0",
|
||||
"nodemailer": "^6.9.1",
|
||||
"nodemailer": "^6.9.9",
|
||||
"pino": "^8.15.1",
|
||||
"redis": "^4.6.7",
|
||||
"zlib": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@owlboard/ts-types": "^0.1.8",
|
||||
"@owlboard/ts-types": "^1.1.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.3",
|
||||
"eslint": "^8.39.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
const valid: string[] = [
|
||||
export const valid: string[] = [
|
||||
"owlboard.info",
|
||||
"avantiwestcoast.co.uk",
|
||||
"btp.police.uk",
|
||||
@ -32,6 +32,3 @@ const valid: string[] = [
|
||||
"tfwrail.wales",
|
||||
"wmtrains.co.uk",
|
||||
];
|
||||
|
||||
module.exports = valid;
|
||||
export { valid };
|
||||
|
@ -5,7 +5,7 @@ interface versions {
|
||||
|
||||
const version: versions = {
|
||||
api: ["/api/v2"],
|
||||
app: "2024.2.1",
|
||||
app: "2025.03.2",
|
||||
};
|
||||
|
||||
module.exports = version;
|
||||
|
@ -4,6 +4,7 @@ const log = require("../utils/logs.utils");
|
||||
async function post(req, res, next) {
|
||||
try {
|
||||
log.out(`issueControllers.post: Request Body: ${JSON.stringify(req.body)}`);
|
||||
setCache(res, "no-store")
|
||||
res.json(await issue.processor(req.body));
|
||||
} catch (err) {
|
||||
console.error("Controller Error", err.message);
|
||||
|
@ -1,5 +1,8 @@
|
||||
const ldb = require("../services/ldb.services");
|
||||
|
||||
import { setCache } from "../utils/cacheHeader.utils";
|
||||
import { logger } from "../utils/logger.utils";
|
||||
|
||||
async function getTrain(req, res, next) {
|
||||
// API v2 Only
|
||||
if (!req.isAuthed) {
|
||||
@ -7,6 +10,7 @@ async function getTrain(req, res, next) {
|
||||
err.status = 401;
|
||||
throw err;
|
||||
}
|
||||
setCache(res, "private", 240)
|
||||
let type = req.params.searchType;
|
||||
let id = req.params.id;
|
||||
try {
|
||||
@ -42,18 +46,53 @@ async function getStation(req, res, next) {
|
||||
err.status = 401;
|
||||
return next(err);
|
||||
}
|
||||
res.json(await ldb.get(id, true));
|
||||
const data = await ldb.get(id, true);
|
||||
// Only cache if data is present
|
||||
if (data.data) {
|
||||
setCache(res, "public", 120);
|
||||
} else {
|
||||
setCache(res, "no-store", 120);
|
||||
}
|
||||
res.json(data);
|
||||
} else {
|
||||
setCache(res, "public", 240)
|
||||
res.json(await ldb.get(id, false));
|
||||
}
|
||||
} catch (err) {
|
||||
setCache(res, "no-store")
|
||||
console.error("Unknown Error", err.message);
|
||||
err.status = 500;
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function getNearest(req, res, next) {
|
||||
// API v2 Only
|
||||
let latitude = req.params.latitude;
|
||||
let longitude = req.params.longitude;
|
||||
try {
|
||||
if (!req.isAuthed) {
|
||||
const err = new Error("Unauthorized");
|
||||
err.status = 401;
|
||||
return next(err)
|
||||
}
|
||||
const data = await ldb.getNearestStations(latitude, longitude)
|
||||
if (data) {
|
||||
setCache(res, "private", 120)
|
||||
} else {
|
||||
setCache(res, "no-store", 120)
|
||||
}
|
||||
res.json(data)
|
||||
} catch (err) {
|
||||
setCache(res, "no-store")
|
||||
logger.Error("Error fetching nearest station")
|
||||
err.status = 500;
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTrain,
|
||||
getStation,
|
||||
getNearest,
|
||||
};
|
||||
|
@ -1,54 +0,0 @@
|
||||
const pis = require("../services/pis.services");
|
||||
|
||||
/* Used in /api/v2 */
|
||||
async function byStartEndCRS(req, res, next) {
|
||||
if (!req.isAuthed) {
|
||||
const err = new Error("Unauthorized");
|
||||
err.status = 401;
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
let startCrs = req.params.startCrs;
|
||||
let endCrs = req.params.endCrs;
|
||||
res.json(await pis.findPisByOrigDest(startCrs, endCrs));
|
||||
} catch (err) {
|
||||
console.error("Unknown Error", err.message);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/* Used in /api/v2 */
|
||||
async function byCode(req, res, next) {
|
||||
if (!req.isAuthed) {
|
||||
const err = new Error("Unauthorized");
|
||||
err.status = 401;
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
let code = req.params.code;
|
||||
res.json(await pis.findPisByCode(code));
|
||||
} catch (err) {
|
||||
console.error("Unknown Error", err.message);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function random(req, res, next) {
|
||||
if (!req.isAuthed) {
|
||||
const err = new Error("Unauthorized");
|
||||
err.status = 401;
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
res.json(await pis.findRandom());
|
||||
} catch (err) {
|
||||
console.error("Unknown Error", err.message);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
byStartEndCRS,
|
||||
byCode,
|
||||
random,
|
||||
};
|
40
src/controllers/pis.controllers.ts
Normal file
40
src/controllers/pis.controllers.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { findPisByOrigDest, findPisByCode } from "../services/pis.services";
|
||||
import { setCache } from "../utils/cacheHeader.utils";
|
||||
|
||||
async function byStartEndCRS(req: Request, res: Response, next: NextFunction) {
|
||||
// if (!req.isAuthed) {
|
||||
// const err = new Error("Unauthorized");
|
||||
// err.status = 401;
|
||||
// return next(err);
|
||||
// }
|
||||
try {
|
||||
let startCrs = req.params.startCrs;
|
||||
let endCrs = req.params.endCrs;
|
||||
setCache(res, "public", 600)
|
||||
res.json(await findPisByOrigDest(startCrs, endCrs));
|
||||
} catch (err: any) {
|
||||
console.error("Unknown Error", err.message);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function byCode(req: Request, res: Response, next: NextFunction) {
|
||||
// if (!req.isAuthed) {
|
||||
// const err = new Error("Unauthorized");
|
||||
// err.status = 401;
|
||||
// return next(err);
|
||||
// }
|
||||
try {
|
||||
let code = req.params.code;
|
||||
res.json(await findPisByCode(code));
|
||||
} catch (err: any) {
|
||||
console.error("Unknown Error", err.message);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
byStartEndCRS,
|
||||
byCode,
|
||||
};
|
@ -1,5 +1,7 @@
|
||||
/* API V2 Exclusive Controller */
|
||||
|
||||
import { setCache } from "../utils/cacheHeader.utils";
|
||||
|
||||
const ldb = require("../services/ldb.services");
|
||||
const find = require("../services/find.services");
|
||||
|
||||
@ -7,6 +9,7 @@ async function getReasonCode(req, res, next) {
|
||||
try {
|
||||
const code = req.params.code;
|
||||
if (code === "all") {
|
||||
setCache(res, "public", 604800)
|
||||
res.json(await ldb.getReasonCodeList());
|
||||
next;
|
||||
}
|
||||
@ -15,6 +18,7 @@ async function getReasonCode(req, res, next) {
|
||||
} catch (err) {
|
||||
console.error("ERROR", err.message);
|
||||
err.status = 500;
|
||||
setCache(res, "no-store", 5)
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
@ -23,6 +27,7 @@ async function getLocationReference(req, res, next) {
|
||||
try {
|
||||
const searchType = req.params.searchType;
|
||||
const id = req.params.id;
|
||||
setCache(res, "public", 604800)
|
||||
switch (searchType) {
|
||||
case "name":
|
||||
res.json(await find.name(id));
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { setCache } from "../utils/cacheHeader.utils";
|
||||
|
||||
const stat = require("../services/stats.services");
|
||||
|
||||
async function versions(req, res, next) {
|
||||
// API v2
|
||||
try {
|
||||
setCache(res, "public", 60)
|
||||
res.json(await stat.getVersions());
|
||||
} catch (err) {
|
||||
console.error("Controller Error", err);
|
||||
@ -14,6 +17,7 @@ async function versions(req, res, next) {
|
||||
async function statistics(req, res, next) {
|
||||
// Api v2
|
||||
try {
|
||||
setCache(res, "public", 60)
|
||||
res.json(await stat.statistics());
|
||||
} catch (err) {
|
||||
console.error("Controller Error", err);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { setCache } from "../utils/cacheHeader.utils";
|
||||
import { logger } from "../utils/logger.utils";
|
||||
|
||||
const train = require("../services/trainService.services");
|
||||
@ -5,11 +6,11 @@ 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;
|
||||
next(err);
|
||||
}
|
||||
// if (!req.isAuthed) {
|
||||
// const err = new Error("Unauthorized");
|
||||
// err.status = 401;
|
||||
// next(err);
|
||||
// }
|
||||
try {
|
||||
var searchHeadcode = req.params.id;
|
||||
res.json(await train.findByHeadcodeToday(searchHeadcode));
|
||||
@ -21,20 +22,22 @@ async function getByHeadcodeToday(req, res, next) {
|
||||
}
|
||||
|
||||
async function get(req, res, next) {
|
||||
if (!req.isAuthed) {
|
||||
const err = new Error("Unauthorized");
|
||||
err.status = 401;
|
||||
next(err);
|
||||
}
|
||||
// if (!req.isAuthed) {
|
||||
// const err = new Error("Unauthorized");
|
||||
// err.status = 401;
|
||||
// next(err);
|
||||
// }
|
||||
let date = req.params.date;
|
||||
let searchType = req.params.searchType;
|
||||
let id = req.params.id;
|
||||
try {
|
||||
switch (searchType) {
|
||||
case "headcode":
|
||||
setCache(res, "private", 1800)
|
||||
res.json(await train.findByHeadcode(id, date));
|
||||
break;
|
||||
case "byTrainUid":
|
||||
setCache(res, "private", 1800)
|
||||
res.json(await train.findByTrainUid(id, date));
|
||||
break;
|
||||
default:
|
||||
|
@ -1,46 +1,43 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
|
||||
const utils = require("../utils/auth.utils");
|
||||
const logger = require("../utils/logger.utils");
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import { logger } from "../utils/logger.utils";
|
||||
import { isAuthed } from "../utils/auth.utils";
|
||||
|
||||
module.exports = async function authCheck(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
logger.logger.debug("auth.middleware: Auth check begun");
|
||||
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");
|
||||
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");
|
||||
logger.info("auth.middleware: Authentication failed");
|
||||
next();
|
||||
} else if (typeof id === "string") {
|
||||
const authCheck = (await utils.isAuthed(id)) || false;
|
||||
const authCheck = (await isAuthed(id)) || false;
|
||||
if (authCheck) {
|
||||
// Authenticate
|
||||
req.isAuthed = true;
|
||||
logger.logger.info("auth.middleware: Authentication Successful");
|
||||
next();
|
||||
} else {
|
||||
req.isAuthed = false;
|
||||
logger.logger.info("auth.middleware: Authentication Failed");
|
||||
logger.info("auth.middleware: Authentication Failed");
|
||||
next();
|
||||
}
|
||||
// Handle cases where UUID passed as an array
|
||||
} else if (Array.isArray(id)) {
|
||||
const authCheck = (await utils.isAuthed(id[0])) || false;
|
||||
const authCheck = (await 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(
|
||||
logger.warn(
|
||||
"auth.middleware: UUID Passed as Array - Authentication Failed"
|
||||
);
|
||||
next();
|
||||
|
@ -4,6 +4,7 @@ const ldbCtr = require("../controllers/ldb.controllers");
|
||||
|
||||
// PIS
|
||||
router.get("/station/:id/:type", ldbCtr.getStation);
|
||||
router.get("/station/nearest/:latitude/:longitude", ldbCtr.getNearest);
|
||||
router.get("/train/:searchType/:id", ldbCtr.getTrain);
|
||||
|
||||
module.exports = router;
|
||||
|
@ -1,225 +0,0 @@
|
||||
const db = require("./dbAccess.services");
|
||||
const clean = require("../utils/sanitizer.utils");
|
||||
const pis = require("../services/pis.services");
|
||||
|
||||
import { logger } from "../utils/logger.utils";
|
||||
|
||||
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}`
|
||||
);
|
||||
const now = new Date();
|
||||
const dayMap = ["su", "m", "t", "w", "th", "f", "s"];
|
||||
const shortDay = dayMap[now.getDay()]; // Fetch short day from map
|
||||
const query = {
|
||||
headcode: sanitizedHeadcode,
|
||||
scheduleStartDate: { $lte: now },
|
||||
scheduleEndDate: { $gte: now },
|
||||
daysRun: { $in: [shortDay] },
|
||||
};
|
||||
const queryData = await db.query("timetable", query);
|
||||
let trainData = await parseTrains(queryData);
|
||||
let preparedData = [];
|
||||
for (let trainService in trainData) {
|
||||
if (pis.supported.includes(trainService?.operator)) {
|
||||
// Search for PIS Code for each service
|
||||
const tiplocList = await getPublicStops(trainService?.stops);
|
||||
//console.log(tiplocList.length); console.log(tiplocList);
|
||||
if (tiplocList.length) {
|
||||
const pisDetail = await pis.findByTiplocArray(tiplocList);
|
||||
trainService["pis"] = pisDetail?.[0]?.["code"] ?? "None";
|
||||
} else if (trainService?.operator === "GW") {
|
||||
trainService["pis"] = "0015"; // Not in Service code
|
||||
// '0015' is a string becuase 0015 is not a valid number..
|
||||
}
|
||||
}
|
||||
preparedData.push(trainService);
|
||||
}
|
||||
return preparedData;
|
||||
}
|
||||
|
||||
// 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}`
|
||||
);
|
||||
let searchDate;
|
||||
if (date === "now") {
|
||||
searchDate = new Date();
|
||||
} else {
|
||||
searchDate = new Date(date);
|
||||
}
|
||||
searchDate.setHours(12, 0, 0); // Set to midday to avoid any timezone issues
|
||||
const dayMap = ["su", "m", "t", "w", "th", "f", "s"];
|
||||
const shortDay = dayMap[searchDate.getDay()]; // Fetch short day from map
|
||||
const query = {
|
||||
headcode: sanitizedHeadcode,
|
||||
scheduleStartDate: { $lte: searchDate },
|
||||
scheduleEndDate: { $gte: searchDate },
|
||||
daysRun: { $in: [shortDay] },
|
||||
};
|
||||
const pipeline = [
|
||||
{
|
||||
$match: query,
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
operator: 1,
|
||||
stops: {
|
||||
$concatArrays: [
|
||||
[{ $first: "$stops" }],
|
||||
[{ $arrayElemAt: ["$stops", -1] }],
|
||||
],
|
||||
},
|
||||
trainUid: 1,
|
||||
stpIndicator: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
const queryData: Service[] = await db.queryAggregate("timetable", pipeline);
|
||||
let filteredData = filterServices(queryData);
|
||||
return await filteredData;
|
||||
}
|
||||
|
||||
// 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();
|
||||
} else {
|
||||
queryDate = date;
|
||||
}
|
||||
const query = {
|
||||
trainUid: uid,
|
||||
scheduleStartDate: { $lte: queryDate },
|
||||
scheduleEndDate: { $gte: queryDate },
|
||||
};
|
||||
const queryData = await db.query("timetable", query);
|
||||
if (queryData.length === 0) {
|
||||
return queryData;
|
||||
}
|
||||
let services;
|
||||
services = await filterServices(queryData);
|
||||
console.log(services);
|
||||
let publicStops;
|
||||
if (pis.supported.includes(services[0]?.operator)) {
|
||||
publicStops = await getPublicStops(services[0]?.stops);
|
||||
if (publicStops.length) {
|
||||
const pisCode = await pis.findByTiplocArray(publicStops);
|
||||
services[0].pis = pisCode[0]?.code;
|
||||
} else if (services[0]?.operator === "GW" && !publicStops.length) {
|
||||
services[0].pis = "0015";
|
||||
}
|
||||
}
|
||||
return services[0];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findByHeadcodeToday,
|
||||
findByHeadcode,
|
||||
findByTrainUid,
|
||||
};
|
||||
|
||||
/* Internal Functions, not to be exported */
|
||||
|
||||
/* Accepts the 'stops' array from a db query and produces an
|
||||
array of only public stops as TIPLOCs. */
|
||||
async function getPublicStops(data: Stop[]): Promise<string[]> {
|
||||
let tiplocList = [];
|
||||
for (const publicStop in data) {
|
||||
if (data[publicStop]["isPublic"]) {
|
||||
tiplocList.push(data[publicStop]["tiploc"]);
|
||||
}
|
||||
}
|
||||
return tiplocList;
|
||||
}
|
||||
|
||||
/* 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: TrainServices[]): Promise<TrainServices[]> {
|
||||
let trainUids: string[] = [];
|
||||
for (const i of data) {
|
||||
const trainUid = i["trainUid"];
|
||||
if (!trainUids.includes(trainUid)) {
|
||||
trainUids.push(trainUid);
|
||||
}
|
||||
}
|
||||
let parsedData: TrainServices[] = [];
|
||||
for (const i in trainUids) {
|
||||
const result: TrainServices | null = await findByTrainUid(trainUids[i]);
|
||||
if (result) {
|
||||
parsedData.push(result);
|
||||
}
|
||||
}
|
||||
return parsedData;
|
||||
}
|
||||
|
||||
// Filters services based on their STP Indicator
|
||||
async function filterServices(data: Service[]): Promise<TrainServices[]> {
|
||||
let stpIndicators = {},
|
||||
filteredServices = [];
|
||||
for (const serviceDetail of data) {
|
||||
const trainUid = serviceDetail["trainUid"];
|
||||
const stpIndicator = serviceDetail["stpIndicator"];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
for (const serviceDetail of data) {
|
||||
const trainUid = serviceDetail["trainUid"];
|
||||
const thisStpIndicators = stpIndicators[trainUid];
|
||||
const stpIndicator = serviceDetail["stpIndicator"];
|
||||
|
||||
if (stpIndicator === "C") {
|
||||
break;
|
||||
}
|
||||
if (stpIndicator === "N" && !thisStpIndicators.hasC) {
|
||||
filteredServices.push(serviceDetail);
|
||||
} else if (
|
||||
stpIndicator === "O" &&
|
||||
!thisStpIndicators.hasC &&
|
||||
!thisStpIndicators.hasN
|
||||
) {
|
||||
filteredServices.push(serviceDetail);
|
||||
} else if (
|
||||
stpIndicator === "P" &&
|
||||
!thisStpIndicators.hasC &&
|
||||
!thisStpIndicators.hasN &&
|
||||
!thisStpIndicators.hasO
|
||||
) {
|
||||
filteredServices.push(serviceDetail);
|
||||
}
|
||||
}
|
||||
return filteredServices;
|
||||
}
|
@ -6,10 +6,14 @@ const dbName = process.env.OWL_DB_NAME || "owlboard";
|
||||
const dbPort = process.env.OWL_DB_PORT || 27017;
|
||||
const dbHost = process.env.OWL_DB_HOST || "localhost";
|
||||
const uri = `mongodb://${dbUser}:${dbPass}@${dbHost}:${dbPort}`;
|
||||
const connOpts = {
|
||||
useUnifiedTopology: true,
|
||||
authSource: "owlboard",
|
||||
}
|
||||
|
||||
const { MongoClient } = require("mongodb");
|
||||
|
||||
const client = new MongoClient(uri);
|
||||
const client = new MongoClient(uri, connOpts);
|
||||
const db = client.db(dbName);
|
||||
|
||||
async function query(collection, query, returnId = false) {
|
||||
|
@ -5,10 +5,11 @@ const util = require("../utils/ldb.utils");
|
||||
const san = require("../utils/sanitizer.utils");
|
||||
const db = require("../services/dbAccess.services");
|
||||
|
||||
import { findStationsByDistancePipeline } from "../utils/ldbPipeline.utils";
|
||||
import { logger } from "../utils/logger.utils";
|
||||
|
||||
import { transform as staffStationTransform } from "../utils/processors/ldb/staffStation";
|
||||
import { msgCodes } from "../configs/errorCodes.configs";
|
||||
|
||||
|
||||
const ldbKey = process.env.OWL_LDB_KEY;
|
||||
const ldbsvKey = process.env.OWL_LDB_SVKEY;
|
||||
@ -32,7 +33,7 @@ async function get(id, staff = false) {
|
||||
logger.error(err, "ldbService.get: Error, Unable to find CRS");
|
||||
return {
|
||||
obStatus: "LOC_NOT_FOUND",
|
||||
obMsg: "UNABLE TO FIND MESSAGE",
|
||||
obMsg: "Location is not available",
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -69,14 +70,18 @@ async function arrDepBoardStaff(CRS) {
|
||||
};
|
||||
const api = new ldb(ldbsvKey, true);
|
||||
console.time(`Fetch Staff LDB for ${CRS.toUpperCase()}`);
|
||||
const result = await api.call(
|
||||
"GetArrivalDepartureBoardByCRS",
|
||||
options,
|
||||
false,
|
||||
false
|
||||
);
|
||||
console.log("\n\n\nORIGINAL DATA");
|
||||
console.log("\n" + JSON.stringify(result) + "\n\n\n");
|
||||
let result
|
||||
try {
|
||||
result = await staffApiCallRetry(
|
||||
api,
|
||||
"GetArrivalDepartureBoardByCRS",
|
||||
options,
|
||||
5,
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err, "Error fetching board data");
|
||||
return {obStatus: "Error", obMsg: "Error fetching data from National Rail", data: null}
|
||||
}
|
||||
console.timeEnd(`Fetch Staff LDB for ${CRS.toUpperCase()}`);
|
||||
try {
|
||||
const _staffLdb = staffStationTransform(result);
|
||||
@ -128,6 +133,29 @@ async function getServicesByOther(id) {
|
||||
}
|
||||
}
|
||||
|
||||
async function staffApiCallRetry(api, method, options, retries) {
|
||||
for (let i=0; i < retries; i++) {
|
||||
try {
|
||||
return await api.call(method, options, false, false);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOTFOUND') {
|
||||
logger.warn(err, "DNS ERR")
|
||||
if (i < retries - 1) {
|
||||
logger.debug('Retrying API Call')
|
||||
await delay(500)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
throw new Error("Max retries exceeded");
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function getReasonCodeList() {
|
||||
logger.debug("ldbService.getReasonCodeList: Fetching reason code list");
|
||||
try {
|
||||
@ -150,6 +178,17 @@ async function getReasonCode(code) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getNearestStations(lat, long) {
|
||||
|
||||
logger.debug(`ldbService.getNearestStations: Fetching nearest stations`)
|
||||
let pipeline = findStationsByDistancePipeline(4, lat, long)
|
||||
try {
|
||||
return await db.queryAggregate("stations", pipeline)
|
||||
} catch (err) {
|
||||
logger.error(err, `ldbService.getNearestStations`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getDateTimeString(date) {
|
||||
const year = date.getFullYear(),
|
||||
month = String(date.getMonth() + 1).padStart(2, "0"),
|
||||
@ -175,4 +214,5 @@ module.exports = {
|
||||
getServicesByOther,
|
||||
getReasonCodeList,
|
||||
getReasonCode,
|
||||
getNearestStations,
|
||||
};
|
||||
|
@ -10,12 +10,13 @@ import { queryAggregate } from "./dbAccess.services";
|
||||
import {
|
||||
getPartialEndTiplocMatchPipeline,
|
||||
getFullTiplocMatchPipeline,
|
||||
getPartialStartTiplocMatchPipeline,
|
||||
} from "../utils/pis.utils";
|
||||
import { Document } from "mongodb";
|
||||
|
||||
export const supported = ["GW", "UK"];
|
||||
export const supported = ["GW", "UK", "HX"];
|
||||
|
||||
async function findPisByOrigDest(start: string, end: string) {
|
||||
export async function findPisByOrigDest(start: string, end: string) {
|
||||
logger.debug(
|
||||
`pisServices.findPisByOrigDest: Searching for Orig: ${start}, Dest: ${end}`
|
||||
);
|
||||
@ -40,13 +41,13 @@ async function findPisByOrigDest(start: string, end: string) {
|
||||
return search;
|
||||
}
|
||||
|
||||
async function findPisByCode(
|
||||
export 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 = {
|
||||
code: parseInt(cleanCode),
|
||||
code: cleanCode,
|
||||
};
|
||||
const search = db.query("pis", query);
|
||||
return await search;
|
||||
@ -78,8 +79,12 @@ export async function findByTiplocArray(
|
||||
if (partialEnd) {
|
||||
return convertDocument(partialEnd, "first");
|
||||
} else {
|
||||
// Here, I should search for a partialStart match. For now return null.
|
||||
return null;
|
||||
const partialStart = await findPartialStartMatchByTiploc(tiplocArray);
|
||||
if (partialStart) {
|
||||
return convertDocument(partialStart, "last");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@ -103,6 +108,13 @@ async function findPartialEndMatchByTiploc(array: string[]): Promise<Document> {
|
||||
return res[0];
|
||||
}
|
||||
|
||||
// Uses a pipeline to find a partial match - supporting codes starting with the correct stops.
|
||||
async function findPartialStartMatchByTiploc(array: string[]): Promise<Document> {
|
||||
const pipeline = getPartialStartTiplocMatchPipeline(array);
|
||||
const res = await queryAggregate("pis", pipeline);
|
||||
return res[0];
|
||||
}
|
||||
|
||||
function convertDocument(doc: Document, skipType: string): OB_Pis_SimpleObject {
|
||||
return {
|
||||
code: doc.code.toString(),
|
||||
|
@ -1,34 +1,32 @@
|
||||
const auth = require("../utils/auth.utils");
|
||||
const db = require("./dbAccess.services");
|
||||
const mail = require("./mail.services");
|
||||
const clean = require("../utils/sanitizer.utils");
|
||||
const domains = require("../configs/domains.configs");
|
||||
const errors = require("../configs/errorCodes.configs");
|
||||
|
||||
import { logger } from "../utils/logger.utils";
|
||||
import { getDomainFromEmail } from "../utils/sanitizer.utils";
|
||||
import { valid as validDomains } from "../configs/domains.configs";
|
||||
import { generateCode } from "../utils/auth.utils";
|
||||
|
||||
async function createRegKey(body) {
|
||||
logger.debug("registerServices.createRegKey: Incoming request");
|
||||
if (body.email) {
|
||||
const domain = await clean.getDomainFromEmail(body.email);
|
||||
const domain = getDomainFromEmail(body.email);
|
||||
logger.info(`registerServices: Registration request from: ${domain}`);
|
||||
if (domains.includes(domain)) {
|
||||
if (validDomains.includes(domain)) {
|
||||
logger.debug(`registerServices.createRegKey: Key from valid: ${domain}`);
|
||||
const uuid = await auth.generateKey();
|
||||
db.addRegReq(uuid, domain);
|
||||
const message = await auth.generateConfirmationEmail(body.email, uuid);
|
||||
const key = generateCode()
|
||||
db.addRegReq(key, domain)
|
||||
const message = await auth.generateConfirmationEmail(body.email, key);
|
||||
if (!message) {
|
||||
const err = new Error("Message generation error");
|
||||
logger.error(
|
||||
err,
|
||||
"registerServices.createRegKey: Error generating email"
|
||||
);
|
||||
const err = new Error("Message Generation Error");
|
||||
logger.error(err, "registerServices.createRegKey: Error generating email");
|
||||
return 500;
|
||||
}
|
||||
if ((await mail.send(message)) == true) {
|
||||
return { status: 201, message: "email sent" };
|
||||
return {status: 201, message: "email sent"};
|
||||
}
|
||||
return { status: 500, errorCode: 950, errorMsg: errors[950] };
|
||||
return {status:500, errorCode:950, errorMsg: errors[950]}
|
||||
}
|
||||
return { status: 403, errorCode: 702, errorMsg: errors[702] };
|
||||
} else {
|
||||
@ -38,8 +36,9 @@ async function createRegKey(body) {
|
||||
|
||||
async function regUser(req) {
|
||||
// Add input validation
|
||||
logger.trace(`Read UUID: ${req.uuid}`);
|
||||
const res = await auth.checkRequest(req.uuid);
|
||||
const regCode = req.uuid.toLocaleUpperCase();
|
||||
logger.trace(`Read UUID: ${regCode}`);
|
||||
const res = await auth.checkRequest(regCode);
|
||||
logger.debug(res, "registrationServices");
|
||||
if (res.result) {
|
||||
const uuid = await auth.generateKey();
|
||||
@ -52,6 +51,8 @@ async function regUser(req) {
|
||||
return { status: 401, errorCode: 703, errorMsg: errors[703] };
|
||||
}
|
||||
|
||||
// Currently errors on a correct code as it cannot be found... Ensure uuid is ALL CAPS
|
||||
|
||||
async function getUser(uuid) {
|
||||
try {
|
||||
const filter = {
|
||||
|
@ -8,8 +8,6 @@ async function buildJson() {
|
||||
let json = {};
|
||||
json.count = {};
|
||||
// Async call all db queries
|
||||
const counters = db.query("meta", { target: "counters" });
|
||||
const versions = db.query("meta", { target: "versions" });
|
||||
const userCount = db.colCount("users");
|
||||
const regCount = db.colCount("registrations");
|
||||
const pisCount = db.colCount("pis");
|
||||
@ -19,12 +17,8 @@ async function buildJson() {
|
||||
|
||||
// Insert data
|
||||
json.mode = process.env.NODE_ENV;
|
||||
json.verBkend = vers.app;
|
||||
json.verApi = vers.api;
|
||||
json.host = os.hostname();
|
||||
// Await and insert async calls
|
||||
json.dat = await counters;
|
||||
json.ver = await versions;
|
||||
json.count.users = await userCount;
|
||||
json.count.reg = await regCount;
|
||||
json.count.pis = await pisCount;
|
||||
@ -42,11 +36,9 @@ async function hits() {
|
||||
|
||||
async function getVersions() {
|
||||
logger.debug("statsServices.getVersions: Fetching versions");
|
||||
const dbMan = await db.query("versions", { target: "dbmanager" });
|
||||
const mqClt = await db.query("versions", { target: "mq-client" });
|
||||
const mqClt = await db.query("versions", { target: "timetable-mgr" });
|
||||
const data = {
|
||||
backend: vers.app,
|
||||
"db-manager": dbMan[0]?.["version"] || "",
|
||||
"mq-client": mqClt[0]?.["version"] || "",
|
||||
};
|
||||
return data;
|
||||
@ -55,11 +47,10 @@ async function getVersions() {
|
||||
async function statistics() {
|
||||
logger.debug("statsServices.statistics: Fetching statistics");
|
||||
|
||||
const countersPromise = db.query("meta", { target: "counters" });
|
||||
const timetablePromise = db.query("meta", { target: "timetable" });
|
||||
const pisPromise = db.query("meta", { target: "pis" });
|
||||
const timetablePromise = db.query("meta", { type: "CifMetadata" });
|
||||
const pisPromise = db.query("meta", { type: "PisMetadata" });
|
||||
const corpusPromise = db.query("meta", { target: "corpus" });
|
||||
const reasonCodesPromise = db.query("meta", { target: "reasonCodes" });
|
||||
const stationsPromise = db.query("meta", {type: "StationsMetadata"});
|
||||
|
||||
const lengthUsersPromise = db.colCount("users");
|
||||
const lengthRegistrationsPromise = db.colCount("registrations");
|
||||
@ -70,51 +61,37 @@ async function statistics() {
|
||||
const lengthReasonCodesPromise = db.colCount("reasonCodes");
|
||||
|
||||
const [
|
||||
counters,
|
||||
timetable,
|
||||
pis,
|
||||
corpus,
|
||||
reasonCodes,
|
||||
lengthUsers,
|
||||
lengthRegistrations,
|
||||
lengthCorpus,
|
||||
lengthStations,
|
||||
lengthPis,
|
||||
lengthTimetable,
|
||||
lengthReasonCodes,
|
||||
stations,
|
||||
] = await Promise.all([
|
||||
countersPromise,
|
||||
timetablePromise,
|
||||
pisPromise,
|
||||
corpusPromise,
|
||||
reasonCodesPromise,
|
||||
lengthUsersPromise,
|
||||
lengthRegistrationsPromise,
|
||||
lengthCorpusPromise,
|
||||
lengthStationsPromise,
|
||||
lengthPisPromise,
|
||||
lengthTimetablePromise,
|
||||
lengthReasonCodesPromise,
|
||||
stationsPromise,
|
||||
]);
|
||||
|
||||
return {
|
||||
hostname: os.hostname() || "Unknown",
|
||||
runtimeMode: process.env.NODE_ENV || "Unknown",
|
||||
reset: counters[0]["since"],
|
||||
updateTimes: {
|
||||
timetable: timetable[0]["updated"],
|
||||
pis: pis[0]["updated"],
|
||||
corpus: corpus[0]["updated"],
|
||||
reasonCodes: reasonCodes[0]["updated"],
|
||||
},
|
||||
requestCounts: {
|
||||
ldbws_api: counters[0]["ldbws"] || 0,
|
||||
lsbsvws_api: counters[0]["ldbsvws"] || 0,
|
||||
corpus_api: counters[0]["corpus_api"] || 0,
|
||||
timetable_db: counters[0]["timetable"] || 0,
|
||||
pis_db: counters[0]["pis"] || 0,
|
||||
corpus_db: counters[0]["corpus"] || 0,
|
||||
stations_db: counters[0]["stations"] || 0,
|
||||
timetable: (timetable[0]["lastUpdate"]),
|
||||
pis: pis[0]["lastUpdate"],
|
||||
corpus: corpus[0]["updated_time"],
|
||||
stations: stations[0]["lastUpdate"],
|
||||
},
|
||||
dbLengths: {
|
||||
users: lengthUsers,
|
||||
@ -123,7 +100,6 @@ async function statistics() {
|
||||
stations: lengthStations,
|
||||
pis: lengthPis,
|
||||
timetable: lengthTimetable,
|
||||
reasonCodes: lengthReasonCodes,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ export async function findByHeadcode(
|
||||
|
||||
const query = {
|
||||
headcode: sanitizedHeadcode.toUpperCase(),
|
||||
daysRun: { $in: [shortDay] },
|
||||
scheduleStartDate: { $lte: searchDate },
|
||||
scheduleEndDate: { $gte: searchDate },
|
||||
daysRun: { $in: [shortDay] },
|
||||
};
|
||||
const pipeline = getFindByHeadcodePipeline(query);
|
||||
|
||||
@ -56,18 +56,20 @@ export async function findByTrainUid(
|
||||
) {
|
||||
// Set the correct date - whether a date or "now" was passed to function
|
||||
let queryDate: Date;
|
||||
if (date instanceof Date) {
|
||||
if (date === 'now') {
|
||||
queryDate = new Date();
|
||||
} else if (date instanceof Date) {
|
||||
queryDate = date;
|
||||
} else {
|
||||
queryDate = new Date();
|
||||
queryDate = new Date(date);
|
||||
}
|
||||
|
||||
// Build query
|
||||
const query = {
|
||||
trainUid: uid.toUpperCase(),
|
||||
daysRun: { $in: [getShortDay(queryDate)] },
|
||||
scheduleStartDate: { $lte: queryDate },
|
||||
scheduleEndDate: { $gte: queryDate },
|
||||
daysRun: { $in: [getShortDay(queryDate)] },
|
||||
};
|
||||
const pipeline = getFindByTrainUidPipeline(query);
|
||||
|
||||
@ -82,7 +84,6 @@ export async function findByTrainUid(
|
||||
} else {
|
||||
pis = null;
|
||||
}
|
||||
// TODO: Format and return data, the function called is not yet complete
|
||||
return formatTimetableDetail(services[0], pis);
|
||||
}
|
||||
|
||||
|
5
src/types/index.d.ts
vendored
5
src/types/index.d.ts
vendored
@ -8,5 +8,10 @@ declare global {
|
||||
export interface Request {
|
||||
isAuthed: boolean;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
cacheType: string;
|
||||
cacheSecs: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ async function isAuthed(uuid: string): Promise<boolean> {
|
||||
|
||||
// Checks whether a registration request key is valid
|
||||
async function checkRequest(key: string) {
|
||||
// For some reason db.query seems to return correctly, but the second logs.out statement prints []??? so registration fails!!
|
||||
const collection = "registrations";
|
||||
const query = { uuid: key };
|
||||
const res = await db.query(collection, query);
|
||||
@ -34,20 +33,33 @@ async function checkRequest(key: string) {
|
||||
|
||||
// Creates an API key for a user
|
||||
async function generateKey() {
|
||||
// Needs testing & moving to 'register.utils' ??? Why does it need moving?
|
||||
return crypt.randomUUID();
|
||||
}
|
||||
|
||||
export function generateCode(): string {
|
||||
const characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
const codeLength = 6;
|
||||
|
||||
let code = '';
|
||||
const bytes = crypt.randomBytes(codeLength); // Generate random bytes
|
||||
for (let i = 0; i < codeLength; i++) {
|
||||
const randomIndex = bytes[i] % characters.length; // Map bytes to characters
|
||||
code += characters.charAt(randomIndex);
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
async function generateConfirmationEmail(eml: string, uuid: string) {
|
||||
try {
|
||||
const htmlTpl = await fs.readFile("mail-templates/register.html", "utf-8");
|
||||
const htmlStr = htmlTpl.replace(/>>ACCESSCODE<</g, uuid);
|
||||
const htmlStr = htmlTpl.replace(/987654/g, uuid);
|
||||
const htmlMin = await minifyMail(htmlStr);
|
||||
const txtTpl = fs.readFile("mail-templates/register.txt", "utf-8");
|
||||
return {
|
||||
to: eml,
|
||||
subject: "OwlBoard Registration",
|
||||
text: (await txtTpl).replace(/>>ACCESSCODE<</g, uuid),
|
||||
text: (await txtTpl).replace(/987654/g, uuid),
|
||||
html: htmlMin,
|
||||
};
|
||||
} catch (err) {
|
||||
@ -64,6 +76,7 @@ module.exports = {
|
||||
generateKey,
|
||||
generateConfirmationEmail,
|
||||
checkRequest,
|
||||
generateCode
|
||||
};
|
||||
|
||||
export { isAuthed, generateKey, generateConfirmationEmail, checkRequest };
|
||||
|
9
src/utils/cacheHeader.utils.ts
Normal file
9
src/utils/cacheHeader.utils.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import type { Response } from "express"
|
||||
|
||||
export function setCache(res: Response, type="private", time=120): void {
|
||||
if (type === "no-store") {
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
return
|
||||
}
|
||||
res.setHeader('Cache-Control', `${type}, max-age=${time}`)
|
||||
}
|
47
src/utils/ldbPipeline.utils.ts
Normal file
47
src/utils/ldbPipeline.utils.ts
Normal file
@ -0,0 +1,47 @@
|
||||
export function findStationsByDistancePipeline(count: number, latitude: string, longitude: string) {
|
||||
const numericLatitude = parseFloat(latitude)
|
||||
const numericLongitude = parseFloat(longitude)
|
||||
const pipeline = [
|
||||
{
|
||||
'$geoNear': {
|
||||
'near': {
|
||||
'type': 'Point',
|
||||
'coordinates': [
|
||||
numericLongitude, numericLatitude
|
||||
]
|
||||
},
|
||||
'distanceField': 'distance'
|
||||
}
|
||||
}, {
|
||||
'$limit': count
|
||||
}, {
|
||||
'$addFields': {
|
||||
'miles': {
|
||||
'$divide': [
|
||||
{
|
||||
'$round': {
|
||||
'$multiply': [
|
||||
{
|
||||
'$divide': [
|
||||
'$distance', 1609.34
|
||||
]
|
||||
}, 4
|
||||
]
|
||||
}
|
||||
}, 4
|
||||
]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'$project': {
|
||||
'_id': 0,
|
||||
'3ALPHA': 1,
|
||||
'NLCDESC': 1,
|
||||
'miles': 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
//console.log(JSON.stringify(pipeline))
|
||||
return pipeline
|
||||
}
|
@ -75,6 +75,61 @@ export function getPartialEndTiplocMatchPipeline(query: string[]) {
|
||||
];
|
||||
}
|
||||
|
||||
export function getPartialStartTiplocMatchPipeline(query: string[]) {
|
||||
return [
|
||||
{
|
||||
'$match': {
|
||||
'tiplocs': {
|
||||
'$all': query
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'$addFields': {
|
||||
'query': query
|
||||
}
|
||||
}, {
|
||||
'$match': {
|
||||
'$expr': {
|
||||
'$eq': [
|
||||
{
|
||||
'$slice': [
|
||||
'$tiplocs', {
|
||||
'$size': '$query'
|
||||
}
|
||||
]
|
||||
}, '$query'
|
||||
]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'$addFields': {
|
||||
'skipStops': {
|
||||
'$subtract': [
|
||||
{
|
||||
'$size': '$tiplocs'
|
||||
}, {
|
||||
'$size': '$query'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'$sort': {
|
||||
'skipStops': 1
|
||||
}
|
||||
}, {
|
||||
'$limit': 1
|
||||
}, {
|
||||
'$project': {
|
||||
'code': 1,
|
||||
'skipStops': 1,
|
||||
'toc': 1,
|
||||
'_id': 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function getFullTiplocMatchPipeline(query: string[]) {
|
||||
return [
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ import type {
|
||||
OB_TrainTT_stopDetail,
|
||||
Stop,
|
||||
} from "@owlboard/ts-types";
|
||||
import { logger } from "../../logger.utils";
|
||||
|
||||
export function formatTimetableDetail(
|
||||
service: Service,
|
||||
@ -22,7 +21,7 @@ export function formatTimetableDetail(
|
||||
scheduleEnd: service.scheduleEndDate,
|
||||
daysRun: service.daysRun,
|
||||
stops: formatStops(service.stops),
|
||||
vstp: service.vstp,
|
||||
serviceDetail: service.serviceDetail,
|
||||
};
|
||||
|
||||
if (pis) {
|
||||
@ -33,6 +32,14 @@ export function formatTimetableDetail(
|
||||
}
|
||||
|
||||
function formatStops(stops: Stop[]): OB_TrainTT_stopDetail[] {
|
||||
if (!stops) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (!stops.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Cleanly coerce Stop[] to OB_TrainTT_stopDetail[]
|
||||
const formattedStops: OB_TrainTT_stopDetail[] = [];
|
||||
|
||||
@ -63,6 +70,22 @@ function formatStopTimes(stop: Stop): OB_TrainTT_stopDetail {
|
||||
if (stop.wttDeparture) {
|
||||
formattedStop.wttDeparture = stop.wttDeparture;
|
||||
}
|
||||
|
||||
if (stop.platform) {
|
||||
formattedStop.platform = stop.platform;
|
||||
}
|
||||
|
||||
if (stop.pass) {
|
||||
formattedStop.pass = stop.pass;
|
||||
}
|
||||
|
||||
if (stop.arrLine) {
|
||||
formattedStop.arrLine = stop.arrLine;
|
||||
}
|
||||
|
||||
if (stop.depLine) {
|
||||
formattedStop.depLine = stop.depLine;
|
||||
}
|
||||
return formattedStop;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
//const log = require('../utils/log.utils');
|
||||
|
||||
import { logger } from "./logger.utils";
|
||||
|
||||
function removeNonAlphanumeric(inputString: string) {
|
||||
|
3
src/utils/userSetup.utils.ts
Normal file
3
src/utils/userSetup.utils.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// Do I need to setup the database?
|
||||
|
||||
// Possibly not, becuase every write will create the document if it doesn't exist
|
Loading…
x
Reference in New Issue
Block a user