Compare commits
5 Commits
v3.0.0-dev
...
v3.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c81b5225f | |||
| d10dabf604 | |||
| 26886f8a7d | |||
| 46dcf65de6 | |||
| d160ad87f9 |
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@owlboard/api-schema-types": "^3.0.2-alpha3",
|
"@owlboard/api-schema-types": "^3.0.3-alpha1",
|
||||||
"latlon-geohash": "^2.0.0"
|
"latlon-geohash": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -504,9 +504,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@owlboard/api-schema-types": {
|
"node_modules/@owlboard/api-schema-types": {
|
||||||
"version": "3.0.2-alpha3",
|
"version": "3.0.3-alpha1",
|
||||||
"resolved": "https://git.fjla.uk/api/packages/OwlBoard/npm/%40owlboard%2Fapi-schema-types/-/3.0.2-alpha3/api-schema-types-3.0.2-alpha3.tgz",
|
"resolved": "https://git.fjla.uk/api/packages/OwlBoard/npm/%40owlboard%2Fapi-schema-types/-/3.0.3-alpha1/api-schema-types-3.0.3-alpha1.tgz",
|
||||||
"integrity": "sha512-3NFP21QdSfjziwlGQixlNnUWC55HlKZGyWANIOimWu0FZejWQWExJiaAVfb6m3Sbv+zvQMu3B8mzcMCcGadZCQ==",
|
"integrity": "sha512-UWe2nbJWb2B/LuZW1UXHJ2lpqOGwugiXTa4G6X5xLiaws3ISEdciweorX8kr2/JAz5+iFYIe1xXRFAWsFtpn/w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"author": "Frederick Boniface",
|
"author": "Frederick Boniface",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@owlboard/api-schema-types": "^3.0.2-alpha3",
|
"@owlboard/api-schema-types": "^3.0.3-alpha1",
|
||||||
"latlon-geohash": "^2.0.0"
|
"latlon-geohash": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -7,3 +7,5 @@ export type * from '@owlboard/api-schema-types'
|
|||||||
// Useful exports for Type Hinting
|
// Useful exports for Type Hinting
|
||||||
export { PisModule } from './modules/pis.js';
|
export { PisModule } from './modules/pis.js';
|
||||||
export { LocationFilterModule } from './modules/locationFilter.js';
|
export { LocationFilterModule } from './modules/locationFilter.js';
|
||||||
|
export { StationDataModule } from './modules/stationData.js';
|
||||||
|
export { TrainsModule } from './modules/trains.js';
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
import { BaseClient } from "./base.js";
|
import { BaseClient } from "./base.js";
|
||||||
import { PisModule } from "../modules/pis.js";
|
import { PisModule } from "../modules/pis.js";
|
||||||
import { LocationFilterModule } from "../modules/locationFilter.js";
|
import { LocationFilterModule } from "../modules/locationFilter.js";
|
||||||
|
import { StationDataModule } from "../modules/stationData.js";
|
||||||
|
import { TrainsModule } from "src/modules/trains.js";
|
||||||
|
|
||||||
export class OwlBoardClient extends BaseClient {
|
export class OwlBoardClient extends BaseClient {
|
||||||
public readonly pis: PisModule;
|
public readonly pis: PisModule;
|
||||||
public readonly locationFilter: LocationFilterModule;
|
public readonly locationFilter: LocationFilterModule;
|
||||||
|
public readonly stationData: StationDataModule;
|
||||||
|
public readonly trains: TrainsModule;
|
||||||
|
|
||||||
constructor(baseUrl: string, apiKey?: string) {
|
constructor(baseUrl: string, apiKey?: string) {
|
||||||
super(baseUrl, apiKey);
|
super(baseUrl, apiKey);
|
||||||
|
|
||||||
this.pis = new PisModule(this);
|
this.pis = new PisModule(this);
|
||||||
this.locationFilter = new LocationFilterModule(this);
|
this.locationFilter = new LocationFilterModule(this);
|
||||||
|
this.stationData = new StationDataModule(this);
|
||||||
|
this.trains = new TrainsModule(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
20
src/lib/helpers.ts
Normal file
20
src/lib/helpers.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Normalises input to YYYY-MM-DD string
|
||||||
|
* - Strings are passed through as is
|
||||||
|
* - Date objects are converted to UK time
|
||||||
|
*/
|
||||||
|
export const ensureDateString = (input: Date | string): string => {
|
||||||
|
if (typeof input === 'string') return input.trim();
|
||||||
|
|
||||||
|
const d = new Intl.DateTimeFormat('en-GB', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
timeZone: 'Europe/London'
|
||||||
|
}).formatToParts(input);
|
||||||
|
|
||||||
|
const part = (type: string) => d.find(p => p.type === type)?.value;
|
||||||
|
|
||||||
|
return `${part('year')}-${part('month')}-${part('day')}`
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Checks if a character code is a digit
|
||||||
|
*/
|
||||||
|
const isDigit = (c: number) => c >= 48 && c <= 57;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a character code is an upper case letter
|
||||||
|
*/
|
||||||
|
const isUpper = (c: number) => c >= 65 && c <= 90;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a string is a valid CRS (Syntactically Only)
|
* Checks if a string is a valid CRS (Syntactically Only)
|
||||||
* using byte level checking for max performance
|
* using byte level checking for max performance
|
||||||
@@ -49,6 +59,60 @@ export const IsValidPis = (PIS: string): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a string is the correct format for a Headcode
|
||||||
|
* USes byte level checking for max performance
|
||||||
|
* @param Headcode The headcode to be validated
|
||||||
|
*/
|
||||||
|
export const IsValidHeadcode = (Headcode: string): boolean => {
|
||||||
|
if (Headcode.length !== 4) return false;
|
||||||
|
const c0 = Headcode.charCodeAt(0);
|
||||||
|
const c1 = Headcode.charCodeAt(1);
|
||||||
|
const c2 = Headcode.charCodeAt(2);
|
||||||
|
const c3 = Headcode.charCodeAt(3);
|
||||||
|
|
||||||
|
return isDigit(c0) && isUpper(c1) && isDigit(c2) && isDigit(c3)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a string is the correct format for a TOC Code
|
||||||
|
*/
|
||||||
|
export const IsValidToc = (toc: string): boolean => {
|
||||||
|
if (toc.length !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const c0 = toc.charCodeAt(0);
|
||||||
|
const c1 = toc.charCodeAt(1);
|
||||||
|
return isUpper(c0) && isUpper(c1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a string is the correct format, and is a valid date
|
||||||
|
*/
|
||||||
|
export const IsValidDateStr = (s: string): boolean => {
|
||||||
|
if (s.length !== 10) return false;
|
||||||
|
if (s[4] !== '-' || s[7] !== '-') return false;
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
if (i === 4 || i === 7) continue;
|
||||||
|
const c = s.charCodeAt(i);
|
||||||
|
if (!isDigit(i)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const y = parseInt(s.substring(0, 4), 10);
|
||||||
|
const m = parseInt(s.substring(5, 7), 10) - 1; // JS months are 0-11
|
||||||
|
const d = parseInt(s.substring(8, 10), 10);
|
||||||
|
|
||||||
|
const date = new Date(y, m, d);
|
||||||
|
|
||||||
|
return (
|
||||||
|
date.getFullYear() === y &&
|
||||||
|
date.getMonth() === m &&
|
||||||
|
date.getDate() === d
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates Geohash string against standard b32 alphabet
|
* Validates Geohash string against standard b32 alphabet
|
||||||
* (Syntactically Only validation)
|
* (Syntactically Only validation)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export class StationDataModule {
|
|||||||
* @param ln Longitude
|
* @param ln Longitude
|
||||||
* @returns Geohash as string
|
* @returns Geohash as string
|
||||||
*/
|
*/
|
||||||
static generateGeohash(lt: number, ln: number): string {
|
generateGeohash(lt: number, ln: number): string {
|
||||||
return Geohash.encode(lt, ln, 6);
|
return Geohash.encode(lt, ln, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
src/modules/trains.ts
Normal file
34
src/modules/trains.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ApiTrainsTrainByHeadcode } from '@owlboard/api-schema-types';
|
||||||
|
import type { BaseClient, ApiResult } from '../lib/base.js';
|
||||||
|
import { IsValidHeadcode, IsValidToc, IsValidDateStr } from '../lib/validation.js';
|
||||||
|
import { ensureDateString } from 'src/lib/helpers.js';
|
||||||
|
import { ValidationError } from '../lib/errors.js';
|
||||||
|
|
||||||
|
export class TrainsModule {
|
||||||
|
constructor(private client: BaseClient) { }
|
||||||
|
|
||||||
|
async getByHeadcode(headcode: string, date: string | Date = new Date, toc: string = ""): Promise<ApiResult<ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[]>> {
|
||||||
|
if (!IsValidHeadcode(headcode)) {
|
||||||
|
throw new ValidationError("headcode", "Invalid headcode format")
|
||||||
|
}
|
||||||
|
if (toc !== "" && !IsValidToc(toc)) {
|
||||||
|
throw new ValidationError("TOC", "Invalid TOC Format, needs to be two characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateStr = ensureDateString(date);
|
||||||
|
if (!IsValidDateStr(dateStr)) {
|
||||||
|
throw new ValidationError("Date", "Invalid date format, need YYYY-MM-DD");
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
searchParams.append('d', dateStr);
|
||||||
|
searchParams.append('h', headcode);
|
||||||
|
if (toc) searchParams.append('t', toc);
|
||||||
|
|
||||||
|
const path = `/trains?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
return this.client.request<ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[]>(path, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user