Skip to content

Commit

Permalink
Migrate NRE departures script to JavaScript (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenjaminEHowe authored Jul 17, 2024
1 parent 8796ec0 commit a43baaf
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ _site
.jekyll-metadata
vendor
node_modules

# wrangler
.dev.vars
.wrangler
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The easiest way to run Jekyll is using Docker:

### Using Wrangler

To test some of the dynamic aspects of the website (e.g. [Cloudflare Pages Functions](https://developers.cloudflare.com/pages/functions/)), it is necessary to use [Wrangler](https://developers.cloudflare.com/workers/wrangler/).
To test some of the dynamic aspects of the website (e.g. [Cloudflare Pages Functions](https://developers.cloudflare.com/pages/functions/)), it is necessary to use [Wrangler](https://developers.cloudflare.com/workers/wrangler/). You may also need [a local `.dev.vars` file](https://developers.cloudflare.com/pages/functions/bindings/#interact-with-your-secrets-locally) containing secrets.

To set up a local database:

Expand Down
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ include:
- _redirects

exclude:
- .wrangler
- functions
- functions-src
- sql
Expand Down
121 changes: 121 additions & 0 deletions functions/api/nreDepartures/v1/[[departures]].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { USER_AGENT } from "../../../../functions-src/util";

// TODO: allow these values to be customised through request params
const NUM_SERVICES = 4;
const TIME_OFFSET = 5;

export async function onRequest(context) {
if (context.request.method !== "GET") {
return new Response("Invalid request method", { status: 405 });
}

const crsLocation = context.params.departures[0];
if (!crsLocation.match(CRS_REGEX)) {
return new Response(`Invalid crsLocation: ${crsLocation}`, { status: 400 });
}
const crsFilter = context.params.departures[1];
if (crsFilter && !crsFilter.match(CRS_REGEX)) {
return new Response(`Invalid crsFilter: ${crsFilter}`, { status: 400 });
}

const params = { "timeOffset": TIME_OFFSET };
if (crsFilter) {
params["filterCrs"] = crsFilter;
}
const paramsString = new URLSearchParams(params).toString();
const response = await fetch(new Request(`https://api1.raildata.org.uk/1010-live-departure-board-dep/LDBWS/api/20220120/GetDepBoardWithDetails/${crsLocation}?${paramsString}`, {
headers: {
"user-agent": USER_AGENT,
"x-apikey": context.env.RAILDATA_LIVE_DEPARTURES_API_KEY,
},
}));
const nreDepartures = await response.json();

const data = {};
data["location"] = nreDepartures["locationName"];
if (crsFilter) {
data["filterLocation"] = nreDepartures["filterLocationName"];
}
const tsRe = nreDepartures["generatedAt"].match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).\d*(\+\d{2}:\d{2})$/);
data["generatedAt"] = `${tsRe[1]}-${tsRe[2]}-${tsRe[3]}T${tsRe[4]}:${tsRe[5]}:${tsRe[6]}${tsRe[7]}`;
const services = []
if ("trainServices" in nreDepartures) {
nreDepartures["trainServices"].forEach((nreService) => {
const service = {};

// verify that the service still calls at the filter station (if applicable)
if (crsFilter) {
const allCallingPoints = []
nreService["subsequentCallingPoints"].forEach((callingPoints => {
console.log(callingPoints["callingPoint"]);
allCallingPoints.push(...callingPoints["callingPoint"]);
}))
const callingPoint = allCallingPoints.filter(cp => cp["crs"] === crsFilter)[0];
if (callingPoint["isCancelled"]) {
return;
}
}

// cancellations
service["cancelled"] = nreService["isCancelled"];
if ("cancelReason" in nreService) {
if (nreService["cancelReason"].includes(CANCEL_REASON_PREFIX)) {
service["cancelReason"] = `Due to ${nreService["cancelReason"].substring(CANCEL_REASON_PREFIX.length)}`;
} else {
service["cancelReason"] = nreService["cancelReason"];
}
}

// timings
service["etd"] = nreService["etd"];
service["std"] = nreService["std"];
service["timeForSort"] = service["std"];
if (service["etd"].includes(":")) {
service["timeForSort"] = service["etd"];
}

// other bits
service["destination"] = nreService["destination"][0]["locationName"]; // TODO: handle multiple destinations
service["length"] = nreService["length"].toString();
if (service["length"] === "0") {
service["length"] = "?";
}
service["operator"] = nreService["operator"];
service["operatorCode"] = nreService["operatorCode"];
service["platform"] = "?";
if ("platform" in nreService) {
service["platform"] = nreService["platform"];
}

services.push(service);
});
}

let servicesContainsEarlyMorning = false;
let servicesContainsLateNight = false;
services.forEach((service) => {
const serviceHour = service["timeForSort"].split(":")[0];
if (serviceHour in SERVICE_HOURS_EARLY_MORNING) {
servicesContainsEarlyMorning = true;
} else if (serviceHour in SERVICE_HOURS_LATE_NIGHT) {
servicesContainsLateNight = true;
}
});
if (servicesContainsLateNight && servicesContainsEarlyMorning) {
services.forEach((service, index) => {
const serviceHour = service["timeForSort"].split(":")[0];
const serviceMinute = service["timeForSort"].split(":")[1];
if (serviceHour in SERVICE_HOURS_EARLY_MORNING) {
this[index]["timeForSort"] = `${parseInt(serviceHour)+24}:${serviceMinute}`;
}
});
}

data["services"] = services.sort().splice(0, NUM_SERVICES);
return Response.json(data);
}

const CANCEL_REASON_PREFIX = "This train has been cancelled because of "
const CRS_REGEX = /^[A-Z]{3}$/;
const SERVICE_HOURS_EARLY_MORNING = ["00", "01", "02", "03"];
const SERVICE_HOURS_LATE_NIGHT = ["20", "21", "22", "23"];

0 comments on commit a43baaf

Please sign in to comment.