Refactor replace some HTTP related magic values by enums.
This commit is contained in:
parent
c988227bc7
commit
15d3f45bae
9 changed files with 135 additions and 24 deletions
|
@ -11,6 +11,7 @@ import { config } from "./config";
|
|||
import type { CleartextPassword, PasswordHash, Username } from "./types";
|
||||
import { isString } from "./types";
|
||||
import Logger from "./logger";
|
||||
import { HttpHeader, HttpStatusCode, MimeType } from "./shared/utils/http";
|
||||
|
||||
export const app: Express = express();
|
||||
|
||||
|
@ -111,7 +112,7 @@ export function init(): void {
|
|||
router.use(compress());
|
||||
|
||||
async function serveTemplate(
|
||||
mimeType: string,
|
||||
mimeType: MimeType,
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
|
@ -120,13 +121,15 @@ export function init(): void {
|
|||
"utf8"
|
||||
);
|
||||
|
||||
res.writeHead(200, { "Content-Type": mimeType });
|
||||
res.writeHead(HttpStatusCode.OK, {
|
||||
[HttpHeader.CONTENT_TYPE]: mimeType,
|
||||
});
|
||||
res.end(_.template(body)({ config: config.client }));
|
||||
}
|
||||
|
||||
usePromise(async (req: Request, res: Response): Promise<void> => {
|
||||
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 type { Request, Response } from "express";
|
||||
import { isString, Mail, MailId } from "../types";
|
||||
import { HttpHeader } from "../shared/utils/http";
|
||||
|
||||
const isValidId = forConstraint(CONSTRAINTS.id, false);
|
||||
|
||||
|
@ -39,7 +40,7 @@ async function doGetAll(
|
|||
export function getAll(req: Request, res: Response): void {
|
||||
doGetAll(req)
|
||||
.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);
|
||||
})
|
||||
.catch((err) => Resources.error(res, err));
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
NodeMonitoringStateResponse,
|
||||
toMonitoringResponse,
|
||||
} from "../types";
|
||||
import { HttpHeader } from "../shared/utils/http";
|
||||
|
||||
const isValidToken = forConstraint(CONSTRAINTS.token, false);
|
||||
|
||||
|
@ -33,7 +34,7 @@ async function doGetAll(
|
|||
export function getAll(req: Request, res: Response): void {
|
||||
doGetAll(req)
|
||||
.then(({ total, result }) => {
|
||||
res.set("X-Total-Count", total.toString(10));
|
||||
res.set(HttpHeader.X_TOTAL_COUNT, total.toString(10));
|
||||
Resources.success(res, result);
|
||||
})
|
||||
.catch((err) => Resources.error(res, err));
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
toNodeTokenResponse,
|
||||
} from "../types";
|
||||
import { filterUndefinedFromJSON } from "../shared/utils/json";
|
||||
import { HttpHeader } from "../shared/utils/http";
|
||||
|
||||
const nodeFields = [
|
||||
"hostname",
|
||||
|
@ -165,7 +166,7 @@ export function getAll(req: Request, res: Response): void {
|
|||
total: number;
|
||||
pageNodes: DomainSpecificNodeResponse[];
|
||||
}) => {
|
||||
res.set("X-Total-Count", result.total.toString(10));
|
||||
res.set(HttpHeader.X_TOTAL_COUNT, result.total.toString(10));
|
||||
return Resources.success(
|
||||
res,
|
||||
result.pageNodes.map(filterUndefinedFromJSON)
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
TaskState,
|
||||
UnixTimestampSeconds,
|
||||
} from "../types";
|
||||
import { HttpHeader } from "../shared/utils/http";
|
||||
|
||||
const isValidId = forConstraint(CONSTRAINTS.id, false);
|
||||
|
||||
|
@ -109,7 +110,7 @@ async function doGetAll(
|
|||
export function getAll(req: Request, res: Response): void {
|
||||
doGetAll(req)
|
||||
.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));
|
||||
})
|
||||
.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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
|
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 {
|
||||
badRequest: { code: 400 },
|
||||
notFound: { code: 404 },
|
||||
conflict: { code: 409 },
|
||||
internalError: { code: 500 },
|
||||
badRequest: { code: HttpStatusCode.BAD_REQUEST },
|
||||
notFound: { code: HttpStatusCode.NOT_FOUND },
|
||||
conflict: { code: HttpStatusCode.CONFLICT },
|
||||
internalError: { code: HttpStatusCode.INTERNAL_SERVER_ERROR },
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
TypeGuard,
|
||||
} from "../types";
|
||||
import { getFieldIfExists } from "../shared/utils/objects";
|
||||
import { HttpHeader, HttpStatusCode, MimeType } from "../shared/utils/http";
|
||||
|
||||
export type RequestData = JSONObject;
|
||||
export type RequestHandler = (request: Request, response: Response) => void;
|
||||
|
@ -46,30 +47,34 @@ export type FilterClause = { query: string; params: unknown[] };
|
|||
|
||||
function respond(
|
||||
res: Response,
|
||||
httpCode: number,
|
||||
httpCode: HttpStatusCode,
|
||||
data: string,
|
||||
type: "html"
|
||||
type: MimeType.TEXT_HTML
|
||||
): void;
|
||||
function respond(
|
||||
res: Response,
|
||||
httpCode: number,
|
||||
httpCode: HttpStatusCode,
|
||||
data: JSONValue,
|
||||
type: "json"
|
||||
type: MimeType.APPLICATION_JSON
|
||||
): void;
|
||||
function respond(
|
||||
res: Response,
|
||||
httpCode: number,
|
||||
httpCode: HttpStatusCode,
|
||||
data: JSONValue,
|
||||
type: "html" | "json"
|
||||
type: MimeType.APPLICATION_JSON | MimeType.TEXT_HTML
|
||||
): void {
|
||||
switch (type) {
|
||||
case "html":
|
||||
res.writeHead(httpCode, { "Content-Type": "text/html" });
|
||||
case MimeType.TEXT_HTML:
|
||||
res.writeHead(httpCode, {
|
||||
[HttpHeader.CONTENT_TYPE]: MimeType.TEXT_HTML,
|
||||
});
|
||||
res.end(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
res.writeHead(httpCode, { "Content-Type": "application/json" });
|
||||
res.writeHead(httpCode, {
|
||||
[HttpHeader.CONTENT_TYPE]: MimeType.APPLICATION_JSON,
|
||||
});
|
||||
res.end(JSON.stringify(data));
|
||||
break;
|
||||
}
|
||||
|
@ -365,18 +370,18 @@ export function filterClause<SortField>(
|
|||
}
|
||||
|
||||
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) {
|
||||
respond(res, 200, html, "html");
|
||||
respond(res, HttpStatusCode.OK, html, MimeType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
export function error(
|
||||
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>(
|
||||
|
|
Loading…
Reference in a new issue