Refactor replace some HTTP related magic values by enums.
This commit is contained in:
parent
c988227bc7
commit
15d3f45bae
|
@ -11,6 +11,7 @@ import { config } from "./config";
|
||||||
import type { CleartextPassword, PasswordHash, Username } from "./types";
|
import type { CleartextPassword, PasswordHash, Username } from "./types";
|
||||||
import { isString } from "./types";
|
import { isString } from "./types";
|
||||||
import Logger from "./logger";
|
import Logger from "./logger";
|
||||||
|
import { HttpHeader, HttpStatusCode, MimeType } from "./shared/utils/http";
|
||||||
|
|
||||||
export const app: Express = express();
|
export const app: Express = express();
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ export function init(): void {
|
||||||
router.use(compress());
|
router.use(compress());
|
||||||
|
|
||||||
async function serveTemplate(
|
async function serveTemplate(
|
||||||
mimeType: string,
|
mimeType: MimeType,
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
@ -120,13 +121,15 @@ export function init(): void {
|
||||||
"utf8"
|
"utf8"
|
||||||
);
|
);
|
||||||
|
|
||||||
res.writeHead(200, { "Content-Type": mimeType });
|
res.writeHead(HttpStatusCode.OK, {
|
||||||
|
[HttpHeader.CONTENT_TYPE]: mimeType,
|
||||||
|
});
|
||||||
res.end(_.template(body)({ config: config.client }));
|
res.end(_.template(body)({ config: config.client }));
|
||||||
}
|
}
|
||||||
|
|
||||||
usePromise(async (req: Request, res: Response): Promise<void> => {
|
usePromise(async (req: Request, res: Response): Promise<void> => {
|
||||||
if (jsTemplateFiles.indexOf(req.path) >= 0) {
|
if (jsTemplateFiles.indexOf(req.path) >= 0) {
|
||||||
await serveTemplate("application/javascript", req, res);
|
await serveTemplate(MimeType.APPLICATION_JSON, req, res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { normalizeString, parseInteger } from "../shared/utils/strings";
|
||||||
import { forConstraint } from "../shared/validation/validator";
|
import { forConstraint } from "../shared/validation/validator";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { isString, Mail, MailId } from "../types";
|
import { isString, Mail, MailId } from "../types";
|
||||||
|
import { HttpHeader } from "../shared/utils/http";
|
||||||
|
|
||||||
const isValidId = forConstraint(CONSTRAINTS.id, false);
|
const isValidId = forConstraint(CONSTRAINTS.id, false);
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ async function doGetAll(
|
||||||
export function getAll(req: Request, res: Response): void {
|
export function getAll(req: Request, res: Response): void {
|
||||||
doGetAll(req)
|
doGetAll(req)
|
||||||
.then(({ total, mails }) => {
|
.then(({ total, mails }) => {
|
||||||
res.set("X-Total-Count", total.toString(10));
|
res.set(HttpHeader.X_TOTAL_COUNT, total.toString(10));
|
||||||
return Resources.success(res, mails);
|
return Resources.success(res, mails);
|
||||||
})
|
})
|
||||||
.catch((err) => Resources.error(res, err));
|
.catch((err) => Resources.error(res, err));
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
NodeMonitoringStateResponse,
|
NodeMonitoringStateResponse,
|
||||||
toMonitoringResponse,
|
toMonitoringResponse,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
import { HttpHeader } from "../shared/utils/http";
|
||||||
|
|
||||||
const isValidToken = forConstraint(CONSTRAINTS.token, false);
|
const isValidToken = forConstraint(CONSTRAINTS.token, false);
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ async function doGetAll(
|
||||||
export function getAll(req: Request, res: Response): void {
|
export function getAll(req: Request, res: Response): void {
|
||||||
doGetAll(req)
|
doGetAll(req)
|
||||||
.then(({ total, result }) => {
|
.then(({ total, result }) => {
|
||||||
res.set("X-Total-Count", total.toString(10));
|
res.set(HttpHeader.X_TOTAL_COUNT, total.toString(10));
|
||||||
Resources.success(res, result);
|
Resources.success(res, result);
|
||||||
})
|
})
|
||||||
.catch((err) => Resources.error(res, err));
|
.catch((err) => Resources.error(res, err));
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
toNodeTokenResponse,
|
toNodeTokenResponse,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { filterUndefinedFromJSON } from "../shared/utils/json";
|
import { filterUndefinedFromJSON } from "../shared/utils/json";
|
||||||
|
import { HttpHeader } from "../shared/utils/http";
|
||||||
|
|
||||||
const nodeFields = [
|
const nodeFields = [
|
||||||
"hostname",
|
"hostname",
|
||||||
|
@ -165,7 +166,7 @@ export function getAll(req: Request, res: Response): void {
|
||||||
total: number;
|
total: number;
|
||||||
pageNodes: DomainSpecificNodeResponse[];
|
pageNodes: DomainSpecificNodeResponse[];
|
||||||
}) => {
|
}) => {
|
||||||
res.set("X-Total-Count", result.total.toString(10));
|
res.set(HttpHeader.X_TOTAL_COUNT, result.total.toString(10));
|
||||||
return Resources.success(
|
return Resources.success(
|
||||||
res,
|
res,
|
||||||
result.pageNodes.map(filterUndefinedFromJSON)
|
result.pageNodes.map(filterUndefinedFromJSON)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
TaskState,
|
TaskState,
|
||||||
UnixTimestampSeconds,
|
UnixTimestampSeconds,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
import { HttpHeader } from "../shared/utils/http";
|
||||||
|
|
||||||
const isValidId = forConstraint(CONSTRAINTS.id, false);
|
const isValidId = forConstraint(CONSTRAINTS.id, false);
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ async function doGetAll(
|
||||||
export function getAll(req: Request, res: Response): void {
|
export function getAll(req: Request, res: Response): void {
|
||||||
doGetAll(req)
|
doGetAll(req)
|
||||||
.then(({ total, pageTasks }) => {
|
.then(({ total, pageTasks }) => {
|
||||||
res.set("X-Total-Count", total.toString(10));
|
res.set(HttpHeader.X_TOTAL_COUNT, total.toString(10));
|
||||||
Resources.success(res, pageTasks.map(toTaskResponse));
|
Resources.success(res, pageTasks.map(toTaskResponse));
|
||||||
})
|
})
|
||||||
.catch((err) => Resources.error(res, err));
|
.catch((err) => Resources.error(res, err));
|
||||||
|
|
|
@ -68,6 +68,18 @@ export type Url = string & { readonly __tag: unique symbol };
|
||||||
*/
|
*/
|
||||||
export const isUrl = toIsNewtype(isString, "" as Url);
|
export const isUrl = toIsNewtype(isString, "" as Url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typesafe string representation of paths.
|
||||||
|
*/
|
||||||
|
export type Path = string & { readonly __tag: unique symbol };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for {@link Path}.
|
||||||
|
*
|
||||||
|
* @param arg - Value to check.
|
||||||
|
*/
|
||||||
|
export const isPath = toIsNewtype(isString, "" as Url);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fastd VPN key of a Freifunk node. This is the key used by the node to open a VPN tunnel to Freifunk gateways.
|
* Fastd VPN key of a Freifunk node. This is the key used by the node to open a VPN tunnel to Freifunk gateways.
|
||||||
*/
|
*/
|
||||||
|
|
84
server/shared/utils/http.ts
Normal file
84
server/shared/utils/http.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* Enum representing supported HTTP methods.
|
||||||
|
*/
|
||||||
|
export enum HttpMethod {
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET}.
|
||||||
|
*/
|
||||||
|
GET = "GET",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST}.
|
||||||
|
*/
|
||||||
|
POST = "POST",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT}.
|
||||||
|
*/
|
||||||
|
PUT = "PUT",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE}.
|
||||||
|
*/
|
||||||
|
DELETE = "DELETE",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing supported HTTP headers.
|
||||||
|
*/
|
||||||
|
export enum HttpHeader {
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type}.
|
||||||
|
*/
|
||||||
|
CONTENT_TYPE = "Content-Type",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the total number of entities known by the server matching the request (ignoring paging parameters).
|
||||||
|
*/
|
||||||
|
X_TOTAL_COUNT = "X-Total-Count",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing supported mime-types.
|
||||||
|
*/
|
||||||
|
export enum MimeType {
|
||||||
|
/**
|
||||||
|
* The content is JSON.
|
||||||
|
*/
|
||||||
|
APPLICATION_JSON = "application/json",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content is (X)HTML.
|
||||||
|
*/
|
||||||
|
TEXT_HTML = "text/html",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing supported HTTP response status codes.
|
||||||
|
*/
|
||||||
|
export enum HttpStatusCode {
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200}.
|
||||||
|
*/
|
||||||
|
OK = 200,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400}.
|
||||||
|
*/
|
||||||
|
BAD_REQUEST = 400,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404}.
|
||||||
|
*/
|
||||||
|
NOT_FOUND = 404,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409}.
|
||||||
|
*/
|
||||||
|
CONFLICT = 409,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500}.
|
||||||
|
*/
|
||||||
|
INTERNAL_SERVER_ERROR = 500,
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
|
import { HttpStatusCode } from "../shared/utils/http";
|
||||||
|
|
||||||
|
// TODO: Replace this by throwing typed errors.
|
||||||
export default {
|
export default {
|
||||||
badRequest: { code: 400 },
|
badRequest: { code: HttpStatusCode.BAD_REQUEST },
|
||||||
notFound: { code: 404 },
|
notFound: { code: HttpStatusCode.NOT_FOUND },
|
||||||
conflict: { code: 409 },
|
conflict: { code: HttpStatusCode.CONFLICT },
|
||||||
internalError: { code: 500 },
|
internalError: { code: HttpStatusCode.INTERNAL_SERVER_ERROR },
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
TypeGuard,
|
TypeGuard,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { getFieldIfExists } from "../shared/utils/objects";
|
import { getFieldIfExists } from "../shared/utils/objects";
|
||||||
|
import { HttpHeader, HttpStatusCode, MimeType } from "../shared/utils/http";
|
||||||
|
|
||||||
export type RequestData = JSONObject;
|
export type RequestData = JSONObject;
|
||||||
export type RequestHandler = (request: Request, response: Response) => void;
|
export type RequestHandler = (request: Request, response: Response) => void;
|
||||||
|
@ -46,30 +47,34 @@ export type FilterClause = { query: string; params: unknown[] };
|
||||||
|
|
||||||
function respond(
|
function respond(
|
||||||
res: Response,
|
res: Response,
|
||||||
httpCode: number,
|
httpCode: HttpStatusCode,
|
||||||
data: string,
|
data: string,
|
||||||
type: "html"
|
type: MimeType.TEXT_HTML
|
||||||
): void;
|
): void;
|
||||||
function respond(
|
function respond(
|
||||||
res: Response,
|
res: Response,
|
||||||
httpCode: number,
|
httpCode: HttpStatusCode,
|
||||||
data: JSONValue,
|
data: JSONValue,
|
||||||
type: "json"
|
type: MimeType.APPLICATION_JSON
|
||||||
): void;
|
): void;
|
||||||
function respond(
|
function respond(
|
||||||
res: Response,
|
res: Response,
|
||||||
httpCode: number,
|
httpCode: HttpStatusCode,
|
||||||
data: JSONValue,
|
data: JSONValue,
|
||||||
type: "html" | "json"
|
type: MimeType.APPLICATION_JSON | MimeType.TEXT_HTML
|
||||||
): void {
|
): void {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "html":
|
case MimeType.TEXT_HTML:
|
||||||
res.writeHead(httpCode, { "Content-Type": "text/html" });
|
res.writeHead(httpCode, {
|
||||||
|
[HttpHeader.CONTENT_TYPE]: MimeType.TEXT_HTML,
|
||||||
|
});
|
||||||
res.end(data);
|
res.end(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
res.writeHead(httpCode, { "Content-Type": "application/json" });
|
res.writeHead(httpCode, {
|
||||||
|
[HttpHeader.CONTENT_TYPE]: MimeType.APPLICATION_JSON,
|
||||||
|
});
|
||||||
res.end(JSON.stringify(data));
|
res.end(JSON.stringify(data));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -365,18 +370,18 @@ export function filterClause<SortField>(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function success(res: Response, data: JSONValue) {
|
export function success(res: Response, data: JSONValue) {
|
||||||
respond(res, 200, data, "json");
|
respond(res, HttpStatusCode.OK, data, MimeType.APPLICATION_JSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function successHtml(res: Response, html: string) {
|
export function successHtml(res: Response, html: string) {
|
||||||
respond(res, 200, html, "html");
|
respond(res, HttpStatusCode.OK, html, MimeType.APPLICATION_JSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function error(
|
export function error(
|
||||||
res: Response,
|
res: Response,
|
||||||
err: { data: JSONValue; type: { code: number } }
|
err: { data: JSONValue; type: { code: HttpStatusCode } }
|
||||||
) {
|
) {
|
||||||
respond(res, err.type.code, err.data, "json");
|
respond(res, err.type.code, err.data, MimeType.APPLICATION_JSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleJSON<Response>(
|
export function handleJSON<Response>(
|
||||||
|
|
Loading…
Reference in a new issue