Initial content

This commit is contained in:
Adam Freeman
2024-05-29 18:54:49 +01:00
parent 2dbc1a8088
commit 5215a1d919
1944 changed files with 203378 additions and 0 deletions
@@ -0,0 +1,31 @@
import { Id, NullableId, Params } from "@feathersjs/feathers";
import { WebService } from "./http_adapter";
export class FeathersWrapper<T> {
constructor(private ws: WebService<T>) {}
get(id: Id) {
return this.ws.getOne(id);
}
find(params: Params) {
return this.ws.getMany(params.query);
}
create(data: any, params: Params) {
return this.ws.store(data);
}
remove(id: NullableId, params: Params) {
return this.ws.delete(id);
}
update(id: NullableId, data: any, params: Params) {
return this.ws.replace(id, data);
}
patch(id: NullableId, data: any, params: Params) {
return this.ws.modify(id, data);
}
}
@@ -0,0 +1,68 @@
import { Express, Response } from "express";
import { ValidationError } from "./validation_types";
export interface WebService<T> {
getOne(id: any) : Promise<T | undefined>;
getMany(query: any) : Promise<T[]>;
store(data: any) : Promise<T | undefined>;
delete(id: any): Promise<boolean>;
replace(id: any, data: any): Promise<T | undefined>;
modify(id: any, data: any): Promise<T | undefined>;
}
export function createAdapter<T>(app: Express, ws: WebService<T>, baseUrl: string) {
app.get(baseUrl, async (req, resp) => {
try {
resp.json(await ws.getMany(req.query));
resp.end();
} catch (err) { writeErrorResponse(err, resp) }
});
app.get(`${baseUrl}/:id`, async (req, resp) => {
try {
const data = await ws.getOne((req.params.id));
if (data == undefined) {
resp.writeHead(404);
} else {
resp.json(data);
}
resp.end();
} catch (err) { writeErrorResponse(err, resp) }
});
app.post(baseUrl, async (req, resp) => {
try {
const data = await ws.store(req.body);
resp.json(data);
resp.end();
} catch (err) { writeErrorResponse(err, resp) }
});
app.delete(`${baseUrl}/:id`, async (req, resp) => {
try {
resp.json(await ws.delete(req.params.id));
resp.end();
} catch (err) { writeErrorResponse(err, resp) }
});
app.put(`${baseUrl}/:id`, async (req, resp) => {
try {
resp.json(await ws.replace(req.params.id, req.body));
resp.end();
} catch (err) { writeErrorResponse(err, resp) }
});
app.patch(`${baseUrl}/:id`, async (req, resp) => {
try {
resp.json(await ws.modify(req.params.id, req.body));
resp.end();
} catch (err) { writeErrorResponse(err, resp) }
});
const writeErrorResponse = (err: any, resp: Response) => {
console.error(err);
resp.writeHead(err instanceof ValidationError ? 400 : 500);
resp.end();
}
}
@@ -0,0 +1,33 @@
import { Express } from "express";
import { createAdapter } from "./http_adapter";
import { ResultWebService } from "./results_api";
import { Validator } from "./validation_adapter";
import { ResultWebServiceValidation } from "./results_api_validation";
import { FeathersWrapper } from "./feathers_adapter";
import { feathers } from "@feathersjs/feathers";
import feathersExpress, { rest } from "@feathersjs/express";
import { ValidationError } from "./validation_types";
export const createApi = (app: Express) => {
// createAdapter(app, new Validator(new ResultWebService(),
// ResultWebServiceValidation), "/api/results");
const feathersApp = feathersExpress(feathers(), app).configure(rest());
const service = new Validator(new ResultWebService(),
ResultWebServiceValidation);
feathersApp.use('/api/results', new FeathersWrapper(service));
feathersApp.hooks({
error: {
all: [(ctx) => {
if (ctx.error instanceof ValidationError) {
ctx.http = { status: 400};
ctx.error = undefined;
}
}]
}
});
}
@@ -0,0 +1,48 @@
import { WebService } from "./http_adapter";
import { Result } from "../data/repository";
import repository from "../data";
import * as jsonpatch from "fast-json-patch";
import { validateModel } from "./validation_functions";
import { ResultModelValidation } from "./results_api_validation";
export class ResultWebService implements WebService<Result> {
getOne(id: any): Promise<Result | undefined> {
return repository.getResultById(id);
}
getMany(query: any): Promise<Result[]> {
if (query.name) {
return repository.getResultsByName(query.name, 10);
} else {
return repository.getAllResults(10);
}
}
async store(data: any): Promise<Result | undefined> {
const { name, age, years} = data;
const nextage = age + years;
const id = await repository.saveResult({ id: 0, name, age,
years, nextage});
return await repository.getResultById(id);
}
delete(id: any): Promise<boolean> {
return repository.delete(Number.parseInt(id));
}
replace(id: any, data: any): Promise<Result | undefined> {
const { name, age, years, nextage } = data;
const validated = validateModel({ name, age, years, nextage },
ResultModelValidation)
return repository.update({ id, ...validated });
}
async modify(id: any, data: any): Promise<Result | undefined> {
const dbData = await this.getOne(id);
if (dbData !== undefined) {
return await this.replace(id,
jsonpatch.applyPatch(dbData, data).newDocument);
}
}
}
@@ -0,0 +1,31 @@
import { ModelValidation, ValidationRequirements, ValidationRule,
WebServiceValidation } from "./validation_types";
import validator from "validator";
const intValidator : ValidationRule = {
validation: [val => validator.isInt(val.toString())],
converter: (val) => Number.parseInt(val)
}
const partialResultValidator: ValidationRequirements = {
name: [(val) => !validator.isEmpty(val)],
age: intValidator,
years: intValidator
}
export const ResultWebServiceValidation: WebServiceValidation = {
keyValidator: intValidator,
store: partialResultValidator,
replace: {
...partialResultValidator,
nextage: intValidator
}
}
export const ResultModelValidation : ModelValidation = {
propertyRules: { ...partialResultValidator, nextage: intValidator },
modelRule: [(m: any) => m.nextage === m.age + m.years]
}
@@ -0,0 +1,49 @@
import { WebService } from "./http_adapter";
import { validate, validateIdProperty } from "./validation_functions";
import { WebServiceValidation } from "./validation_types";
export class Validator<T> implements WebService<T> {
constructor(private ws: WebService<T>,
private validation: WebServiceValidation) {}
getOne(id: any): Promise<T | undefined> {
return this.ws.getOne(this.validateId(id));
}
getMany(query: any): Promise<T[]> {
if (this.validation.getMany) {
query = validate(query, this.validation.getMany);
}
return this.ws.getMany(query);
}
store(data: any): Promise<T | undefined> {
if (this.validation.store) {
data = validate(data, this.validation.store);
}
return this.ws.store(data);
}
delete(id: any): Promise<boolean> {
return this.ws.delete(this.validateId(id));
}
replace(id: any, data: any): Promise<T | undefined> {
if (this.validation.replace) {
data = validate(data, this.validation.replace);
}
return this.ws.replace(this.validateId(id), data);
}
modify(id: any, data: any): Promise<T | undefined> {
if (this.validation.modify) {
data = validate(data, this.validation.modify);
}
return this.ws.modify(this.validateId(id), data);
}
validateId(val: any) {
return validateIdProperty(val, this.validation);
}
}
@@ -0,0 +1,59 @@
import { ModelValidation, ValidationError, ValidationRequirements,
ValidationRule, WebServiceValidation } from "./validation_types";
export type ValidationResult = [valid: boolean, value: any];
export function validate(data: any, reqs: ValidationRequirements): any {
let validatedData: any = {};
Object.entries(reqs).forEach(([prop, rule]) => {
const [valid, value] = applyRule(data[prop], rule);
if (valid) {
validatedData[prop] = value;
} else {
throw new ValidationError(prop, "Validation Error");
}
});
return validatedData;
}
function applyRule(val: any,
rule: ValidationRule): ValidationResult {
const required = Array.isArray(rule) ? true : rule.required;
const checks = Array.isArray(rule) ? rule : rule.validation;
const convert = Array.isArray(rule) ? (v: any) => v : rule.converter;
if (val === null || val == undefined || val === "") {
return [required ? false : true, val];
}
let valid = true;
checks.forEach(check => {
if (!check(val)) {
valid = false;
}
});
return [valid, convert ? convert(val) : val];
}
export function validateIdProperty<T>(val: any,
v: WebServiceValidation) : any {
if (v.keyValidator) {
const [valid, value] = applyRule(val, v.keyValidator);
if (valid) {
return value;
}
throw new ValidationError("ID", "Validation Error");
}
return val;
}
export function validateModel(model: any, rules: ModelValidation) : any {
if (rules.propertyRules) {
model = validate(model, rules.propertyRules);
}
if (rules.modelRule) {
const [valid, data] = applyRule(model, rules.modelRule);
if (valid) {
return data;
}
throw new ValidationError("Model", "Validation Error");
}
}
@@ -0,0 +1,30 @@
export interface WebServiceValidation {
keyValidator?: ValidationRule;
getMany?: ValidationRequirements;
store?: ValidationRequirements;
replace?: ValidationRequirements;
modify?: ValidationRequirements;
}
export type ValidationRequirements = {
[key: string] : ValidationRule
}
export type ValidationRule =
((value: any) => boolean)[] |
{
required? : boolean,
validation: ((value: any) => boolean)[],
converter?: (value: any) => any,
}
export class ValidationError implements Error {
constructor(public name: string, public message: string) {}
stack?: string | undefined;
cause?: unknown;
}
export type ModelValidation = {
modelRule?: ValidationRule,
propertyRules?: ValidationRequirements
}