mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
refactor: ♻️ Always use explicit types in every function
This commit is contained in:
parent
54cea29ce9
commit
c1dcdc78ae
62 changed files with 359 additions and 226 deletions
48
utils/api.ts
48
utils/api.ts
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue