Major refactoring and fixes.
* Split Node into multiple types and make sure fields are actually set when type says so. * Refactor request handling. * Start getting rid of moment as a dependency by using UnixTimestampSeconds instead.
This commit is contained in:
parent
cfa784dfe2
commit
250353edbf
16 changed files with 676 additions and 455 deletions
server/utils
|
@ -5,7 +5,18 @@ import ErrorTypes from "../utils/errorTypes";
|
|||
import Logger from "../logger";
|
||||
import {Constraints, forConstraints, isConstraints} from "../validation/validator";
|
||||
import {Request, Response} from "express";
|
||||
import {EnumTypeGuard, EnumValue, type GenericSortField, SortDirection, TypeGuard} from "../types";
|
||||
import {
|
||||
EnumTypeGuard,
|
||||
EnumValue,
|
||||
type GenericSortField,
|
||||
isJSONObject,
|
||||
JSONObject,
|
||||
SortDirection,
|
||||
TypeGuard
|
||||
} from "../types";
|
||||
|
||||
export type RequestData = JSONObject;
|
||||
export type RequestHandler = (request: Request, response: Response) => void;
|
||||
|
||||
export type Entity = { [key: string]: any };
|
||||
|
||||
|
@ -110,8 +121,20 @@ function getConstrainedValues(data: { [key: string]: any }, constraints: Constra
|
|||
return values;
|
||||
}
|
||||
|
||||
export function getData(req: Request): any {
|
||||
return _.extend({}, req.body, req.params, req.query);
|
||||
function normalize(data: any): JSONObject {
|
||||
return isJSONObject(data) ? data : {};
|
||||
}
|
||||
|
||||
export function getData(req: Request): RequestData {
|
||||
const body = normalize(req.body);
|
||||
const params = normalize(req.params);
|
||||
const query = normalize(req.query);
|
||||
|
||||
return {
|
||||
...body,
|
||||
...params,
|
||||
...query,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getValidRestParams(
|
||||
|
@ -197,7 +220,7 @@ export function filter<E>(entities: ArrayLike<E>, allowedFilterFields: string[],
|
|||
return true;
|
||||
}
|
||||
if (_.startsWith(key, 'has')) {
|
||||
const entityKey = key.substr(3, 1).toLowerCase() + key.substr(4);
|
||||
const entityKey = key.substring(3, 4).toLowerCase() + key.substring(4);
|
||||
return _.isEmpty(entity[entityKey]).toString() !== value;
|
||||
}
|
||||
return entity[key] === value;
|
||||
|
@ -267,3 +290,19 @@ export function successHtml(res: Response, html: string) {
|
|||
export function error(res: Response, err: { data: any, type: { code: number } }) {
|
||||
respond(res, err.type.code, err.data, 'json');
|
||||
}
|
||||
|
||||
export function handleJSON<Response>(handler: () => Promise<Response>): RequestHandler {
|
||||
return (request, response) => {
|
||||
handler()
|
||||
.then(data => success(response, data || {}))
|
||||
.catch(error => error(response, error));
|
||||
};
|
||||
}
|
||||
|
||||
export function handleJSONWithData<Response>(handler: (data: RequestData) => Promise<Response>): RequestHandler {
|
||||
return (request, response) => {
|
||||
handler(getData(request))
|
||||
.then(data => success(response, data || {}))
|
||||
.catch(error => error(response, error));
|
||||
};
|
||||
}
|
||||
|
|
53
server/utils/time.test.ts
Normal file
53
server/utils/time.test.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import {parseTimestamp} from "./time";
|
||||
import moment from "moment";
|
||||
|
||||
const TIMESTAMP_INVALID_STRING = "2020-01-02T42:99:23.000Z";
|
||||
const TIMESTAMP_VALID_STRING = "2020-01-02T12:34:56.000Z";
|
||||
|
||||
test('parseTimestamp() should fail parsing non-string timestamp', () => {
|
||||
// given
|
||||
const timestamp = {};
|
||||
|
||||
// when
|
||||
const parsedTimestamp = parseTimestamp(timestamp);
|
||||
|
||||
// then
|
||||
expect(parsedTimestamp).toEqual(null);
|
||||
});
|
||||
|
||||
test('parseTimestamp() should fail parsing empty timestamp string', () => {
|
||||
// given
|
||||
const timestamp = "";
|
||||
|
||||
// when
|
||||
const parsedTimestamp = parseTimestamp(timestamp);
|
||||
|
||||
// then
|
||||
expect(parsedTimestamp).toEqual(null);
|
||||
});
|
||||
|
||||
test('parseTimestamp() should fail parsing invalid timestamp string', () => {
|
||||
// given
|
||||
// noinspection UnnecessaryLocalVariableJS
|
||||
const timestamp = TIMESTAMP_INVALID_STRING;
|
||||
|
||||
// when
|
||||
const parsedTimestamp = parseTimestamp(timestamp);
|
||||
|
||||
// then
|
||||
expect(parsedTimestamp).toEqual(null);
|
||||
});
|
||||
|
||||
test('parseTimestamp() should succeed parsing valid timestamp string', () => {
|
||||
// given
|
||||
const timestamp = TIMESTAMP_VALID_STRING;
|
||||
|
||||
// when
|
||||
const parsedTimestamp = parseTimestamp(timestamp);
|
||||
|
||||
// then
|
||||
if (parsedTimestamp === null) {
|
||||
fail('timestamp should not be null');
|
||||
}
|
||||
expect(moment.unix(parsedTimestamp).toISOString()).toEqual(timestamp);
|
||||
});
|
57
server/utils/time.ts
Normal file
57
server/utils/time.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import {DurationSeconds, UnixTimestampSeconds} from "../types";
|
||||
import _ from "lodash";
|
||||
import moment, {Moment} from "moment";
|
||||
|
||||
export function now(): UnixTimestampSeconds {
|
||||
return Math.round(Date.now() / 1000.0) as UnixTimestampSeconds;
|
||||
}
|
||||
|
||||
export function subtract(timestamp: UnixTimestampSeconds, duration: DurationSeconds): UnixTimestampSeconds {
|
||||
return (timestamp - duration) as UnixTimestampSeconds;
|
||||
}
|
||||
|
||||
const SECOND: DurationSeconds = 1 as DurationSeconds;
|
||||
const MINUTE: DurationSeconds = (60 * SECOND) as DurationSeconds;
|
||||
const HOUR: DurationSeconds = (60 * MINUTE) as DurationSeconds;
|
||||
const DAY: DurationSeconds = (24 * HOUR) as DurationSeconds;
|
||||
const WEEK: DurationSeconds = (7 * DAY) as DurationSeconds;
|
||||
|
||||
export function seconds(n: number): DurationSeconds {
|
||||
return (n * SECOND) as DurationSeconds;
|
||||
}
|
||||
|
||||
export function minutes(n: number): DurationSeconds {
|
||||
return (n * MINUTE) as DurationSeconds;
|
||||
}
|
||||
|
||||
export function hours(n: number): DurationSeconds {
|
||||
return (n * HOUR) as DurationSeconds;
|
||||
}
|
||||
|
||||
export function days(n: number): DurationSeconds {
|
||||
return (n * DAY) as DurationSeconds;
|
||||
}
|
||||
|
||||
export function weeks(n: number): DurationSeconds {
|
||||
return (n * WEEK) as DurationSeconds;
|
||||
}
|
||||
|
||||
export function unix(moment: Moment): UnixTimestampSeconds {
|
||||
return moment.unix() as UnixTimestampSeconds;
|
||||
}
|
||||
|
||||
export function formatTimestamp(timestamp: UnixTimestampSeconds): string {
|
||||
return moment.unix(timestamp).format();
|
||||
}
|
||||
|
||||
export function parseTimestamp(timestamp: any): UnixTimestampSeconds | null {
|
||||
if (!_.isString(timestamp)) {
|
||||
return null;
|
||||
}
|
||||
const parsed = moment.utc(timestamp);
|
||||
if (!parsed.isValid()) {
|
||||
return null;
|
||||
}
|
||||
return unix(parsed);
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue