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)
|
// OwlBoard - © Fred Boniface 2022-2023 - Licensed under GPLv3 (or later)
|
||||||
|
|
||||||
// Please see the included LICENSE file
|
// Please see the included LICENSE file
|
||||||
|
|
||||||
const mode = process.env.NODE_ENV || "development";
|
const mode = process.env.NODE_ENV || "development";
|
||||||
@ -13,7 +12,6 @@ const express = require("express");
|
|||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
const compression = require("compression");
|
|
||||||
const rateLimit = require("express-rate-limit");
|
const rateLimit = require("express-rate-limit");
|
||||||
const cors = require("cors");
|
const cors = require("cors");
|
||||||
const authenticate = require("./src/middlewares/auth.middlewares");
|
const authenticate = require("./src/middlewares/auth.middlewares");
|
||||||
@ -52,15 +50,14 @@ app.use((err, req, res, next) => {
|
|||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Global Middleware:
|
// Pre Middleware:
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
origin: "*", //[/\.owlboard\.info$/, 'localhost:5173', 'localhost:4173']
|
origin: "*", //[/\.owlboard\.info$/, 'localhost:5173', 'localhost:4173']
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(express.json()); //JSON Parsing for POST Requests
|
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);
|
app.use(authenticate);
|
||||||
|
|
||||||
// 2023 Rationalisation Routes (/api/v2, /misc)
|
// 2023 Rationalisation Routes (/api/v2, /misc)
|
||||||
@ -84,6 +81,9 @@ mode === "development"
|
|||||||
? app.get("/api/v1/ip", (req, res) => res.send(req.ip))
|
? app.get("/api/v1/ip", (req, res) => res.send(req.ip))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Disable etags
|
||||||
|
app.set('etag', false)
|
||||||
|
|
||||||
// Start Express
|
// Start Express
|
||||||
app.listen(srvPort, srvListen, (error) => {
|
app.listen(srvPort, srvListen, (error) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
html {
|
html {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #404c55;
|
||||||
|
background-image: radial-gradient(#2b343c, #404c55);
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -25,6 +29,10 @@
|
|||||||
background-color: #404c55;
|
background-color: #404c55;
|
||||||
background-image: radial-gradient(#2b343c, #404c55);
|
background-image: radial-gradient(#2b343c, #404c55);
|
||||||
}
|
}
|
||||||
|
p {
|
||||||
|
margin-left: 40px;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
#title {
|
#title {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
@ -43,16 +51,24 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
.digits {
|
||||||
|
color: azure;
|
||||||
|
font-size: xx-large;
|
||||||
|
font-weight: bolder;
|
||||||
|
letter-spacing: 0.75ch;
|
||||||
|
margin-left: 0.75ch;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<br /><br />
|
<br><br>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img
|
<img
|
||||||
src="https://owlboard.info/images/logo/wide_logo.svg"
|
src="https://owlboard.info/images/logo/wide_logo.svg"
|
||||||
id="title"
|
id="title"
|
||||||
|
alt="OwlBoard Logo"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -61,27 +77,16 @@
|
|||||||
<h1>Register for OwlBoard</h1>
|
<h1>Register for OwlBoard</h1>
|
||||||
<br />
|
<br />
|
||||||
<p>
|
<p>
|
||||||
Tap the button to register this device, or scan the barcode with
|
You'll need to type your registration code in to the OwlBoard app
|
||||||
another device.
|
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<a
|
<h2>Your Code:</h2>
|
||||||
href="https://owlboard.info/more/reg/submit?key=>>ACCESSCODE<<"
|
<span class="digits">987654</span>
|
||||||
id="button"
|
<br><br>
|
||||||
>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 />
|
|
||||||
<p>
|
<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>
|
</p>
|
||||||
|
<br /><br /><br />
|
||||||
<p>
|
<p>
|
||||||
This registration is for one device only, you can register again
|
This registration is for one device only, you can register again
|
||||||
using the same email address for other devices and access OwlBoard
|
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
|
can safely ignore this email. Your email address has not been stored
|
||||||
by us.
|
by us.
|
||||||
</p>
|
</p>
|
||||||
<p>The registration link will expire after 30 minutes.</p>
|
<p>The registration link will expire after 1 hour.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</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.
|
Go back to the OwlBoard app, goto "Menu > Registration" and click on the link at the top to enter your code.
|
||||||
|
|
||||||
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<<
|
|
||||||
|
|
||||||
If you did not request to register to OwlBoard then you can safely ignore this email.
|
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.
|
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",
|
"name": "owlboard-backend",
|
||||||
"version": "2024.2.1",
|
"version": "2024.2.2",
|
||||||
"description": "OwlBoard is an API and PWA for live rail departure board in the UK.",
|
"description": "Provides LDB, PIS and live train details for the OwlBoard web client",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.fjla.uk/owlboard/backend.git"
|
"url": "https://git.fjla.uk/owlboard/backend.git"
|
||||||
@ -27,13 +27,13 @@
|
|||||||
"ldbs-json": "^1.2.1",
|
"ldbs-json": "^1.2.1",
|
||||||
"moment-timezone": "^0.5.43",
|
"moment-timezone": "^0.5.43",
|
||||||
"mongodb": "^4.13.0",
|
"mongodb": "^4.13.0",
|
||||||
"nodemailer": "^6.9.1",
|
"nodemailer": "^6.9.9",
|
||||||
"pino": "^8.15.1",
|
"pino": "^8.15.1",
|
||||||
"redis": "^4.6.7",
|
"redis": "^4.6.7",
|
||||||
"zlib": "^1.0.5"
|
"zlib": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@owlboard/ts-types": "^0.1.8",
|
"@owlboard/ts-types": "^1.1.0",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.3",
|
||||||
"eslint": "^8.39.0",
|
"eslint": "^8.39.0",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const valid: string[] = [
|
export const valid: string[] = [
|
||||||
"owlboard.info",
|
"owlboard.info",
|
||||||
"avantiwestcoast.co.uk",
|
"avantiwestcoast.co.uk",
|
||||||
"btp.police.uk",
|
"btp.police.uk",
|
||||||
@ -32,6 +32,3 @@ const valid: string[] = [
|
|||||||
"tfwrail.wales",
|
"tfwrail.wales",
|
||||||
"wmtrains.co.uk",
|
"wmtrains.co.uk",
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = valid;
|
|
||||||
export { valid };
|
|
||||||
|
@ -5,7 +5,7 @@ interface versions {
|
|||||||
|
|
||||||
const version: versions = {
|
const version: versions = {
|
||||||
api: ["/api/v2"],
|
api: ["/api/v2"],
|
||||||
app: "2024.2.1",
|
app: "2025.03.2",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = version;
|
module.exports = version;
|
||||||
|
@ -4,6 +4,7 @@ const log = require("../utils/logs.utils");
|
|||||||
async function post(req, res, next) {
|
async function post(req, res, next) {
|
||||||
try {
|
try {
|
||||||
log.out(`issueControllers.post: Request Body: ${JSON.stringify(req.body)}`);
|
log.out(`issueControllers.post: Request Body: ${JSON.stringify(req.body)}`);
|
||||||
|
setCache(res, "no-store")
|
||||||
res.json(await issue.processor(req.body));
|
res.json(await issue.processor(req.body));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Controller Error", err.message);
|
console.error("Controller Error", err.message);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
const ldb = require("../services/ldb.services");
|
const ldb = require("../services/ldb.services");
|
||||||
|
|
||||||
|
import { setCache } from "../utils/cacheHeader.utils";
|
||||||
|
import { logger } from "../utils/logger.utils";
|
||||||
|
|
||||||
async function getTrain(req, res, next) {
|
async function getTrain(req, res, next) {
|
||||||
// API v2 Only
|
// API v2 Only
|
||||||
if (!req.isAuthed) {
|
if (!req.isAuthed) {
|
||||||
@ -7,6 +10,7 @@ async function getTrain(req, res, next) {
|
|||||||
err.status = 401;
|
err.status = 401;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
setCache(res, "private", 240)
|
||||||
let type = req.params.searchType;
|
let type = req.params.searchType;
|
||||||
let id = req.params.id;
|
let id = req.params.id;
|
||||||
try {
|
try {
|
||||||
@ -42,18 +46,53 @@ async function getStation(req, res, next) {
|
|||||||
err.status = 401;
|
err.status = 401;
|
||||||
return next(err);
|
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 {
|
} else {
|
||||||
|
setCache(res, "public", 240)
|
||||||
res.json(await ldb.get(id, false));
|
res.json(await ldb.get(id, false));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
setCache(res, "no-store")
|
||||||
console.error("Unknown Error", err.message);
|
console.error("Unknown Error", err.message);
|
||||||
err.status = 500;
|
err.status = 500;
|
||||||
next(err);
|
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 = {
|
module.exports = {
|
||||||
getTrain,
|
getTrain,
|
||||||
getStation,
|
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 */
|
/* API V2 Exclusive Controller */
|
||||||
|
|
||||||
|
import { setCache } from "../utils/cacheHeader.utils";
|
||||||
|
|
||||||
const ldb = require("../services/ldb.services");
|
const ldb = require("../services/ldb.services");
|
||||||
const find = require("../services/find.services");
|
const find = require("../services/find.services");
|
||||||
|
|
||||||
@ -7,6 +9,7 @@ async function getReasonCode(req, res, next) {
|
|||||||
try {
|
try {
|
||||||
const code = req.params.code;
|
const code = req.params.code;
|
||||||
if (code === "all") {
|
if (code === "all") {
|
||||||
|
setCache(res, "public", 604800)
|
||||||
res.json(await ldb.getReasonCodeList());
|
res.json(await ldb.getReasonCodeList());
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
@ -15,6 +18,7 @@ async function getReasonCode(req, res, next) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("ERROR", err.message);
|
console.error("ERROR", err.message);
|
||||||
err.status = 500;
|
err.status = 500;
|
||||||
|
setCache(res, "no-store", 5)
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,6 +27,7 @@ async function getLocationReference(req, res, next) {
|
|||||||
try {
|
try {
|
||||||
const searchType = req.params.searchType;
|
const searchType = req.params.searchType;
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
setCache(res, "public", 604800)
|
||||||
switch (searchType) {
|
switch (searchType) {
|
||||||
case "name":
|
case "name":
|
||||||
res.json(await find.name(id));
|
res.json(await find.name(id));
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import { setCache } from "../utils/cacheHeader.utils";
|
||||||
|
|
||||||
const stat = require("../services/stats.services");
|
const stat = require("../services/stats.services");
|
||||||
|
|
||||||
async function versions(req, res, next) {
|
async function versions(req, res, next) {
|
||||||
// API v2
|
// API v2
|
||||||
try {
|
try {
|
||||||
|
setCache(res, "public", 60)
|
||||||
res.json(await stat.getVersions());
|
res.json(await stat.getVersions());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Controller Error", err);
|
console.error("Controller Error", err);
|
||||||
@ -14,6 +17,7 @@ async function versions(req, res, next) {
|
|||||||
async function statistics(req, res, next) {
|
async function statistics(req, res, next) {
|
||||||
// Api v2
|
// Api v2
|
||||||
try {
|
try {
|
||||||
|
setCache(res, "public", 60)
|
||||||
res.json(await stat.statistics());
|
res.json(await stat.statistics());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Controller Error", err);
|
console.error("Controller Error", err);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { setCache } from "../utils/cacheHeader.utils";
|
||||||
import { logger } from "../utils/logger.utils";
|
import { logger } from "../utils/logger.utils";
|
||||||
|
|
||||||
const train = require("../services/trainService.services");
|
const train = require("../services/trainService.services");
|
||||||
@ -5,11 +6,11 @@ const train = require("../services/trainService.services");
|
|||||||
async function getByHeadcodeToday(req, res, next) {
|
async function getByHeadcodeToday(req, res, next) {
|
||||||
// Deprecated - for future removal.
|
// Deprecated - for future removal.
|
||||||
logger.warn("Deprecated Function Called - trainService.services-getByHeadcodeToday")
|
logger.warn("Deprecated Function Called - trainService.services-getByHeadcodeToday")
|
||||||
if (!req.isAuthed) {
|
// if (!req.isAuthed) {
|
||||||
const err = new Error("Unauthorized");
|
// const err = new Error("Unauthorized");
|
||||||
err.status = 401;
|
// err.status = 401;
|
||||||
next(err);
|
// next(err);
|
||||||
}
|
// }
|
||||||
try {
|
try {
|
||||||
var searchHeadcode = req.params.id;
|
var searchHeadcode = req.params.id;
|
||||||
res.json(await train.findByHeadcodeToday(searchHeadcode));
|
res.json(await train.findByHeadcodeToday(searchHeadcode));
|
||||||
@ -21,20 +22,22 @@ async function getByHeadcodeToday(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function get(req, res, next) {
|
async function get(req, res, next) {
|
||||||
if (!req.isAuthed) {
|
// if (!req.isAuthed) {
|
||||||
const err = new Error("Unauthorized");
|
// const err = new Error("Unauthorized");
|
||||||
err.status = 401;
|
// err.status = 401;
|
||||||
next(err);
|
// next(err);
|
||||||
}
|
// }
|
||||||
let date = req.params.date;
|
let date = req.params.date;
|
||||||
let searchType = req.params.searchType;
|
let searchType = req.params.searchType;
|
||||||
let id = req.params.id;
|
let id = req.params.id;
|
||||||
try {
|
try {
|
||||||
switch (searchType) {
|
switch (searchType) {
|
||||||
case "headcode":
|
case "headcode":
|
||||||
|
setCache(res, "private", 1800)
|
||||||
res.json(await train.findByHeadcode(id, date));
|
res.json(await train.findByHeadcode(id, date));
|
||||||
break;
|
break;
|
||||||
case "byTrainUid":
|
case "byTrainUid":
|
||||||
|
setCache(res, "private", 1800)
|
||||||
res.json(await train.findByTrainUid(id, date));
|
res.json(await train.findByTrainUid(id, date));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1,46 +1,43 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
|
import { logger } from "../utils/logger.utils";
|
||||||
const utils = require("../utils/auth.utils");
|
import { isAuthed } from "../utils/auth.utils";
|
||||||
const logger = require("../utils/logger.utils");
|
|
||||||
|
|
||||||
module.exports = async function authCheck(
|
module.exports = async function authCheck(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
logger.logger.debug("auth.middleware: Auth check begun");
|
logger.debug("auth.middleware: Auth check begun");
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
req.isAuthed = true;
|
req.isAuthed = true;
|
||||||
logger.logger.warn("auth.middleware: DEV MODE - Access Granted");
|
logger.warn("auth.middleware: DEV MODE - Access Granted");
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
const id: string | string[] | undefined = req.headers.uuid;
|
const id: string | string[] | undefined = req.headers.uuid;
|
||||||
if (typeof id === "undefined") {
|
if (typeof id === "undefined") {
|
||||||
req.isAuthed = false;
|
req.isAuthed = false;
|
||||||
logger.logger.info("auth.middleware: Authentication failed");
|
logger.info("auth.middleware: Authentication failed");
|
||||||
next();
|
next();
|
||||||
} else if (typeof id === "string") {
|
} else if (typeof id === "string") {
|
||||||
const authCheck = (await utils.isAuthed(id)) || false;
|
const authCheck = (await isAuthed(id)) || false;
|
||||||
if (authCheck) {
|
if (authCheck) {
|
||||||
|
// Authenticate
|
||||||
req.isAuthed = true;
|
req.isAuthed = true;
|
||||||
logger.logger.info("auth.middleware: Authentication Successful");
|
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
req.isAuthed = false;
|
req.isAuthed = false;
|
||||||
logger.logger.info("auth.middleware: Authentication Failed");
|
logger.info("auth.middleware: Authentication Failed");
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
// Handle cases where UUID passed as an array
|
||||||
} else if (Array.isArray(id)) {
|
} else if (Array.isArray(id)) {
|
||||||
const authCheck = (await utils.isAuthed(id[0])) || false;
|
const authCheck = (await isAuthed(id[0])) || false;
|
||||||
if (authCheck) {
|
if (authCheck) {
|
||||||
req.isAuthed = true;
|
req.isAuthed = true;
|
||||||
logger.logger.warn(
|
|
||||||
"auth.middleware: UUID Passed as Array - Authentication Successful"
|
|
||||||
);
|
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
req.isAuthed = false;
|
req.isAuthed = false;
|
||||||
logger.logger.warn(
|
logger.warn(
|
||||||
"auth.middleware: UUID Passed as Array - Authentication Failed"
|
"auth.middleware: UUID Passed as Array - Authentication Failed"
|
||||||
);
|
);
|
||||||
next();
|
next();
|
||||||
|
@ -4,6 +4,7 @@ const ldbCtr = require("../controllers/ldb.controllers");
|
|||||||
|
|
||||||
// PIS
|
// PIS
|
||||||
router.get("/station/:id/:type", ldbCtr.getStation);
|
router.get("/station/:id/:type", ldbCtr.getStation);
|
||||||
|
router.get("/station/nearest/:latitude/:longitude", ldbCtr.getNearest);
|
||||||
router.get("/train/:searchType/:id", ldbCtr.getTrain);
|
router.get("/train/:searchType/:id", ldbCtr.getTrain);
|
||||||
|
|
||||||
module.exports = router;
|
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 dbPort = process.env.OWL_DB_PORT || 27017;
|
||||||
const dbHost = process.env.OWL_DB_HOST || "localhost";
|
const dbHost = process.env.OWL_DB_HOST || "localhost";
|
||||||
const uri = `mongodb://${dbUser}:${dbPass}@${dbHost}:${dbPort}`;
|
const uri = `mongodb://${dbUser}:${dbPass}@${dbHost}:${dbPort}`;
|
||||||
|
const connOpts = {
|
||||||
|
useUnifiedTopology: true,
|
||||||
|
authSource: "owlboard",
|
||||||
|
}
|
||||||
|
|
||||||
const { MongoClient } = require("mongodb");
|
const { MongoClient } = require("mongodb");
|
||||||
|
|
||||||
const client = new MongoClient(uri);
|
const client = new MongoClient(uri, connOpts);
|
||||||
const db = client.db(dbName);
|
const db = client.db(dbName);
|
||||||
|
|
||||||
async function query(collection, query, returnId = false) {
|
async function query(collection, query, returnId = false) {
|
||||||
|
@ -5,10 +5,11 @@ const util = require("../utils/ldb.utils");
|
|||||||
const san = require("../utils/sanitizer.utils");
|
const san = require("../utils/sanitizer.utils");
|
||||||
const db = require("../services/dbAccess.services");
|
const db = require("../services/dbAccess.services");
|
||||||
|
|
||||||
|
import { findStationsByDistancePipeline } from "../utils/ldbPipeline.utils";
|
||||||
import { logger } from "../utils/logger.utils";
|
import { logger } from "../utils/logger.utils";
|
||||||
|
|
||||||
import { transform as staffStationTransform } from "../utils/processors/ldb/staffStation";
|
import { transform as staffStationTransform } from "../utils/processors/ldb/staffStation";
|
||||||
import { msgCodes } from "../configs/errorCodes.configs";
|
|
||||||
|
|
||||||
const ldbKey = process.env.OWL_LDB_KEY;
|
const ldbKey = process.env.OWL_LDB_KEY;
|
||||||
const ldbsvKey = process.env.OWL_LDB_SVKEY;
|
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");
|
logger.error(err, "ldbService.get: Error, Unable to find CRS");
|
||||||
return {
|
return {
|
||||||
obStatus: "LOC_NOT_FOUND",
|
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);
|
const api = new ldb(ldbsvKey, true);
|
||||||
console.time(`Fetch Staff LDB for ${CRS.toUpperCase()}`);
|
console.time(`Fetch Staff LDB for ${CRS.toUpperCase()}`);
|
||||||
const result = await api.call(
|
let result
|
||||||
"GetArrivalDepartureBoardByCRS",
|
try {
|
||||||
options,
|
result = await staffApiCallRetry(
|
||||||
false,
|
api,
|
||||||
false
|
"GetArrivalDepartureBoardByCRS",
|
||||||
);
|
options,
|
||||||
console.log("\n\n\nORIGINAL DATA");
|
5,
|
||||||
console.log("\n" + JSON.stringify(result) + "\n\n\n");
|
);
|
||||||
|
} 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()}`);
|
console.timeEnd(`Fetch Staff LDB for ${CRS.toUpperCase()}`);
|
||||||
try {
|
try {
|
||||||
const _staffLdb = staffStationTransform(result);
|
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() {
|
async function getReasonCodeList() {
|
||||||
logger.debug("ldbService.getReasonCodeList: Fetching reason code list");
|
logger.debug("ldbService.getReasonCodeList: Fetching reason code list");
|
||||||
try {
|
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) {
|
async function getDateTimeString(date) {
|
||||||
const year = date.getFullYear(),
|
const year = date.getFullYear(),
|
||||||
month = String(date.getMonth() + 1).padStart(2, "0"),
|
month = String(date.getMonth() + 1).padStart(2, "0"),
|
||||||
@ -175,4 +214,5 @@ module.exports = {
|
|||||||
getServicesByOther,
|
getServicesByOther,
|
||||||
getReasonCodeList,
|
getReasonCodeList,
|
||||||
getReasonCode,
|
getReasonCode,
|
||||||
|
getNearestStations,
|
||||||
};
|
};
|
||||||
|
@ -10,12 +10,13 @@ import { queryAggregate } from "./dbAccess.services";
|
|||||||
import {
|
import {
|
||||||
getPartialEndTiplocMatchPipeline,
|
getPartialEndTiplocMatchPipeline,
|
||||||
getFullTiplocMatchPipeline,
|
getFullTiplocMatchPipeline,
|
||||||
|
getPartialStartTiplocMatchPipeline,
|
||||||
} from "../utils/pis.utils";
|
} from "../utils/pis.utils";
|
||||||
import { Document } from "mongodb";
|
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(
|
logger.debug(
|
||||||
`pisServices.findPisByOrigDest: Searching for Orig: ${start}, Dest: ${end}`
|
`pisServices.findPisByOrigDest: Searching for Orig: ${start}, Dest: ${end}`
|
||||||
);
|
);
|
||||||
@ -40,13 +41,13 @@ async function findPisByOrigDest(start: string, end: string) {
|
|||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findPisByCode(
|
export async function findPisByCode(
|
||||||
code: string
|
code: string
|
||||||
): Promise<OB_Pis_SimpleObject | null> {
|
): Promise<OB_Pis_SimpleObject | null> {
|
||||||
logger.debug(`pisServices.findPisByCode: Searching for PIS code: ${code}`);
|
logger.debug(`pisServices.findPisByCode: Searching for PIS code: ${code}`);
|
||||||
const cleanCode = clean.removeNonNumeric(code);
|
const cleanCode = clean.removeNonNumeric(code);
|
||||||
const query = {
|
const query = {
|
||||||
code: parseInt(cleanCode),
|
code: cleanCode,
|
||||||
};
|
};
|
||||||
const search = db.query("pis", query);
|
const search = db.query("pis", query);
|
||||||
return await search;
|
return await search;
|
||||||
@ -78,8 +79,12 @@ export async function findByTiplocArray(
|
|||||||
if (partialEnd) {
|
if (partialEnd) {
|
||||||
return convertDocument(partialEnd, "first");
|
return convertDocument(partialEnd, "first");
|
||||||
} else {
|
} else {
|
||||||
// Here, I should search for a partialStart match. For now return null.
|
const partialStart = await findPartialStartMatchByTiploc(tiplocArray);
|
||||||
return null;
|
if (partialStart) {
|
||||||
|
return convertDocument(partialStart, "last");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -103,6 +108,13 @@ async function findPartialEndMatchByTiploc(array: string[]): Promise<Document> {
|
|||||||
return res[0];
|
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 {
|
function convertDocument(doc: Document, skipType: string): OB_Pis_SimpleObject {
|
||||||
return {
|
return {
|
||||||
code: doc.code.toString(),
|
code: doc.code.toString(),
|
||||||
|
@ -1,34 +1,32 @@
|
|||||||
const auth = require("../utils/auth.utils");
|
const auth = require("../utils/auth.utils");
|
||||||
const db = require("./dbAccess.services");
|
const db = require("./dbAccess.services");
|
||||||
const mail = require("./mail.services");
|
const mail = require("./mail.services");
|
||||||
const clean = require("../utils/sanitizer.utils");
|
|
||||||
const domains = require("../configs/domains.configs");
|
|
||||||
const errors = require("../configs/errorCodes.configs");
|
const errors = require("../configs/errorCodes.configs");
|
||||||
|
|
||||||
import { logger } from "../utils/logger.utils";
|
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) {
|
async function createRegKey(body) {
|
||||||
logger.debug("registerServices.createRegKey: Incoming request");
|
logger.debug("registerServices.createRegKey: Incoming request");
|
||||||
if (body.email) {
|
if (body.email) {
|
||||||
const domain = await clean.getDomainFromEmail(body.email);
|
const domain = getDomainFromEmail(body.email);
|
||||||
logger.info(`registerServices: Registration request from: ${domain}`);
|
logger.info(`registerServices: Registration request from: ${domain}`);
|
||||||
if (domains.includes(domain)) {
|
if (validDomains.includes(domain)) {
|
||||||
logger.debug(`registerServices.createRegKey: Key from valid: ${domain}`);
|
logger.debug(`registerServices.createRegKey: Key from valid: ${domain}`);
|
||||||
const uuid = await auth.generateKey();
|
const key = generateCode()
|
||||||
db.addRegReq(uuid, domain);
|
db.addRegReq(key, domain)
|
||||||
const message = await auth.generateConfirmationEmail(body.email, uuid);
|
const message = await auth.generateConfirmationEmail(body.email, key);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
const err = new Error("Message generation error");
|
const err = new Error("Message Generation Error");
|
||||||
logger.error(
|
logger.error(err, "registerServices.createRegKey: Error generating email");
|
||||||
err,
|
|
||||||
"registerServices.createRegKey: Error generating email"
|
|
||||||
);
|
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
if ((await mail.send(message)) == true) {
|
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] };
|
return { status: 403, errorCode: 702, errorMsg: errors[702] };
|
||||||
} else {
|
} else {
|
||||||
@ -38,8 +36,9 @@ async function createRegKey(body) {
|
|||||||
|
|
||||||
async function regUser(req) {
|
async function regUser(req) {
|
||||||
// Add input validation
|
// Add input validation
|
||||||
logger.trace(`Read UUID: ${req.uuid}`);
|
const regCode = req.uuid.toLocaleUpperCase();
|
||||||
const res = await auth.checkRequest(req.uuid);
|
logger.trace(`Read UUID: ${regCode}`);
|
||||||
|
const res = await auth.checkRequest(regCode);
|
||||||
logger.debug(res, "registrationServices");
|
logger.debug(res, "registrationServices");
|
||||||
if (res.result) {
|
if (res.result) {
|
||||||
const uuid = await auth.generateKey();
|
const uuid = await auth.generateKey();
|
||||||
@ -52,6 +51,8 @@ async function regUser(req) {
|
|||||||
return { status: 401, errorCode: 703, errorMsg: errors[703] };
|
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) {
|
async function getUser(uuid) {
|
||||||
try {
|
try {
|
||||||
const filter = {
|
const filter = {
|
||||||
|
@ -8,8 +8,6 @@ async function buildJson() {
|
|||||||
let json = {};
|
let json = {};
|
||||||
json.count = {};
|
json.count = {};
|
||||||
// Async call all db queries
|
// 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 userCount = db.colCount("users");
|
||||||
const regCount = db.colCount("registrations");
|
const regCount = db.colCount("registrations");
|
||||||
const pisCount = db.colCount("pis");
|
const pisCount = db.colCount("pis");
|
||||||
@ -19,12 +17,8 @@ async function buildJson() {
|
|||||||
|
|
||||||
// Insert data
|
// Insert data
|
||||||
json.mode = process.env.NODE_ENV;
|
json.mode = process.env.NODE_ENV;
|
||||||
json.verBkend = vers.app;
|
|
||||||
json.verApi = vers.api;
|
|
||||||
json.host = os.hostname();
|
json.host = os.hostname();
|
||||||
// Await and insert async calls
|
// Await and insert async calls
|
||||||
json.dat = await counters;
|
|
||||||
json.ver = await versions;
|
|
||||||
json.count.users = await userCount;
|
json.count.users = await userCount;
|
||||||
json.count.reg = await regCount;
|
json.count.reg = await regCount;
|
||||||
json.count.pis = await pisCount;
|
json.count.pis = await pisCount;
|
||||||
@ -42,11 +36,9 @@ async function hits() {
|
|||||||
|
|
||||||
async function getVersions() {
|
async function getVersions() {
|
||||||
logger.debug("statsServices.getVersions: Fetching versions");
|
logger.debug("statsServices.getVersions: Fetching versions");
|
||||||
const dbMan = await db.query("versions", { target: "dbmanager" });
|
const mqClt = await db.query("versions", { target: "timetable-mgr" });
|
||||||
const mqClt = await db.query("versions", { target: "mq-client" });
|
|
||||||
const data = {
|
const data = {
|
||||||
backend: vers.app,
|
backend: vers.app,
|
||||||
"db-manager": dbMan[0]?.["version"] || "",
|
|
||||||
"mq-client": mqClt[0]?.["version"] || "",
|
"mq-client": mqClt[0]?.["version"] || "",
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
@ -55,11 +47,10 @@ async function getVersions() {
|
|||||||
async function statistics() {
|
async function statistics() {
|
||||||
logger.debug("statsServices.statistics: Fetching statistics");
|
logger.debug("statsServices.statistics: Fetching statistics");
|
||||||
|
|
||||||
const countersPromise = db.query("meta", { target: "counters" });
|
const timetablePromise = db.query("meta", { type: "CifMetadata" });
|
||||||
const timetablePromise = db.query("meta", { target: "timetable" });
|
const pisPromise = db.query("meta", { type: "PisMetadata" });
|
||||||
const pisPromise = db.query("meta", { target: "pis" });
|
|
||||||
const corpusPromise = db.query("meta", { target: "corpus" });
|
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 lengthUsersPromise = db.colCount("users");
|
||||||
const lengthRegistrationsPromise = db.colCount("registrations");
|
const lengthRegistrationsPromise = db.colCount("registrations");
|
||||||
@ -70,51 +61,37 @@ async function statistics() {
|
|||||||
const lengthReasonCodesPromise = db.colCount("reasonCodes");
|
const lengthReasonCodesPromise = db.colCount("reasonCodes");
|
||||||
|
|
||||||
const [
|
const [
|
||||||
counters,
|
|
||||||
timetable,
|
timetable,
|
||||||
pis,
|
pis,
|
||||||
corpus,
|
corpus,
|
||||||
reasonCodes,
|
|
||||||
lengthUsers,
|
lengthUsers,
|
||||||
lengthRegistrations,
|
lengthRegistrations,
|
||||||
lengthCorpus,
|
lengthCorpus,
|
||||||
lengthStations,
|
lengthStations,
|
||||||
lengthPis,
|
lengthPis,
|
||||||
lengthTimetable,
|
lengthTimetable,
|
||||||
lengthReasonCodes,
|
stations,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
countersPromise,
|
|
||||||
timetablePromise,
|
timetablePromise,
|
||||||
pisPromise,
|
pisPromise,
|
||||||
corpusPromise,
|
corpusPromise,
|
||||||
reasonCodesPromise,
|
|
||||||
lengthUsersPromise,
|
lengthUsersPromise,
|
||||||
lengthRegistrationsPromise,
|
lengthRegistrationsPromise,
|
||||||
lengthCorpusPromise,
|
lengthCorpusPromise,
|
||||||
lengthStationsPromise,
|
lengthStationsPromise,
|
||||||
lengthPisPromise,
|
lengthPisPromise,
|
||||||
lengthTimetablePromise,
|
lengthTimetablePromise,
|
||||||
lengthReasonCodesPromise,
|
stationsPromise,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hostname: os.hostname() || "Unknown",
|
hostname: os.hostname() || "Unknown",
|
||||||
runtimeMode: process.env.NODE_ENV || "Unknown",
|
runtimeMode: process.env.NODE_ENV || "Unknown",
|
||||||
reset: counters[0]["since"],
|
|
||||||
updateTimes: {
|
updateTimes: {
|
||||||
timetable: timetable[0]["updated"],
|
timetable: (timetable[0]["lastUpdate"]),
|
||||||
pis: pis[0]["updated"],
|
pis: pis[0]["lastUpdate"],
|
||||||
corpus: corpus[0]["updated"],
|
corpus: corpus[0]["updated_time"],
|
||||||
reasonCodes: reasonCodes[0]["updated"],
|
stations: stations[0]["lastUpdate"],
|
||||||
},
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
dbLengths: {
|
dbLengths: {
|
||||||
users: lengthUsers,
|
users: lengthUsers,
|
||||||
@ -123,7 +100,6 @@ async function statistics() {
|
|||||||
stations: lengthStations,
|
stations: lengthStations,
|
||||||
pis: lengthPis,
|
pis: lengthPis,
|
||||||
timetable: lengthTimetable,
|
timetable: lengthTimetable,
|
||||||
reasonCodes: lengthReasonCodes,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,9 @@ export async function findByHeadcode(
|
|||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
headcode: sanitizedHeadcode.toUpperCase(),
|
headcode: sanitizedHeadcode.toUpperCase(),
|
||||||
|
daysRun: { $in: [shortDay] },
|
||||||
scheduleStartDate: { $lte: searchDate },
|
scheduleStartDate: { $lte: searchDate },
|
||||||
scheduleEndDate: { $gte: searchDate },
|
scheduleEndDate: { $gte: searchDate },
|
||||||
daysRun: { $in: [shortDay] },
|
|
||||||
};
|
};
|
||||||
const pipeline = getFindByHeadcodePipeline(query);
|
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
|
// Set the correct date - whether a date or "now" was passed to function
|
||||||
let queryDate: Date;
|
let queryDate: Date;
|
||||||
if (date instanceof Date) {
|
if (date === 'now') {
|
||||||
|
queryDate = new Date();
|
||||||
|
} else if (date instanceof Date) {
|
||||||
queryDate = date;
|
queryDate = date;
|
||||||
} else {
|
} else {
|
||||||
queryDate = new Date();
|
queryDate = new Date(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build query
|
// Build query
|
||||||
const query = {
|
const query = {
|
||||||
trainUid: uid.toUpperCase(),
|
trainUid: uid.toUpperCase(),
|
||||||
|
daysRun: { $in: [getShortDay(queryDate)] },
|
||||||
scheduleStartDate: { $lte: queryDate },
|
scheduleStartDate: { $lte: queryDate },
|
||||||
scheduleEndDate: { $gte: queryDate },
|
scheduleEndDate: { $gte: queryDate },
|
||||||
daysRun: { $in: [getShortDay(queryDate)] },
|
|
||||||
};
|
};
|
||||||
const pipeline = getFindByTrainUidPipeline(query);
|
const pipeline = getFindByTrainUidPipeline(query);
|
||||||
|
|
||||||
@ -82,7 +84,6 @@ export async function findByTrainUid(
|
|||||||
} else {
|
} else {
|
||||||
pis = null;
|
pis = null;
|
||||||
}
|
}
|
||||||
// TODO: Format and return data, the function called is not yet complete
|
|
||||||
return formatTimetableDetail(services[0], pis);
|
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 {
|
export interface Request {
|
||||||
isAuthed: boolean;
|
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
|
// Checks whether a registration request key is valid
|
||||||
async function checkRequest(key: string) {
|
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 collection = "registrations";
|
||||||
const query = { uuid: key };
|
const query = { uuid: key };
|
||||||
const res = await db.query(collection, query);
|
const res = await db.query(collection, query);
|
||||||
@ -34,20 +33,33 @@ async function checkRequest(key: string) {
|
|||||||
|
|
||||||
// Creates an API key for a user
|
// Creates an API key for a user
|
||||||
async function generateKey() {
|
async function generateKey() {
|
||||||
// Needs testing & moving to 'register.utils' ??? Why does it need moving?
|
|
||||||
return crypt.randomUUID();
|
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) {
|
async function generateConfirmationEmail(eml: string, uuid: string) {
|
||||||
try {
|
try {
|
||||||
const htmlTpl = await fs.readFile("mail-templates/register.html", "utf-8");
|
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 htmlMin = await minifyMail(htmlStr);
|
||||||
const txtTpl = fs.readFile("mail-templates/register.txt", "utf-8");
|
const txtTpl = fs.readFile("mail-templates/register.txt", "utf-8");
|
||||||
return {
|
return {
|
||||||
to: eml,
|
to: eml,
|
||||||
subject: "OwlBoard Registration",
|
subject: "OwlBoard Registration",
|
||||||
text: (await txtTpl).replace(/>>ACCESSCODE<</g, uuid),
|
text: (await txtTpl).replace(/987654/g, uuid),
|
||||||
html: htmlMin,
|
html: htmlMin,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -64,6 +76,7 @@ module.exports = {
|
|||||||
generateKey,
|
generateKey,
|
||||||
generateConfirmationEmail,
|
generateConfirmationEmail,
|
||||||
checkRequest,
|
checkRequest,
|
||||||
|
generateCode
|
||||||
};
|
};
|
||||||
|
|
||||||
export { isAuthed, generateKey, generateConfirmationEmail, checkRequest };
|
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[]) {
|
export function getFullTiplocMatchPipeline(query: string[]) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ import type {
|
|||||||
OB_TrainTT_stopDetail,
|
OB_TrainTT_stopDetail,
|
||||||
Stop,
|
Stop,
|
||||||
} from "@owlboard/ts-types";
|
} from "@owlboard/ts-types";
|
||||||
import { logger } from "../../logger.utils";
|
|
||||||
|
|
||||||
export function formatTimetableDetail(
|
export function formatTimetableDetail(
|
||||||
service: Service,
|
service: Service,
|
||||||
@ -22,7 +21,7 @@ export function formatTimetableDetail(
|
|||||||
scheduleEnd: service.scheduleEndDate,
|
scheduleEnd: service.scheduleEndDate,
|
||||||
daysRun: service.daysRun,
|
daysRun: service.daysRun,
|
||||||
stops: formatStops(service.stops),
|
stops: formatStops(service.stops),
|
||||||
vstp: service.vstp,
|
serviceDetail: service.serviceDetail,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pis) {
|
if (pis) {
|
||||||
@ -33,6 +32,14 @@ export function formatTimetableDetail(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatStops(stops: Stop[]): OB_TrainTT_stopDetail[] {
|
function formatStops(stops: Stop[]): OB_TrainTT_stopDetail[] {
|
||||||
|
if (!stops) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stops.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanly coerce Stop[] to OB_TrainTT_stopDetail[]
|
// Cleanly coerce Stop[] to OB_TrainTT_stopDetail[]
|
||||||
const formattedStops: OB_TrainTT_stopDetail[] = [];
|
const formattedStops: OB_TrainTT_stopDetail[] = [];
|
||||||
|
|
||||||
@ -63,6 +70,22 @@ function formatStopTimes(stop: Stop): OB_TrainTT_stopDetail {
|
|||||||
if (stop.wttDeparture) {
|
if (stop.wttDeparture) {
|
||||||
formattedStop.wttDeparture = 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;
|
return formattedStop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
//const log = require('../utils/log.utils');
|
|
||||||
|
|
||||||
import { logger } from "./logger.utils";
|
import { logger } from "./logger.utils";
|
||||||
|
|
||||||
function removeNonAlphanumeric(inputString: string) {
|
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