newStaffLDB-API #48

Merged
fred.boniface merged 85 commits from newStaffLDB-API into main 2023-10-03 21:35:03 +01:00
61 changed files with 2498 additions and 520705 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
{
"GetStationBoardResult": {
"generatedAt": "2023-01-14T11:23:12.6558466+00:00",
"locationName": "Pilning",
"crs": "PIL",
"nrccMessages": {
"message": "\nPoor weather affecting services in Wales due to flooding on the railway More details can be found in <a href=\"https://t.co/uBU966PUmX\">Latest Travel News</a>."
},
"platformAvailable": "true"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
{
"service": [
{
"sta": "16:07",
"eta": "On time",
"operator": "South Western Railway",
"operatorCode": "SW",
"serviceType": "ferry",
"serviceID": "37782PHBR____",
"origin": {
"location": { "locationName": "Ryde Pier Head", "crs": "RYP" }
},
"destination": {
"location": { "locationName": "Portsmouth Harbour", "crs": "PMH" }
},
"previousCallingPoints": {
"callingPointList": {
"callingPoint": {
"locationName": "Ryde Pier Head",
"crs": "RYP",
"st": "15:45",
"et": "On time"
}
}
}
},
{
"std": "16:15",
"etd": "On time",
"operator": "South Western Railway",
"operatorCode": "SW",
"serviceType": "ferry",
"serviceID": "37746PHBR____",
"origin": {
"location": { "locationName": "Portsmouth Harbour", "crs": "PMH" }
},
"destination": {
"location": { "locationName": "Ryde Pier Head", "crs": "RYP" }
},
"subsequentCallingPoints": {
"callingPointList": {
"callingPoint": {
"locationName": "Ryde Pier Head",
"crs": "RYP",
"st": "16:37",
"et": "On time"
}
}
}
}
]
}

View File

@ -5,6 +5,7 @@ COPY ./.npmrc ./
RUN npm install
COPY . .
# Ideally the tests should be run separately in a CI/CD workflow rather than during the build
# Currently, it does prevent a container being published with failing tests
RUN npm run test
RUN npm run build

51
app.js
View File

@ -2,9 +2,12 @@
// Please see the included LICENSE file
console.log("Initialising OwlBoard");
const mode = process.env.NODE_ENV || "development";
// Logging
const logger = require("./src/utils/logger.utils");
logger.logger.info("Logger Initialised");
// External Requires
const express = require("express");
const app = express();
@ -16,8 +19,8 @@ const cors = require("cors");
const authenticate = require("./src/middlewares/auth.middlewares");
// Internal Requires
const log = require("./src/utils/logs.utils");
const version = require("./src/configs/version.configs");
const version = require("./src/configs/version.configs"); /*
{}{}{} REMOVE THESE FILES - THEY ARE NO LONGER REQUIRED - {}{}{}
const listRtr = require("./src/routes/list.routes");
const ldbRtr = require("./src/routes/ldb.routes");
const ldbsRtr = require("./src/routes/ldbs.routes");
@ -26,7 +29,7 @@ const findRtr = require("./src/routes/find.routes");
const issueRtr = require("./src/routes/issue.routes");
const statRtr = require("./src/routes/stats.routes");
const regRtr = require("./src/routes/registration.routes");
const pisRtr = require("./src/routes/pis.routes");
const pisRtr = require("./src/routes/pis.routes");*/
const trainRtr = require("./src/routes/train.routes");
const pis2Rtr = require("./src/routes/pis2.routes"); // API Version 2 Routes
const ref2Rtr = require("./src/routes/ref2.routes"); // API Version 2 Routes
@ -43,21 +46,19 @@ const limiter = rateLimit({
windowMs: 15 * (60 * 1000), // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: true, // Disable the `X-RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
// Print version number:
log.out(`app: Starting OwlBoard in ${mode} mode`, "init");
log.out(
`app: Starting OwlBoard - Backend Version: ${version.app} - ` +
`API versions: ${version.api}`,
"init"
);
logger.logger.info(`Starting version ${version.app} in ${mode} mode`);
// Remove X-Powered-By header:
app.disable("x-powered-by");
// Express Error Handling:
app.use((err, req, res, next) => {
const statusCode = err.statuscode || 500;
console.error(err.message, err.stack);
logger.logger.error(err, "Express Error");
res.status(statusCode).json({ message: err.message });
return;
});
@ -82,17 +83,17 @@ app.use("/api/v2/user", user2Rtr); // API Version 2
app.use("/misc", miscRtr); // Non public-api endpoints (Stats, Issue, etc.)
// Unauthenticated Routes
app.use("/api/v1/list", listRtr);
app.use("/api/v1/ldb", ldbRtr);
app.use("/api/v1/kube", kubeRtr);
app.use("/api/v1/find", findRtr);
app.use("/api/v1/issue", issueRtr);
app.use("/api/v1/stats", statRtr);
app.use("/api/v1/register", regRtr);
//app.use("/api/v1/list", listRtr); - Remove v1 List API - list.routes
//app.use("/api/v1/ldb", ldbRtr); - Remove v1 LDB API
//app.use("/api/v1/kube", kubeRtr); - Remove v1 Kube API
//app.use("/api/v1/find", findRtr); - Remove v1 Find API
//app.use("/api/v1/issue", issueRtr); - Remove v1 Issue API
//app.use("/api/v1/stats", statRtr); - Remove v1 Stats API
//app.use("/api/v1/register", regRtr); - Remove v1 Register API
// Authented Routes
app.use("/api/v1/ldbs", authenticate, ldbsRtr);
app.use("/api/v1/pis", authenticate, pisRtr);
//app.use("/api/v1/ldbs", authenticate, ldbsRtr); - Remove v1 LDBS API
//app.use("/api/v1/pis", authenticate, pisRtr); - Remove v1 PIS API
app.use("/api/v1/auth/test", authenticate, (req, res) =>
res.status(200).json({
status: "ok",
@ -110,12 +111,8 @@ mode === "development"
// Start Express
app.listen(srvPort, srvListen, (error) => {
if (!error) {
log.out(`app.listen: Listening on http://${srvListen}:${srvPort}`, "init");
log.out("app.listen: State - alive", "init");
logger.logger.info(`Listening on http://${srvListen}:${srvPort}`);
} else {
log.out(
`app.listen: Error occurred, server can't start ${error.message}`,
"err"
);
logger.logger.error(error, `Error starting server`);
}
});

View File

@ -4,19 +4,26 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
html {
background-color: #404c55;
background-image: radial-gradient(#2b343c, #404c55);
text-align: center;
width: 100%;
}
body {
margin: 0;
padding: 0;
}
a {
color: azure;
}
table {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
color: azure;
font-family: sans-serif;
text-align: center;
background-color: #404c55;
background-image: radial-gradient(#2b343c, #404c55);
}
#title {
height: 100px;
@ -53,14 +60,25 @@
<td>
<h1>Register for OwlBoard</h1>
<br />
<p>Use the link below to register for OwlBoard (Staff Version)</p>
<p>
Tap the button to register this device, or scan the barcode with
another device.
</p>
<br />
<a
href="https://owlboard.info/more/reg/submit?key=>>ACCESSCODE<<"
id="button"
>Register</a
>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>
Alternatively copy and paste the link:<br />https://owlboard.info/more/reg/submit?key=>>ACCESSCODE<<
</p>

View File

@ -2,7 +2,9 @@ Complete your OwlBoard (Staff) Registration using the link below.
https://owlboard.info/more/reg/submit?key=>>ACCESSCODE<<
Alternatively you can copy and paste the above link.
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<<
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.

3089
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,15 +8,15 @@
},
"license": "GPL-3.0-or-later",
"author": "Fred Boniface",
"main": "express.js",
"main": "app.js",
"scripts": {
"build": "tsc",
"run": "tsc && node dist/app.js",
"start": "node app.js",
"test": "jest"
"test": "jest",
"format": "npx prettier -w ."
},
"dependencies": {
"axios": "^1.2.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.18.2",
@ -27,11 +27,12 @@
"moment-timezone": "^0.5.43",
"mongodb": "^4.13.0",
"nodemailer": "^6.9.1",
"pino": "^8.15.1",
"redis": "^4.6.7",
"zlib": "^1.0.5"
},
"devDependencies": {
"@owlboard/ts-types": "^0.0.8",
"@owlboard/ts-types": "^0.0.9",
"@types/jest": "^29.5.3",
"eslint": "^8.39.0",
"jest": "^29.6.2",

View File

@ -1,6 +1,6 @@
// statusCodes should be a map, not an object
const statusCodes = {
export const statusCodes = {
400: "data not found",
700: "no authentication attempt",
701: "invalid credentials",
@ -14,5 +14,18 @@ const statusCodes = {
951: "unknown server error",
};
export const msgCodes = new Map<string, string>([
[
"LOC_NOT_FOUND",
"Location not found. If you are sure that the location exists, there may be a fault with the data provider.",
],
[
"USR_NOT_FOUND",
"User is not registered, consider regeristering for access to this resource",
],
["AUTH_ERR", "Authentication Error"],
["OK", "OK"],
]);
module.exports = statusCodes;
export { statusCodes };
//export { statusCodes };

View File

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

View File

@ -52,7 +52,7 @@ async function getStation(req, res, next) {
if (!req.isAuthed) {
const err = new Error("Unauthorized");
err.status = 401;
throw err;
return next(err);
}
res.json(await ldb.get(id, true));
} else {

View File

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

View File

@ -1,8 +0,0 @@
const express = require("express");
const router = express.Router();
// Controller Imports
// Routes
module.exports = router;

View File

@ -1,28 +0,0 @@
const express = require("express");
const router = express.Router();
/* Controller Imports */
// Live
// Timetable
// Ref
// User
/* Routes */
// Live
router.get("/live/station/:id/:type");
router.get("/live/train/:searchType/:id");
// Timetable
router.get("/timetable/train/:date/:searchType/:id");
// User
router.post("/user");
router.get("/user/:uuid");
router.delete("/user/:uuid");
module.exports = router;

View File

@ -1,5 +1,4 @@
/* global process */
const log = require("../utils/logs.utils"); // Log Helper
import { logger } from "../utils/logger.utils";
const dbUser = process.env.OWL_DB_USER || "owl";
const dbPass = process.env.OWL_DB_PASS || "twittwoo";
@ -15,27 +14,27 @@ const db = client.db(dbName);
async function query(collection, query, returnId = false) {
await client.connect();
log.out(`dbAccess.query: Connecting to collection: '${collection}'`, "info");
logger.trace(`dbAccess.query: Connecting to collection: '${collection}'`);
var qcoll = db.collection(collection);
var qcursor = qcoll.find(query);
if (!returnId) {
qcursor.project({ _id: 0 });
}
log.out(`dbAccess.query: Running Query: ${JSON.stringify(query)}`, "info");
logger.trace(query, "dbAccess.query: Runnung Query");
increment(collection);
return await qcursor.toArray();
let result = await qcursor.toArray();
logger.trace(result, "dbAccess.query: Response");
return result;
}
async function queryProject(collection, query, projection) {
await client.connect();
log.out(`dbAccess.queryProject: Connecting to col: '${collection}'`, "info");
logger.debug(`dbAccess.queryProject: Connecting to col: '${collection}'`);
const qcoll = db.collection(collection);
const qcursor = qcoll.find(query).project(projection);
log.out(
`dbAccess.query: Running Query: ${JSON.stringify(
query
)}, Projection: ${JSON.stringify(projection)}`,
"dbug"
logger.debug(
projection,
`dbAccess.query: Running Query: ${JSON.stringify(query)}`
);
increment(collection);
return await qcursor.toArray();
@ -43,17 +42,14 @@ async function queryProject(collection, query, projection) {
async function queryAggregate(collection, pipeline) {
await client.connect();
log.out(`dbAccess.queryProject: Connecting to col: '${collection}'`, "info");
log.out(
`dbAccess.query: Running Aggregation: ${JSON.stringify(pipeline)}`,
"dbug"
);
logger.debug(`dbAccess.queryProject: Connecting to col: '${collection}'`);
logger.trace(pipeline, "dbAccess.query: Running Aggregation");
increment(collection);
return await db.collection(collection).aggregate(pipeline).toArray();
}
async function increment(target) {
log.out(`dbAccess.increment: Incrementing counter for: ${target}`, "info");
logger.debug(`dbAccess.increment: Incrementing counter for: ${target}`);
await client.connect();
let col = db.collection("meta");
let update = {};
@ -63,7 +59,7 @@ async function increment(target) {
async function addUser(uuid, domain) {
// Needs testing
log.out("dbAccess.addUser: Adding user to database");
logger.debug("dbAccess.addUser: Adding user to database");
let doc = { uuid: uuid, domain: domain, atime: new Date() };
await client.connect();
let col = db.collection("users");
@ -76,7 +72,7 @@ async function addUser(uuid, domain) {
async function addRegReq(uuid, domain) {
// Needs testing
log.out("dbAccess.addRegReq: Adding registration request");
logger.debug("dbAccess.addRegReq: Adding registration request");
let doc = { uuid: uuid, time: new Date(), domain: domain };
await client.connect();
let col = db.collection("registrations");
@ -86,7 +82,7 @@ async function addRegReq(uuid, domain) {
async function userAtime(uuid) {
// Needs testing
log.out("dbAccess.userAtime: Updating access time for user");
logger.debug("dbAccess.userAtime: Updating access time for user");
let q = { uuid: uuid };
let n = { $set: { uuid: uuid, atime: new Date() } };
await client.connect();
@ -97,7 +93,7 @@ async function userAtime(uuid) {
// Deletes one single registration request entry from the DB
async function delRegReq(uuid) {
log.out("dbAccess.delRegReq: Deleting a Registration Request");
logger.debug("dbAccess.delRegReq: Deleting a Registration Request");
let collection = "registrations";
await client.connect();
let col = db.collection(collection);
@ -105,11 +101,13 @@ async function delRegReq(uuid) {
}
async function colCount(collection) {
log.out(`dbAccess.colCount: Counting entries in collection: ${collection}`);
logger.debug(
`dbAccess.colCount: Counting entries in collection: ${collection}`
);
await client.connect();
let col = db.collection(collection);
let count = col.countDocuments();
log.out(
logger.debug(
`dbAccess.colCount: Collection: ${collection} contains ${count}` +
" documents"
);
@ -127,3 +125,15 @@ module.exports = {
delRegReq,
colCount,
};
export {
query,
queryProject,
queryAggregate,
increment,
addUser,
userAtime,
addRegReq,
delRegReq,
colCount,
};

View File

@ -1,59 +0,0 @@
// Parse and return a find request
const log = require("../utils/logs.utils"); // Log Helper
const db = require("../services/dbAccess.services");
const san = require("../utils/sanitizer.utils");
// DB Query: query(collection, query)
// Define collection as all queries are for the "corpus" collection.
const col = "corpus";
async function name(id) {
log.out(`findServices.name: Finding station name: ${id}`, "info");
var name = san.cleanApiEndpointTxt(id.toUpperCase());
let query = { NLCDESC: name };
//var data = await db.query(col,query)
return await db.query(col, query);
}
async function crs(id) {
log.out(`findServices.crs: Finding crs: ${id}`, "info");
var crs = san.cleanApiEndpointTxt(id.toUpperCase());
let query = { "3ALPHA": crs };
//var data = await db.query(col,query)
return await db.query(col, query);
}
async function nlc(id) {
log.out(`findServices.nlc: Finding nlc: ${id}`, "info");
var nlc = san.cleanApiEndpointNum(id);
let query = { NLC: parseInt(nlc) };
log.out(`findServices.nlc: NLC Converted to int: ${query}`, "info");
//var data = await db.query(col,query)
return await db.query(col, query);
}
async function tiploc(id) {
log.out(`findServices.tiploc: Finding tiploc: ${id}`, "info");
var tiploc = san.cleanApiEndpointTxt(id.toUpperCase());
let query = { TIPLOC: tiploc };
//var data = await db.query(col,query)
return await db.query(col, query);
}
async function stanox(id) {
log.out(`findServices.stanox: Finding stanox: ${id}`, "info");
var stanox = san.cleanApiEndpointNum(id);
let query = { STANOX: String(stanox) };
//var data = await db.query(col,query)
return await db.query(col, query);
}
module.exports = {
name,
crs,
nlc,
tiploc,
stanox,
};

View File

@ -0,0 +1,55 @@
// Parse and return a find request
import { query } from "../services/dbAccess.services";
import {
cleanApiEndpointTxt,
cleanApiEndpointNum,
} from "../utils/sanitizer.utils";
import { logger } from "../utils/logger.utils";
// Define collection as all queries are for the "corpus" collection.
const col: string = "corpus";
async function name(id: string) {
logger.debug(`findServices.name: Finding station name: ${id}`);
var name = cleanApiEndpointTxt(id.toUpperCase());
let queryObj = { NLCDESC: name };
return await query(col, queryObj);
}
async function crs(id: string) {
logger.debug(`findServices.crs: Finding crs: ${id}`);
var crs = cleanApiEndpointTxt(id.toUpperCase());
let queryObj = { "3ALPHA": crs };
return await query(col, queryObj);
}
async function nlc(id: string) {
logger.debug(`findServices.nlc: Finding nlc: ${id}`);
var nlc = cleanApiEndpointNum(id);
let queryObj = { NLC: parseInt(nlc) };
logger.trace(`findServices.nlc: NLC Converted to int: ${query}`);
return await query(col, queryObj);
}
async function tiploc(id: string) {
logger.debug(`findServices.tiploc: Finding tiploc: ${id}`);
var tiploc = cleanApiEndpointTxt(id.toUpperCase());
let queryObj = { TIPLOC: tiploc };
return await query(col, queryObj);
}
async function stanox(id: string) {
logger.debug(`findServices.stanox: Finding stanox: ${id}`);
var stanox = cleanApiEndpointNum(id);
let queryObj = { STANOX: String(stanox) };
return await query(col, queryObj);
}
module.exports = {
name,
crs,
nlc,
tiploc,
stanox,
};

View File

@ -1,6 +1,4 @@
/* eslint-disable no-useless-escape */
const axios = require("axios");
const log = require("../utils/logs.utils");
import { logger } from "../utils/logger.utils";
const issueLabels = {
bug: 120,
@ -11,8 +9,7 @@ const issueLabels = {
};
async function processor(data) {
log.out("issueService.processor: Issue received", "info");
console.log(data); // TEMPORARY MEASURE
logger.debug("issueService.processor: Issue received");
let out = {};
out.labels = [issueLabels[data?.label] || 0, issueLabels["web-user"]];
out.title = data?.subject.replace(/<[^>]+>|[\*\$]/g, "");
@ -21,22 +18,32 @@ async function processor(data) {
}
async function sendToGitea(body) {
let key = process.env.OWL_GIT_ISSUEBOT;
let url = process.env.OWL_GIT_APIENDPOINT;
let opts = {
headers: {
Authorization: key,
},
};
var res = await axios.post(url, body, opts);
/* Need to read the output from the POST and pass the result upwards to the
client.*/
if (res.status == 201) {
log.out("issueService.sendToGitea: Issue sent to Gitea", "info");
return { status: res.status, message: "issue created" };
} else {
log.out(`issueService.sendToGitea: Fail to send issue: ${res.body}`, "err");
return { status: res.status, message: "issue not created" };
try {
const key = process.env.OWL_GIT_ISSUEBOT;
const url = process.env.OWL_GIT_APIENDPOINT;
const opts = {
method: "POST",
headers: {
Authorization: key,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
};
const res = await fetch(url, opts);
if (res.status === 201) {
logger.debug("issueService.sendToGitea: Issue created");
return { status: res.status, message: "issue created" };
} else {
logger.error(
`issueService.sendtoGitea: Error creating issue RETURN: ${res.status}`
);
return { status: res.status, message: "issue not created" };
}
} catch (err) {
logger.error(err, `issueService.sendToGitea`);
return { status: 500, message: "Internal Server Error" };
}
}

View File

@ -1,28 +0,0 @@
const testing = require("../services/mail.services");
const log = require("../utils/logs.utils");
async function getAlive() {
log.out("kubeServices.getAlive: alive hook checked", "info");
return { code: 200, state: { state: "alive", noise: "twit-twoo" } };
}
async function getReady() {
log.out("kubeServices.getReady: ready hook checked", "info");
testing.send({
to: "fred@fjla.uk",
subject: "OwlBoard Test",
txt: "This is a test message from OwlBoard (testing)",
});
return "not_implemented";
}
async function getTime() {
var now = new Date();
return { responseGenerated: now };
}
module.exports = {
getAlive,
getReady,
getTime,
};

View File

@ -0,0 +1,22 @@
import { logger } from "../utils/logger.utils";
async function getAlive() {
logger.trace("kubeServices.getAlive: alive hook checked");
return { code: 200, state: { state: "alive", noise: "twit-twoo" } };
}
async function getReady() {
logger.trace("kubeServices.getReady: ready hook checked");
return "not_implemented";
}
async function getTime() {
var now: Date = new Date();
return { responseGenerated: now };
}
module.exports = {
getAlive,
getReady,
getTime,
};

View File

@ -1,12 +1,14 @@
// Parse and return an LDB Request
const log = require("../utils/logs.utils"); // Log Helper
const ldb = require("ldbs-json");
const util = require("../utils/ldb.utils");
const san = require("../utils/sanitizer.utils");
const db = require("../services/dbAccess.services");
import { logger } from "../utils/logger.utils";
import { transform as staffStationTransform } from "../utils/translators/ldb/staffStation";
import { msgCodes } from "../configs/errorCodes.configs";
const ldbKey = process.env.OWL_LDB_KEY;
const ldbsvKey = process.env.OWL_LDB_SVKEY;
@ -16,7 +18,7 @@ async function get(id, staff = false) {
const obj = await util.checkCrs(cleanId);
try {
const crs = obj[0]["3ALPHA"];
log.out(`ldbService.get: Determined CRS for lookup to be: ${crs}`, "info");
logger.debug(`ldbService.get: Determined CRS for lookup to be: ${crs}`);
if (staff) {
const data = arrDepBoardStaff(crs);
db.increment("ldbsvws");
@ -27,16 +29,16 @@ async function get(id, staff = false) {
return await data;
}
} catch (err) {
log.out(`ldbService.get: Error, Unable to find CRS: ${err}`, "info");
logger.error(err, "ldbService.get: Error, Unable to find CRS");
return {
ERROR: "NOT_FOUND",
description: "The entered station was not found.",
obStatus: "LOC_NOT_FOUND",
obMsg: "UNABLE TO FIND MESSAGE",
};
}
}
async function arrDepBoard(CRS) {
log.out(`ldbService.arrDepBoard: Trying to fetch board for ${CRS}`, "info");
logger.trace(`ldbService.arrDepBoard: Trying to fetch board for ${CRS}`);
try {
const options = {
numRows: 10,
@ -46,7 +48,7 @@ async function arrDepBoard(CRS) {
let d = await api.call("GetArrDepBoardWithDetails", options, false, false);
return await util.cleanData(d);
} catch (err) {
log.out(`ldbService.arrDepBoard: Lookup Failed for: ${CRS}`, "warn");
logger.error(err, "ldbService.arrDepBoard: Lookup Failed");
return {
GetStationBoardResult: "not available",
Reason: `The CRS code ${CRS} is not valid`,
@ -55,7 +57,7 @@ async function arrDepBoard(CRS) {
}
async function arrDepBoardStaff(CRS) {
log.out(`ldbService.arrDepBoardStaff: Try to fetch board for ${CRS}`, "dbug");
logger.debug(`ldbService.arrDepBoardStaff: Try to fetch board for ${CRS}`);
try {
const options = {
numRows: 40,
@ -73,18 +75,24 @@ async function arrDepBoardStaff(CRS) {
false,
false
);
console.log("\n\n\nORIGINAL DATA");
console.log("\n" + JSON.stringify(result) + "\n\n\n");
console.timeEnd(`Fetch Staff LDB for ${CRS.toUpperCase()}`);
try {
const _staffLdb = staffStationTransform(result);
console.log("Transformation Test Successful");
console.log(JSON.stringify(_staffLdb));
logger.debug("StaffLDB Transformed");
logger.trace(_staffLdb, "StaffLDB Transformed");
return {
obStatus: "OK",
obMsg: "OK",
data: _staffLdb,
};
} catch (err) {
console.log("Transformation Test Failed: " + err);
logger.error(err, "Transformation Error");
}
return result;
} catch (err) {
log.out(`ldbService.arrDepBoardStaff: Lookup Failed for: ${CRS}`, "warn");
log.out(`ldbService.arrDepBoardStaff: ${err}`);
logger.error(err, "ldbService.arrDepBoardStaff error");
return {
GetStationBoardResult: "not available",
Reason: `The CRS code ${CRS} is not valid`,
@ -93,7 +101,7 @@ async function arrDepBoardStaff(CRS) {
}
async function getServiceByRID(rid) {
log.out(`ldbService.getServiceByRID: Finding RID: ${rid}`, "dbug");
logger.debug(`ldbService.getServiceByRID: Finding RID: ${rid}`);
try {
const options = {
rid: String(rid),
@ -101,12 +109,12 @@ async function getServiceByRID(rid) {
const api = new ldb(ldbsvKey, true);
return await api.call("GetServiceDetailsByRID", options, false, false);
} catch (err) {
log.out(`ldbService.queryService: ${err}`, "EROR");
logger.error(err, `ldbService.queryService`);
}
}
async function getServicesByOther(id) {
log.out(`ldbService.getServiceByOther: Finding services: ${id}`, "dbug");
logger.debug(`ldbService.getServiceByOther: Finding services: ${id}`);
try {
const options = {
serviceID: id,
@ -115,30 +123,30 @@ async function getServicesByOther(id) {
const api = new ldb(ldbsvKey, true);
return await api.call("QueryServices", options, false, false);
} catch (err) {
log.out(`ldbService.getServiceByOther: Error: ${err}`, "EROR");
logger.error(err, "ldbService.getServiceByOther");
return false;
}
}
async function getReasonCodeList() {
log.out("ldbService.getReasonCodeList: Fetching reason code list", "eror");
logger.debug("ldbService.getReasonCodeList: Fetching reason code list");
try {
const dbFilter = {};
return await db.query("reasonCodes", dbFilter, false);
} catch (err) {
log.out(`ldbService.getReasonCodeList: ${err}`, "eror");
logger.error(err, "ldbService.getReasonCodeList");
}
}
async function getReasonCode(code) {
log.out(`ldbService.getReasonCode: Fetching reason code ${code}`, "dbug");
logger.debug(`ldbService.getReasonCode: Fetching reason code ${code}`);
try {
const dbFilter = {
code: code,
};
return await db.query("reasonCodes", dbFilter, false);
} catch (err) {
log.out(`ldbService.getReasonCode: ${err}`, "eror");
logger.error(err, "ldbService.getReasonCode");
}
}

View File

@ -1,19 +0,0 @@
const log = require("../utils/logs.utils"); // Log Helper
const db = require("../services/dbAccess.services");
async function getStations() {
var out = db.query("stations");
log.out("listServices.getStations: Fetching stations list", "info");
return await out;
}
async function getCorpus() {
var out = db.query("corpus");
log.out("listServices.getCorpus: Fetching CORPUS list", "info");
return await out;
}
module.exports = {
getStations,
getCorpus,
};

View File

@ -0,0 +1,19 @@
import { query } from "./dbAccess.services";
import { logger } from "../utils/logger.utils";
async function getStations() {
var out = query("stations");
logger.debug("listServices.getStations: Fetching stations list");
return await out;
}
async function getCorpus() {
var out = query("corpus");
logger.debug("listServices.getCorpus: Fetching CORPUS list");
return await out;
}
module.exports = {
getStations,
getCorpus,
};

View File

@ -1,37 +0,0 @@
const log = require("../utils/logs.utils");
const mail = require("nodemailer"); //>> Probs wrong
const fromAddr = process.env.OWL_EML_FROM;
const smtpUser = process.env.OWL_EML_USER;
const smtpPass = process.env.OWL_EML_PASS;
const smtpHost = process.env.OWL_EML_HOST;
const smtpPort = process.env.OWL_EML_PORT;
let transporter = mail.createTransport({
host: smtpHost,
port: smtpPort,
secure: false, // Must be false for STARTTLS on port 587
auth: {
user: smtpUser,
pass: smtpPass,
},
});
/* 'message' is an object containing string values for:
*to, cc, bcc, *subject, *txt, html. * denotes required */
async function send(message) {
log.out("mailServices.send: Message send request received", "info");
message.from = fromAddr;
try {
var res = await transporter.sendMail(message);
} catch (err) {
log.out(`mailServices.send: Message send failed: ${err}`, "err");
return false;
}
log.out(`mailServices.send: SMTP Response: ${res.response}`);
return true;
}
module.exports = {
send,
};

View File

@ -0,0 +1,45 @@
const mail = require("nodemailer");
import { logger } from "../utils/logger.utils";
export interface msgDescriptor {
to: string;
cc?: string;
bcc?: string;
subject: string;
txt: string;
html?: string;
from: string;
}
const fromAddr = process.env.OWL_EML_FROM;
const smtpUser = process.env.OWL_EML_USER;
const smtpPass = process.env.OWL_EML_PASS;
const smtpHost = process.env.OWL_EML_HOST;
const smtpPort = process.env.OWL_EML_PORT;
const transporter = mail.createTransport({
host: smtpHost,
port: smtpPort,
secure: false, // Must be false for STARTTLS on port 587 which is always secure
auth: {
user: smtpUser,
pass: smtpPass,
},
});
async function send(message: msgDescriptor): Promise<boolean> {
logger.debug("mailServices.send: Message send request received");
message.from = fromAddr || "no-reply@owlboard.info";
try {
var res = await transporter.sendMail(message);
} catch (err) {
logger.error(err, "mailServices.send: Message send failed");
return false;
}
logger.debug(res.response, "mailServices.send: SMTP Response");
return true;
}
module.exports = {
send,
};

View File

@ -1,16 +1,15 @@
// Finds PIS Codes using DB Lookups
const db = require("../services/dbAccess.services");
const log = require("../utils/logs.utils");
const clean = require("../utils/sanitizer.utils");
import { logger } from "../utils/logger.utils";
const supported = ["GW", "UK"];
async function findPisByOrigDest(start, end) {
log.out(
"pisServices.findPisByOrigDest: Searching for PIS for Orig: " +
`${start}, Dest: ${end}`,
"dbug"
logger.debug(
`pisServices.findPisByOrigDest: Searching for Orig: ${start}, Dest: ${end}`
);
const firstCrs = clean.cleanApiEndpointTxt(start.toLowerCase());
const lastCrs = clean.cleanApiEndpointTxt(end.toLowerCase());
@ -33,7 +32,7 @@ async function findPisByOrigDest(start, end) {
}
async function findPisByCode(code) {
log.out(`pisServices.findPisByCode: Searching for PIS code: ${code}`, "dbug");
logger.debug(`pisServices.findPisByCode: Searching for PIS code: ${code}`);
const cleanCode = clean.removeNonNumeric(code);
const query = {
code: parseInt(cleanCode),

View File

@ -1,4 +1,3 @@
const log = require("../utils/logs.utils");
const auth = require("../utils/auth.utils");
const db = require("./dbAccess.services");
const mail = require("./mail.services");
@ -6,23 +5,24 @@ const clean = require("../utils/sanitizer.utils");
const domains = require("../configs/domains.configs");
const errors = require("../configs/errorCodes.configs");
import { logger } from "../utils/logger.utils";
async function createRegKey(body) {
log.out("registerServices.createRegKey: Incoming request", "INFO");
logger.debug("registerServices.createRegKey: Incoming request");
if (body.email) {
const domain = await clean.getDomainFromEmail(body.email);
log.out(`registerServices: Registration request from: ${domain}`, "info");
logger.info(`registerServices: Registration request from: ${domain}`);
if (domains.includes(domain)) {
log.out(
`registerServices.createRegKey: Key from valid: ${domain}`,
"info"
);
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);
if (!message) {
const err = new Error("Message generation error");
log.out("registerServices.createRegKey: Error generating email", "err");
log.out(err, "err");
logger.error(
err,
"registerServices.createRegKey: Error generating email"
);
return 500;
}
if ((await mail.send(message)) == true) {
@ -38,10 +38,9 @@ async function createRegKey(body) {
async function regUser(req) {
// Add input validation
log.out(`Read UUID: ${req.uuid}`, "dbug");
log.out(`registrationServices.regUser: Request from: ${req.uuid}`, "info");
logger.trace(`Read UUID: ${req.uuid}`);
const res = await auth.checkRequest(req.uuid);
log.out(`registrationServices.regUser: ${JSON.stringify(res)}`, "info");
logger.debug(res, "registrationServices");
if (res.result) {
const uuid = await auth.generateKey();
const apiKey = await db.addUser(uuid, res.domain);
@ -54,7 +53,6 @@ async function regUser(req) {
}
async function getUser(uuid) {
log.out("registrationServices: Finding user for given UUID", "dbug");
try {
const filter = {
uuid: uuid,

View File

@ -1,8 +1,9 @@
const log = require("../utils/logs.utils"); // Log Helper
const db = require("../services/dbAccess.services");
const os = require("os");
const vers = require("../configs/version.configs");
import { logger } from "../utils/logger.utils";
async function buildJson() {
let json = {};
json.count = {};
@ -34,14 +35,13 @@ async function buildJson() {
}
async function hits() {
log.out("statsServices.hits: Statistics Requested", "info");
logger.debug("statsServices.hits: Statistics Requested");
const out = await buildJson();
log.out(`statsServices.hits: Sending Data: ${JSON.stringify(out)}`, "info");
return out;
}
async function getVersions() {
log.out("statsServices.getVersions: Fetching versions", "info");
logger.debug("statsServices.getVersions: Fetching versions");
const dbMan = await db.query("versions", { target: "dbmanager" });
const mqClt = await db.query("versions", { target: "mq-client" });
const data = {
@ -53,7 +53,7 @@ async function getVersions() {
}
async function statistics() {
log.out("statsServices.statistics: Fetching statistics", "info");
logger.debug("statsServices.statistics: Fetching statistics");
const countersPromise = db.query("meta", { target: "counters" });
const timetablePromise = db.query("meta", { target: "timetable" });

View File

@ -1,15 +1,13 @@
const log = require("../utils/logs.utils");
const db = require("./dbAccess.services");
const clean = require("../utils/sanitizer.utils");
const pis = require("../services/pis.services");
const { filter } = require("compression");
import { logger } from "../utils/logger.utils";
async function findByHeadcodeToday(headcode) {
const sanitizedHeadcode = clean.removeNonAlphanumeric(headcode).toUpperCase();
log.out(
"trainServiceServices.findByHeadcode: Searching for headcode " +
sanitizedHeadcode,
"dbug"
logger.debug(
`trainServiceServices.findByHeadcode: Searching for headcode ${sanitizedHeadcode}`
);
const now = new Date();
const dayMap = ["su", "m", "t", "w", "th", "f", "s"];
@ -43,10 +41,8 @@ async function findByHeadcodeToday(headcode) {
async function findByHeadcode(date, headcode) {
const sanitizedHeadcode = clean.removeNonAlphanumeric(headcode).toUpperCase();
log.out(
"trainServiceServices.findByHeadcode: Searching for headcode " +
sanitizedHeadcode,
"dbug"
logger.debug(
`trainServiceServices.findByHeadcode: Searching for headcode ${sanitizedHeadcode}`
);
let searchDate;
if (date === "now") {
@ -82,7 +78,6 @@ async function findByHeadcode(date, headcode) {
},
];
const queryData = await db.queryAggregate("timetable", pipeline);
console.log(JSON.stringify(queryData));
let filteredData = filterServices(queryData);
return await filteredData;
}

View File

@ -1,19 +1,18 @@
const logs = require("../utils/logs.utils");
const crypt = require("crypto");
const db = require("../services/dbAccess.services");
const fs = require("fs/promises");
import { minifyMail } from "./minify.utils";
import { logger } from "./logger.utils";
// Checks users registration key against issued keys
async function isAuthed(uuid: string) {
async function isAuthed(uuid: string): Promise<boolean> {
// Needs testing
const q = { uuid: uuid };
const q = {
uuid: uuid,
};
const res = await db.query("users", q);
logs.out(
"authUtils.checkUser: DB Query answer: " + JSON.stringify(res[0]),
"dbug"
);
logger.debug(res, "checkUser: DB Query Result");
const authorized = res && res[0] && res[0].domain;
if (authorized) db.userAtime(uuid);
return authorized;
@ -21,13 +20,11 @@ async function isAuthed(uuid: string) {
// 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);
logs.out(
"authUtils.checkRequest: DB Query result: " + JSON.stringify(res),
"dbug"
);
logger.debug(res, "checkRequest: DB Lookup result");
const result =
res.length > 0 && res[0].time
? { result: true, domain: res[0].domain }
@ -37,7 +34,7 @@ async function checkRequest(key: string) {
// Creates an API key for a user
async function generateKey() {
// Needs testing & moving to 'register.utils'
// Needs testing & moving to 'register.utils' ??? Why does it need moving?
return crypt.randomUUID();
}
@ -54,9 +51,9 @@ async function generateConfirmationEmail(eml: string, uuid: string) {
html: htmlMin,
};
} catch (err) {
logs.out(
"mailServices.generateConfirmationEmail: Error reading template, " + err,
"err"
logger.error(
err,
"generateConfirmationEmail: Error rendering email templates"
);
return false;
}

View File

@ -18,6 +18,7 @@ async function checkCrs(input = "") {
}
// Needs to be moved to the frontend `ensureArray() func`
// Usage of this function should be migrated to the `translator` utilities.
async function cleanMessages(input) {
log.out("ldbUtils.cleanMessages: Deprecated function has been called", "err");
var out = [];

19
src/utils/logger.utils.ts Normal file
View File

@ -0,0 +1,19 @@
import pino from "pino";
const runtime = process.env.NODE_ENV;
let level: string;
if (runtime === "production") {
level = "info";
} else {
level = "debug";
}
export const logger = pino({
level: level,
formatters: {
level: (label) => {
return { level: label.toUpperCase() };
},
},
timestamp: pino.stdTimeFunctions.isoTime,
});

View File

@ -1,7 +1,11 @@
import { logger } from "./logger.utils";
const htmlShrink = require("html-minifier").minify;
const juice = require("juice");
// Inlines styles and minifies the inlined HTML
async function minifyMail(input: string): Promise<string> {
logger.trace("minifyMail: Minifying mail output");
const inlined: string = juice(input);
return htmlShrink(inlined, {
removeComments: true,

View File

@ -1,4 +1,7 @@
import { logger } from "./logger.utils";
export function removeNewlineAndPTag(input: string): string {
logger.debug("removeNewlineAndPTag: Cleaning string");
const regex = /[\n\r]|<\/?p[^>]*>/g;
return input.replace(regex, function (match) {
if (match === "\n" || match === "\r") {

View File

@ -1,14 +1,19 @@
//const log = require('../utils/log.utils');
import { logger } from "./logger.utils";
function removeNonAlphanumeric(inputString: string) {
logger.debug("removeNonAlphanumeric: Sanitizing string");
return inputString.replace(/[^a-zA-Z0-9]/g, "");
}
function removeNonAlpha(inputString: string) {
logger.debug("removeNonAlpha: Sanitizing string");
return inputString.replace(/[^a-zA-Z]/g, "");
}
function removeNonNumeric(inputString: string) {
logger.debug("removeNonNumeric: Sanitizing string");
return inputString.replace(/[^0-9]/g, "");
}
@ -16,15 +21,17 @@ const cleanApiEndpointTxt = removeNonAlpha;
const cleanApiEndpointNum = removeNonAlphanumeric;
function cleanNrcc(input: string) {
logger.error("DEPRECATED FUNCTION", "cleanNrcc: Converting NRCC Data");
// Remove newlines and then <p> tags from input
const cleanInput = input.replace(/[\n\r]/g, "").replace(/<\/?p[^>]*>/g, "");
return cleanInput;
}
function getDomainFromEmail(mail: string) {
logger.debug("getDomainFromEmail: Obtaining domain from email address");
// Needs testing
let split = mail.split("@");
return split[1];
return split[1].toLowerCase();
}
module.exports = {

View File

@ -1,14 +1,19 @@
function unixLocal(unix: number) {
import { logger } from "./logger.utils";
function unixLocal(unix: number): string {
logger.trace(`unixLocal: Converting time: ${unix}`);
var jsTime = unix * 1000;
var dt = new Date(jsTime);
return dt.toLocaleString();
}
function jsUnix(js: number) {
var preRound = js / 1000;
return Math.round(preRound);
function jsUnix(js: number): number {
logger.trace(`jsUnix: Converting time: ${js}`);
return Math.floor(js / 1000);
}
export { jsUnix, unixLocal };
module.exports = {
unixLocal,
jsUnix,

View File

@ -8,6 +8,8 @@ import type {
import { tz } from "moment-timezone";
import { removeNewlineAndPTag } from "../../newSanitizer";
import { logger } from "../../logger.utils";
/// I do not yet have a type defined for any of the input object
export function transform(input: any): StaffLdb | null {
console.time("StaffLdb Transformation");
@ -24,20 +26,26 @@ export function transform(input: any): StaffLdb | null {
ferryServices: transformTrainServices(data?.ferryServices) || undefined,
};
console.timeEnd("StaffLdb Transformation");
return output;
if (output.locationName !== "Not Found") {
return output;
}
} catch (err) {
console.log("utils/translators/ldb/staffLdb.transform: Caught Error");
console.log("Unable to parse data, assuming no data: " + err);
logger.error(err, "utils/translators/ldb/staffLdb.transform");
}
console.timeEnd("StaffLdb Transformation");
return null;
}
function transformDateTime(input: string): Date {
logger.trace("utils/translators/ldb/staffLdb.transformDateTime: Running");
return new Date(input);
}
function transformNrcc(input: any): NrccMessage[] | undefined {
logger.trace("utils/translators/ldb/staffLdb.transformNrcc: Running");
if (input === undefined) {
return input;
}
let output: NrccMessage[] = [];
let messages = input;
if (!Array.isArray(input?.message)) {
@ -57,6 +65,9 @@ function transformNrcc(input: any): NrccMessage[] | undefined {
}
function transformTrainServices(input: any): TrainServices[] {
logger.trace(
"utils/translators/ldb/staffLdb.transformTrainServices: Running"
);
let services: any = input?.service;
let output: TrainServices[] = [];
if (services === undefined) {
@ -66,6 +77,7 @@ function transformTrainServices(input: any): TrainServices[] {
services = [input.service];
}
for (const service of services) {
const times = parseTimes(service);
const trainService: TrainServices = {
rid: service?.rid,
uid: service?.uid,
@ -73,7 +85,7 @@ function transformTrainServices(input: any): TrainServices[] {
operatorCode: service?.operatorCode || "UK",
platform: service?.platform || "-",
platformIsHidden: service?.platformIsHidden,
serviceIsSupressed: service?.serviceIsSupressed,
serviceIsSupressed: checkIsSupressed(service),
origin: transformLocation(service?.origin),
destination: transformLocation(service?.destination),
length: calculateLength(service),
@ -82,12 +94,12 @@ function transformTrainServices(input: any): TrainServices[] {
delayReason: service?.delayReason,
arrivalType: service?.arrivalType,
departureType: service?.departureType,
sta: transformUnspecifiedDateTime(service?.sta),
eta: transformUnspecifiedDateTime(service?.eta),
ata: transformUnspecifiedDateTime(service?.ata),
std: transformUnspecifiedDateTime(service?.std),
etd: transformUnspecifiedDateTime(service?.etd),
atd: transformUnspecifiedDateTime(service?.atd),
sta: times.sta,
eta: times.eta,
ata: times.ata,
std: times.std,
etd: times.etd,
atd: times.atd,
};
Object.keys(trainService).forEach(
(key) => trainService[key] === undefined && delete trainService[key]
@ -97,7 +109,17 @@ function transformTrainServices(input: any): TrainServices[] {
return output;
}
function checkIsSupressed(service: TrainServices): string | undefined {
logger.trace("utils/translators/ldb/staffStation.checkIsSupressed: Running");
if (service.serviceIsSupressed === "true" || service.isPassengerService === "false") {
return "true";
} else {
return undefined;
}
}
function transformLocation(input: any): ServiceLocation[] {
logger.trace("utils/translators/ldb/staffStation.transformLocation: Running");
let output: ServiceLocation[] = [];
let locations: any[] = input.location;
if (!Array.isArray(input.location)) {
@ -116,6 +138,7 @@ function transformLocation(input: any): ServiceLocation[] {
}
export function calculateLength(input: any): number | undefined {
logger.trace("utils/translators/ldb/staffStation.calculateLength: Running");
let length: number;
if (input?.length) {
length = input.length;
@ -129,9 +152,66 @@ export function calculateLength(input: any): number | undefined {
}
function transformUnspecifiedDateTime(input: string): Date | undefined {
logger.trace(
"utils/translators/ldb/staffStation.transformUnspecifiedDateTime: Running"
);
if (!input) {
return undefined;
}
const date = tz(input, "Europe/London");
const date = tz(input, "Europe/London"); // Want to be creating a moment object using moment.tz(...)
return date.toDate();
}
function parseTimes(service: TrainServices) {
logger.trace("utils/translators/ldb/staffStation.parseTimes: Running");
let { sta, eta, ata, std, etd, atd } = Object.fromEntries(
Object.entries(service).map(([key, value]) => [
key,
transformUnspecifiedDateTime(value),
])
);
let etaResult: Date | undefined | string = eta;
let ataResult: Date | undefined | string = ata;
let etdResult: Date | undefined | string = etd;
let atdResult: Date | undefined | string = atd;
if (sta) {
if (
eta !== undefined &&
Math.abs(eta.getTime() - sta.getTime()) / 60000 <= 1.5
) {
etaResult = "RT";
}
if (
ata !== undefined &&
Math.abs(ata.getTime() - sta.getTime()) / 60000 <= 1.5
) {
ataResult = "RT";
}
}
if (std) {
if (
etd !== undefined &&
Math.abs(etd.getTime() - std.getTime()) / 60000 <= 1.5
) {
etdResult = "RT";
}
if (
atd !== undefined &&
Math.abs(atd.getTime() - std.getTime()) / 60000 <= 1.5
) {
atdResult = "RT";
}
}
return {
sta: sta,
eta: etaResult,
ata: ataResult,
std: std,
etd: etdResult,
atd: atdResult,
};
}

View File

@ -1,5 +1,7 @@
import { getDomainFromEmail } from "../../src/utils/sanitizer.utils";
import { removeNonNumeric } from "../../src/utils/sanitizer.utils";
import { removeNonAlpha } from "../../src/utils/sanitizer.utils";
import { removeNonAlphanumeric } from "../../src/utils/sanitizer.utils";
describe("Sanitize Email", () => {
const inputs = [
@ -9,6 +11,7 @@ describe("Sanitize Email", () => {
"I%Have{Special}%Characters@example.com",
"Basic.address@example.com",
`"very.(),:;<>[]\".VERY.\"very\ \"very\".unusual"@example.com`,
"THIS_EMAIL_CONTAINS_CAPITALISED_DOMAIN@EXAMPLE.COM",
];
const expectedOutput = "example.com";
for (const addr of inputs) {
@ -29,3 +32,31 @@ describe("Remove non-numeric", () => {
});
}
});
describe("Remove non-Alpha", () => {
const inputs = ["DROP/*comment*/sampletable", "10; DROP TABLE members /*"];
const outputs = ["DROPcommentsampletable", "DROPTABLEmembers"];
for (const key in inputs) {
const input = inputs[key];
const desired = outputs[key];
test(`Should return with only letters: ${key}`, () => {
expect(removeNonAlpha(input)).toEqual(desired);
});
}
});
describe("Remove non-alphanumeric", () => {
const inputs = [
"DROP/*comment*/sampletable",
"10; DROP TABLE members /*",
"1F44",
];
const outputs = ["DROPcommentsampletable", "10DROPTABLEmembers", "1F44"];
for (const key in inputs) {
const input = inputs[key];
const desired = outputs[key];
test(`Should return with only alphanumeric: ${key}`, () => {
expect(removeNonAlphanumeric(input)).toEqual(desired);
});
}
});

View File

@ -0,0 +1,17 @@
import { jsUnix, unixLocal } from "../../src/utils/timeConvert.utils";
describe("Time Conversion", () => {
test("Should return unix time (seconds)", () => {
const now = new Date();
const nowJs = now.getTime();
const nowUnix = Math.floor(now.getTime() / 1000);
expect(jsUnix(nowJs)).toEqual(nowUnix);
});
test("Should return locale date string", () => {
const now = new Date();
const nowUnix = Math.floor(now.getTime() / 1000);
const result = now.toLocaleString();
expect(unixLocal(nowUnix)).toEqual(result);
});
});

View File

@ -53,4 +53,51 @@ export const inputs: any[] = [
},
},
},
{
GetBoardResult: {
generatedAt: "2023-08-01T20:37:05.559123+01:00",
locationName: "Railway Station",
crs: "RLY",
stationManager: "Network Rail",
stationManagerCode: "RT",
isTruncated: "true",
trainServices: {
service: [
{
rid: "202308017159276",
uid: "G59276",
trainid: "1M83",
sdd: "2023-08-01",
operator: "CrossCountry",
operatorCode: "XC",
sta: "2023-08-01T20:24:00",
ata: "2023-08-01T20:27:22",
arrivalType: "Actual",
std: "2023-08-01T20:35:00",
etd: "2023-08-01T20:35:00",
departureType: "Estimated",
departureSource: "Darwin",
platform: "5",
length: "10",
origin: {
location: {
locationName: "Plymouth",
crs: "PLY",
tiploc: "PLYMTH",
},
},
destination: {
location: {
locationName: "Birmingham New Street",
crs: "BHM",
tiploc: "BHAMNWS",
},
},
category: "XX",
activities: "T",
},
],
},
},
},
];

View File

@ -26,7 +26,40 @@ export const outputs: StaffLdb[] = [
ata: expect.any(Date),
arrivalType: "Actual",
std: expect.any(Date),
etd: expect.any(Date),
etd: "RT",
departureType: "Estimated",
platform: "5",
length: 10,
origin: [
{
tiploc: "PLYMTH",
},
],
destination: [
{
tiploc: "BHAMNWS",
},
],
},
],
busServices: [],
ferryServices: [],
},
{
generatedAt: expect.any(Date),
locationName: "Railway Station",
stationManagerCode: "RT",
trainServices: [
{
rid: "202308017159276",
uid: "G59276",
trainid: "1M83",
operatorCode: "XC",
sta: expect.any(Date),
ata: expect.any(Date),
arrivalType: "Actual",
std: expect.any(Date),
etd: "RT",
departureType: "Estimated",
platform: "5",
length: 10,

View File

@ -25,7 +25,7 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"module": "Node16" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node16" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
@ -52,11 +52,11 @@
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
"removeComments": true /* Disable emitting comments. */,
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */