refactor: ♻️ Always use explicit types in every function

This commit is contained in:
Jesse Wierzbinski 2024-11-02 00:43:33 +01:00
parent 54cea29ce9
commit c1dcdc78ae
No known key found for this signature in database
62 changed files with 359 additions and 226 deletions

View file

@ -1,4 +1,4 @@
import type { Context } from "@hono/hono";
import type { Context, MiddlewareHandler } from "@hono/hono";
import { createMiddleware } from "@hono/hono/factory";
import type { OpenAPIHono } from "@hono/zod-openapi";
import { getLogger } from "@logtape/logtape";
@ -6,7 +6,7 @@ import { Application, type User, db } from "@versia/kit/db";
import { Challenges } from "@versia/kit/tables";
import { extractParams, verifySolution } from "altcha-lib";
import chalk from "chalk";
import { eq } from "drizzle-orm";
import { type SQL, eq } from "drizzle-orm";
import {
anyOf,
caseInsensitive,
@ -21,14 +21,14 @@ import {
not,
oneOrMore,
} from "magic-regexp";
import { parse } from "qs";
import { type ParsedQs, parse } from "qs";
import type { z } from "zod";
import { fromZodError } from "zod-validation-error";
import { type AuthData, getFromHeader } from "~/classes/functions/user";
import { config } from "~/packages/config-manager/index.ts";
import type { ApiRouteMetadata, HonoEnv, HttpVerb } from "~/types/api";
export const applyConfig = (routeMeta: ApiRouteMetadata) => {
export const applyConfig = (routeMeta: ApiRouteMetadata): ApiRouteMetadata => {
const newMeta = routeMeta;
// Apply ratelimits from config
@ -42,7 +42,8 @@ export const applyConfig = (routeMeta: ApiRouteMetadata) => {
return newMeta;
};
export const apiRoute = (fn: (app: OpenAPIHono<HonoEnv>) => void) => fn;
export const apiRoute = (fn: (app: OpenAPIHono<HonoEnv>) => void): typeof fn =>
fn;
export const idValidator = createRegExp(
anyOf(digit, charIn("ABCDEF")).times(8),
@ -115,7 +116,12 @@ export const webfingerMention = createRegExp(
[],
);
export const parseUserAddress = (address: string) => {
export const parseUserAddress = (
address: string,
): {
username: string;
domain: string;
} => {
let output = address;
// Remove leading @ if it exists
if (output.startsWith("@")) {
@ -238,7 +244,7 @@ export const checkRouteNeedsChallenge = async (
}
const challenge = await db.query.Challenges.findFirst({
where: (c, { eq }) => eq(c.id, challenge_id),
where: (c, { eq }): SQL | undefined => eq(c.id, challenge_id),
});
if (!challenge) {
@ -286,7 +292,7 @@ export const auth = (
authData: ApiRouteMetadata["auth"],
permissionData?: ApiRouteMetadata["permissions"],
challengeData?: ApiRouteMetadata["challenge"],
) =>
): MiddlewareHandler<HonoEnv, string> =>
createMiddleware<HonoEnv>(async (context, next) => {
const header = context.req.header("Authorization");
@ -335,7 +341,10 @@ export const auth = (
});
// Helper function to parse form data
async function parseFormData(context: Context) {
async function parseFormData(context: Context): Promise<{
parsed: ParsedQs;
files: Map<string, File>;
}> {
const formData = await context.req.formData();
const urlparams = new URLSearchParams();
const files = new Map<string, File>();
@ -365,7 +374,7 @@ async function parseFormData(context: Context) {
}
// Helper function to parse urlencoded data
async function parseUrlEncoded(context: Context) {
async function parseUrlEncoded(context: Context): Promise<ParsedQs> {
const parsed = parse(await context.req.text(), {
parseArrays: true,
interpretNumericEntities: true,
@ -374,7 +383,7 @@ async function parseUrlEncoded(context: Context) {
return parsed;
}
export const qsQuery = () => {
export const qsQuery = (): MiddlewareHandler => {
return createMiddleware(async (context, next) => {
const parsed = parse(context.req.query(), {
parseArrays: true,
@ -382,10 +391,10 @@ export const qsQuery = () => {
});
// @ts-expect-error Very bad hack
context.req.query = () => parsed;
context.req.query = (): typeof parsed => parsed;
// @ts-expect-error I'm so sorry for this
context.req.queries = () => parsed;
context.req.queries = (): typeof parsed => parsed;
await next();
});
};
@ -395,8 +404,11 @@ export const setContextFormDataToObject = (
setTo: object,
): Context => {
context.req.bodyCache.json = setTo;
context.req.parseBody = () => Promise.resolve(context.req.bodyCache.json);
context.req.json = () => Promise.resolve(context.req.bodyCache.json);
context.req.parseBody = (): Promise<unknown> =>
Promise.resolve(context.req.bodyCache.json);
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
context.req.json = (): Promise<any> =>
Promise.resolve(context.req.bodyCache.json);
return context;
};
@ -406,7 +418,7 @@ export const setContextFormDataToObject = (
* Add it to random Hono routes and hope it works
* @returns
*/
export const jsonOrForm = () => {
export const jsonOrForm = (): MiddlewareHandler => {
return createMiddleware(async (context, next) => {
const contentType = context.req.header("content-type");
@ -434,7 +446,7 @@ export const jsonOrForm = () => {
});
};
export const debugRequest = async (req: Request) => {
export const debugRequest = async (req: Request): Promise<void> => {
const body = await req.text();
const logger = getLogger("server");
@ -459,7 +471,7 @@ export const debugRequest = async (req: Request) => {
}
};
export const debugResponse = async (res: Response) => {
export const debugResponse = async (res: Response): Promise<void> => {
const body = await res.clone().text();
const logger = getLogger("server");

View file

@ -1,12 +1,18 @@
import { db } from "@versia/kit/db";
import { Challenges } from "@versia/kit/tables";
import { createChallenge } from "altcha-lib";
import type { Challenge } from "altcha-lib/types";
import { sql } from "drizzle-orm";
import { config } from "~/packages/config-manager";
export const generateChallenge = async (
maxNumber = config.validation.challenges.difficulty,
) => {
): Promise<{
id: string;
challenge: Challenge;
expiresAt: string;
createdAt: string;
}> => {
const expirationDate = new Date(
Date.now() + config.validation.challenges.expiration * 1000,
);

View file

@ -1,4 +1,4 @@
import { config } from "~/packages/config-manager/index.ts";
export const localObjectUri = (id: string) =>
export const localObjectUri = (id: string): string =>
new URL(`/objects/${id}`, config.http.base_url).toString();

View file

@ -2,7 +2,12 @@ import type { ContentFormat } from "@versia/federation/types";
import { lookup } from "mime-types";
import { config } from "~/packages/config-manager";
export const getBestContentType = (content?: ContentFormat | null) => {
export const getBestContentType = (
content?: ContentFormat | null,
): {
content: string;
format: string;
} => {
if (!content) {
return { content: "", format: "text/plain" };
}

View file

@ -3,7 +3,7 @@ import { User } from "@versia/kit/db";
import chalk from "chalk";
import type { Config } from "~/packages/config-manager";
export const checkConfig = async (config: Config) => {
export const checkConfig = async (config: Config): Promise<void> => {
await checkFederationConfig(config);
await checkHttpProxyConfig(config);
@ -11,7 +11,7 @@ export const checkConfig = async (config: Config) => {
await checkChallengeConfig(config);
};
const checkHttpProxyConfig = async (config: Config) => {
const checkHttpProxyConfig = async (config: Config): Promise<void> => {
const logger = getLogger("server");
if (config.http.proxy.enabled) {
@ -35,7 +35,7 @@ const checkHttpProxyConfig = async (config: Config) => {
}
};
const checkChallengeConfig = async (config: Config) => {
const checkChallengeConfig = async (config: Config): Promise<void> => {
const logger = getLogger("server");
if (
@ -65,7 +65,7 @@ const checkChallengeConfig = async (config: Config) => {
}
};
const checkFederationConfig = async (config: Config) => {
const checkFederationConfig = async (config: Config): Promise<void> => {
const logger = getLogger("server");
if (!(config.instance.keys.public && config.instance.keys.private)) {

View file

@ -1,4 +1,5 @@
import {
type Stats,
appendFileSync,
closeSync,
existsSync,
@ -63,7 +64,7 @@ export function getBaseRotatingFileSink<TFile>(
options.flushSync(fd);
offset += bytes.length;
};
sink[Symbol.dispose] = () => options.closeSync(fd);
sink[Symbol.dispose] = (): void => options.closeSync(fd);
return sink;
}
@ -120,21 +121,21 @@ export function defaultConsoleFormatter(record: LogRecord): string[] {
}
export const nodeDriver: RotatingFileSinkDriver<number> = {
openSync(path: string) {
openSync(path: string): number {
return openSync(path, "a");
},
writeSync(fd, chunk) {
writeSync(fd, chunk): void {
appendFileSync(fd, chunk, {
flush: true,
});
},
flushSync() {
flushSync(): void {
// ...
},
closeSync(fd) {
closeSync(fd): void {
closeSync(fd);
},
statSync(path) {
statSync(path): Stats {
// If file does not exist, create it
if (!existsSync(path)) {
// Mkdir all directories in path
@ -148,7 +149,7 @@ export const nodeDriver: RotatingFileSinkDriver<number> = {
renameSync,
};
export const configureLoggers = (silent = false) =>
export const configureLoggers = (silent = false): Promise<void> =>
configure({
reset: true,
sinks: {

View file

@ -5,7 +5,10 @@ import { sentry } from "./sentry.ts";
export const renderMarkdownInPath = async (
path: string,
defaultText?: string,
) => {
): Promise<{
content: string;
lastModified: Date;
}> => {
let content = await markdownParse(defaultText ?? "");
let lastModified = new Date(1970, 0, 0);

View file

@ -1,4 +1,7 @@
export const randomString = (length: number, encoding?: BufferEncoding) =>
export const randomString = (
length: number,
encoding?: BufferEncoding,
): string =>
Buffer.from(crypto.getRandomValues(new Uint8Array(length))).toString(
encoding,
);

View file

@ -9,7 +9,7 @@ import type { Application } from "@versia/kit/db";
export const checkIfOauthIsValid = (
application: Application,
routeScopes: string[],
) => {
): boolean => {
if (routeScopes.length === 0) {
return true;
}

View file

@ -9,7 +9,7 @@ export type Json =
| Json[]
| { [key: string]: Json };
export const proxyUrl = (url: string | null = null) => {
export const proxyUrl = (url: string | null = null): string | null => {
const urlAsBase64Url = Buffer.from(url || "").toString("base64url");
return url
? new URL(

View file

@ -1,7 +1,7 @@
import { stringifyEntitiesLight } from "stringify-entities";
import xss, { type IFilterXSSOptions } from "xss";
export const sanitizedHtmlStrip = (html: string) => {
export const sanitizedHtmlStrip = (html: string): Promise<string> => {
return sanitizeHtml(html, {
whiteList: {},
});
@ -10,7 +10,7 @@ export const sanitizedHtmlStrip = (html: string) => {
export const sanitizeHtmlInline = (
html: string,
extraConfig?: IFilterXSSOptions,
) => {
): Promise<string> => {
return sanitizeHtml(html, {
whiteList: {
a: ["href", "title", "target", "rel", "class"],
@ -33,7 +33,7 @@ export const sanitizeHtmlInline = (
export const sanitizeHtml = async (
html: string,
extraConfig?: IFilterXSSOptions,
) => {
): Promise<string> => {
const sanitizedHtml = xss(html, {
whiteList: {
a: ["href", "title", "target", "rel", "class"],
@ -81,7 +81,7 @@ export const sanitizeHtml = async (
track: ["src", "label", "kind"],
},
stripIgnoreTag: false,
escapeHtml: (unsafeHtml) =>
escapeHtml: (unsafeHtml): string =>
stringifyEntitiesLight(unsafeHtml, {
escapeOnly: true,
}),
@ -103,7 +103,7 @@ export const sanitizeHtml = async (
return await new HTMLRewriter()
.on("*[class]", {
element(element) {
element(element): void {
const classes = element.getAttribute("class")?.split(" ") ?? [];
for (const className of classes) {

View file

@ -1,9 +1,13 @@
import type { OpenAPIHono } from "@hono/zod-openapi";
import type { Server } from "bun";
import type { Config } from "~/packages/config-manager/config.type";
import type { HonoEnv } from "~/types/api";
import { debugResponse } from "./api.ts";
export const createServer = (config: Config, app: OpenAPIHono<HonoEnv>) =>
export const createServer = (
config: Config,
app: OpenAPIHono<HonoEnv>,
): Server =>
Bun.serve({
port: config.http.bind_port,
reusePort: true,
@ -18,7 +22,7 @@ export const createServer = (config: Config, app: OpenAPIHono<HonoEnv>) =>
}
: undefined,
hostname: config.http.bind || "0.0.0.0", // defaults to "0.0.0.0"
async fetch(req, server) {
async fetch(req, server): Promise<Response> {
const output = await app.fetch(req, { ip: server.requestIP(req) });
if (config.logging.log_responses) {

View file

@ -1,4 +1,5 @@
import type { db } from "@versia/kit/db";
import type { SQL } from "drizzle-orm";
import type {
Notification,
findManyNotifications,
@ -18,7 +19,10 @@ export async function fetchTimeline<T extends UserType | Status | Notification>(
| Parameters<typeof db.query.Notifications.findMany>[0],
req: Request,
userId?: string,
) {
): Promise<{
link: string;
objects: T[];
}> {
// BEFORE: Before in a top-to-bottom order, so the most recent posts
// AFTER: After in a top-to-bottom order, so the oldest posts
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
@ -37,7 +41,8 @@ export async function fetchTimeline<T extends UserType | Status | Notification>(
const objectsBefore = await model({
...args,
// @ts-expect-error this hack breaks typing :(
where: (object, { gt }) => gt(object.id, objects[0].id),
where: (object, { gt }): SQL | undefined =>
gt(object.id, objects[0].id),
limit: 1,
});
@ -56,7 +61,8 @@ export async function fetchTimeline<T extends UserType | Status | Notification>(
const objectsAfter = await model({
...args,
// @ts-expect-error this hack breaks typing :(
where: (object, { lt }) => lt(object.id, objects.at(-1).id),
where: (object, { lt }): SQL | undefined =>
lt(object.id, objects.at(-1)?.id),
limit: 1,
});