From c1dcdc78aed17b6d56b453598a42c2d6ba2005bf Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Sat, 2 Nov 2024 00:43:33 +0100 Subject: [PATCH] refactor: :recycle: Always use explicit types in every function --- api/api/auth/login/index.ts | 6 +- api/api/auth/redirect/index.ts | 2 +- api/api/auth/reset/index.ts | 2 +- .../v1/accounts/familiar_followers/index.ts | 6 +- api/api/v1/markers/index.ts | 6 +- api/api/v1/notifications/:id/index.ts | 4 +- api/api/v1/notifications/index.test.ts | 4 +- api/api/v1/notifications/index.ts | 7 +- api/api/v1/statuses/:id/pin.ts | 3 +- api/api/v2/filters/:id/index.ts | 6 +- api/api/v2/filters/index.ts | 4 +- app.ts | 2 +- biome.json | 12 ++- classes/database/application.ts | 5 +- classes/database/attachment.ts | 4 +- classes/database/emoji.ts | 2 +- classes/database/instance.ts | 2 +- classes/database/like.ts | 6 +- classes/database/note.ts | 12 +-- classes/database/relationship.ts | 4 +- classes/database/role.ts | 25 ++++-- classes/database/timeline.ts | 4 +- classes/database/user.ts | 84 ++++++++++--------- classes/functions/status.ts | 10 +-- classes/functions/user.ts | 13 ++- classes/inbox/processor.ts | 20 +++-- classes/media/drivers/s3.test.ts | 3 +- classes/media/preprocessors/blurhash.test.ts | 2 +- classes/plugin/loader.test.ts | 26 +++--- classes/search/search-manager.ts | 26 +++--- cli/classes.ts | 14 +++- cli/commands/user/create.ts | 4 +- drizzle/db.ts | 2 +- middlewares/bait.ts | 4 +- packages/config-manager/index.ts | 2 +- packages/plugin-kit/index.ts | 10 +-- packages/plugin-kit/plugin.ts | 11 +-- packages/plugin-kit/schema.ts | 1 + plugins/openid/routes/authorize.ts | 2 +- plugins/openid/routes/jwks.ts | 2 +- plugins/openid/routes/oauth/callback.ts | 8 +- plugins/openid/routes/oauth/revoke.ts | 6 +- plugins/openid/routes/oauth/sso.ts | 2 +- plugins/openid/routes/oauth/token.ts | 6 +- plugins/openid/routes/sso/:id/index.ts | 8 +- plugins/openid/routes/sso/index.ts | 2 +- plugins/openid/utils.ts | 30 +++++-- tests/api/accounts.test.ts | 4 +- tests/utils.ts | 23 +++-- utils/api.ts | 48 +++++++---- utils/challenges.ts | 8 +- utils/constants.ts | 2 +- utils/content_types.ts | 7 +- utils/init.ts | 8 +- utils/loggers.ts | 15 ++-- utils/markdown.ts | 5 +- utils/math.ts | 5 +- utils/oauth.ts | 2 +- utils/response.ts | 2 +- utils/sanitization.ts | 10 +-- utils/server.ts | 8 +- utils/timelines.ts | 12 ++- 62 files changed, 359 insertions(+), 226 deletions(-) diff --git a/api/api/auth/login/index.ts b/api/api/auth/login/index.ts index 4b1b8d76..098a8c2d 100644 --- a/api/api/auth/login/index.ts +++ b/api/api/auth/login/index.ts @@ -86,7 +86,11 @@ const route = createRoute({ }, }); -const returnError = (context: Context, error: string, description: string) => { +const returnError = ( + context: Context, + error: string, + description: string, +): Response => { const searchParams = new URLSearchParams(); // Add all data that is not undefined except email and password diff --git a/api/api/auth/redirect/index.ts b/api/api/auth/redirect/index.ts index a0423a73..229f1f54 100644 --- a/api/api/auth/redirect/index.ts +++ b/api/api/auth/redirect/index.ts @@ -49,7 +49,7 @@ export default apiRoute((app) => app.openapi(route, async (context) => { const { redirect_uri, client_id, code } = context.req.valid("query"); - const redirectToLogin = (error: string) => + const redirectToLogin = (error: string): Response => context.redirect( `${config.frontend.routes.login}?${new URLSearchParams({ ...context.req.query, diff --git a/api/api/auth/reset/index.ts b/api/api/auth/reset/index.ts index 70a01fbc..737ba1ee 100644 --- a/api/api/auth/reset/index.ts +++ b/api/api/auth/reset/index.ts @@ -54,7 +54,7 @@ const returnError = ( token: string, error: string, description: string, -) => { +): Response => { const searchParams = new URLSearchParams(); searchParams.append("error", error); diff --git a/api/api/v1/accounts/familiar_followers/index.ts b/api/api/v1/accounts/familiar_followers/index.ts index 953edd36..c7d6f589 100644 --- a/api/api/v1/accounts/familiar_followers/index.ts +++ b/api/api/v1/accounts/familiar_followers/index.ts @@ -2,7 +2,7 @@ import { apiRoute, applyConfig, auth, qsQuery } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { User, db } from "@versia/kit/db"; import { RolePermissions, Users } from "@versia/kit/tables"; -import { inArray } from "drizzle-orm"; +import { type SQL, inArray } from "drizzle-orm"; import { z } from "zod"; import { ErrorSchema } from "~/types/api"; @@ -70,7 +70,7 @@ export default apiRoute((app) => columns: { ownerId: true, }, - where: (relationship, { inArray, and, eq }) => + where: (relationship, { inArray, and, eq }): SQL | undefined => and( inArray( relationship.subjectId, @@ -89,7 +89,7 @@ export default apiRoute((app) => columns: { subjectId: true, }, - where: (relationship, { inArray, and, eq }) => + where: (relationship, { inArray, and, eq }): SQL | undefined => and( eq(relationship.ownerId, self.id), inArray( diff --git a/api/api/v1/markers/index.ts b/api/api/v1/markers/index.ts index 4f187389..a9265559 100644 --- a/api/api/v1/markers/index.ts +++ b/api/api/v1/markers/index.ts @@ -3,7 +3,7 @@ import { createRoute } from "@hono/zod-openapi"; import type { Marker as ApiMarker } from "@versia/client/types"; import { db } from "@versia/kit/db"; import { Markers, RolePermissions } from "@versia/kit/tables"; -import { and, eq } from "drizzle-orm"; +import { type SQL, and, eq } from "drizzle-orm"; import { z } from "zod"; import { ErrorSchema } from "~/types/api"; @@ -133,7 +133,7 @@ export default apiRoute((app) => { if (timeline.includes("home")) { const found = await db.query.Markers.findFirst({ - where: (marker, { and, eq }) => + where: (marker, { and, eq }): SQL | undefined => and( eq(marker.userId, user.id), eq(marker.timeline, "home"), @@ -156,7 +156,7 @@ export default apiRoute((app) => { if (timeline.includes("notifications")) { const found = await db.query.Markers.findFirst({ - where: (marker, { and, eq }) => + where: (marker, { and, eq }): SQL | undefined => and( eq(marker.userId, user.id), eq(marker.timeline, "notifications"), diff --git a/api/api/v1/notifications/:id/index.ts b/api/api/v1/notifications/:id/index.ts index 10739d5b..bb7f771d 100644 --- a/api/api/v1/notifications/:id/index.ts +++ b/api/api/v1/notifications/:id/index.ts @@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { Note, User } from "@versia/kit/db"; import { RolePermissions } from "@versia/kit/tables"; +import type { SQL } from "drizzle-orm"; import { z } from "zod"; import { findManyNotifications, @@ -103,7 +104,8 @@ export default apiRoute((app) => const notification = ( await findManyNotifications( { - where: (notification, { eq }) => eq(notification.id, id), + where: (notification, { eq }): SQL | undefined => + eq(notification.id, id), limit: 1, }, user.id, diff --git a/api/api/v1/notifications/index.test.ts b/api/api/v1/notifications/index.test.ts index 0ed6c795..7c06f11d 100644 --- a/api/api/v1/notifications/index.test.ts +++ b/api/api/v1/notifications/index.test.ts @@ -3,7 +3,9 @@ import type { Notification as ApiNotification } from "@versia/client/types"; import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils"; import { meta } from "./index.ts"; -const getFormData = (object: Record) => +const getFormData = ( + object: Record, +): FormData => Object.keys(object).reduce((formData, key) => { formData.append(key, String(object[key])); return formData; diff --git a/api/api/v1/notifications/index.ts b/api/api/v1/notifications/index.ts index 0c498327..b23635f0 100644 --- a/api/api/v1/notifications/index.ts +++ b/api/api/v1/notifications/index.ts @@ -3,7 +3,7 @@ import { fetchTimeline } from "@/timelines"; import { createRoute } from "@hono/zod-openapi"; import { Note, User } from "@versia/kit/db"; import { RolePermissions } from "@versia/kit/tables"; -import { sql } from "drizzle-orm"; +import { type SQL, sql } from "drizzle-orm"; import { z } from "zod"; import { findManyNotifications, @@ -159,7 +159,7 @@ export default apiRoute((app) => notification, // @ts-expect-error Yes I KNOW the types are wrong { lt, gte, gt, and, eq, not, inArray }, - ) => + ): SQL | undefined => and( max_id ? lt(notification.id, max_id) : undefined, since_id @@ -200,7 +200,8 @@ export default apiRoute((app) => ), limit, // @ts-expect-error Yes I KNOW the types are wrong - orderBy: (notification, { desc }) => desc(notification.id), + orderBy: (notification, { desc }): SQL | undefined => + desc(notification.id), }, context.req.raw, user.id, diff --git a/api/api/v1/statuses/:id/pin.ts b/api/api/v1/statuses/:id/pin.ts index 0c923993..bb72bc1e 100644 --- a/api/api/v1/statuses/:id/pin.ts +++ b/api/api/v1/statuses/:id/pin.ts @@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth, idValidator } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { Note, db } from "@versia/kit/db"; import { RolePermissions } from "@versia/kit/tables"; +import type { SQL } from "drizzle-orm"; import { z } from "zod"; import { ErrorSchema } from "~/types/api"; @@ -90,7 +91,7 @@ export default apiRoute((app) => if ( await db.query.UserToPinnedNotes.findFirst({ - where: (userPinnedNote, { and, eq }) => + where: (userPinnedNote, { and, eq }): SQL | undefined => and( eq(userPinnedNote.noteId, foundStatus.data.id), eq(userPinnedNote.userId, user.id), diff --git a/api/api/v2/filters/:id/index.ts b/api/api/v2/filters/:id/index.ts index 1bc63ff8..d8ab0e58 100644 --- a/api/api/v2/filters/:id/index.ts +++ b/api/api/v2/filters/:id/index.ts @@ -2,7 +2,7 @@ import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { db } from "@versia/kit/db"; import { FilterKeywords, Filters, RolePermissions } from "@versia/kit/tables"; -import { and, eq, inArray } from "drizzle-orm"; +import { type SQL, and, eq, inArray } from "drizzle-orm"; import { z } from "zod"; import { ErrorSchema } from "~/types/api"; @@ -204,7 +204,7 @@ export default apiRoute((app) => { } const userFilter = await db.query.Filters.findFirst({ - where: (filter, { eq, and }) => + where: (filter, { eq, and }): SQL | undefined => and(eq(filter.userId, user.id), eq(filter.id, id)), with: { keywords: true, @@ -300,7 +300,7 @@ export default apiRoute((app) => { } const updatedFilter = await db.query.Filters.findFirst({ - where: (filter, { eq, and }) => + where: (filter, { eq, and }): SQL | undefined => and(eq(filter.userId, user.id), eq(filter.id, id)), with: { keywords: true, diff --git a/api/api/v2/filters/index.ts b/api/api/v2/filters/index.ts index 7526c01e..4775a014 100644 --- a/api/api/v2/filters/index.ts +++ b/api/api/v2/filters/index.ts @@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { db } from "@versia/kit/db"; import { FilterKeywords, Filters, RolePermissions } from "@versia/kit/tables"; +import type { SQL } from "drizzle-orm"; import { z } from "zod"; import { ErrorSchema } from "~/types/api"; export const meta = applyConfig({ @@ -139,7 +140,8 @@ export default apiRoute((app) => { } const userFilters = await db.query.Filters.findMany({ - where: (filter, { eq }) => eq(filter.userId, user.id), + where: (filter, { eq }): SQL | undefined => + eq(filter.userId, user.id), with: { keywords: true, }, diff --git a/app.ts b/app.ts index cb56fc4b..389226ce 100644 --- a/app.ts +++ b/app.ts @@ -23,7 +23,7 @@ import { logger } from "./middlewares/logger.ts"; import { routes } from "./routes.ts"; import type { ApiRouteExports, HonoEnv } from "./types/api.ts"; -export const appFactory = async () => { +export const appFactory = async (): Promise> => { await configureLoggers(); const serverLogger = getLogger("server"); diff --git a/biome.json b/biome.json index 6c3e5655..896bc056 100644 --- a/biome.json +++ b/biome.json @@ -66,7 +66,17 @@ "options": { "accessibility": "explicit" } - } + }, + "noCommonJs": "warn", + "noDynamicNamespaceImportAccess": "warn", + "noExportedImports": "warn", + "noIrregularWhitespace": "warn", + "noSubstr": "warn", + "noTemplateCurlyInString": "warn", + "noUselessEscapeInRegex": "warn", + "noUselessStringRaw": "warn", + "useAdjacentOverloadSignatures": "warn", + "useExplicitType": "warn" } } }, diff --git a/classes/database/application.ts b/classes/database/application.ts index abcef36d..f5ebdd44 100644 --- a/classes/database/application.ts +++ b/classes/database/application.ts @@ -82,7 +82,8 @@ export class Application extends BaseInterface { token: string, ): Promise { const result = await db.query.Tokens.findFirst({ - where: (tokens, { eq }) => eq(tokens.accessToken, token), + where: (tokens, { eq }): SQL | undefined => + eq(tokens.accessToken, token), with: { application: true, }, @@ -141,7 +142,7 @@ export class Application extends BaseInterface { return application; } - public get id() { + public get id(): string { return this.data.id; } diff --git a/classes/database/attachment.ts b/classes/database/attachment.ts index 41c3ce74..cd1b522f 100644 --- a/classes/database/attachment.ts +++ b/classes/database/attachment.ts @@ -148,11 +148,11 @@ export class Attachment extends BaseInterface { return attachment; } - public get id() { + public get id(): string { return this.data.id; } - public static getUrl(name: string) { + public static getUrl(name: string): string { if (config.media.backend === MediaBackendType.Local) { return new URL(`/media/${name}`, config.http.base_url).toString(); } diff --git a/classes/database/emoji.ts b/classes/database/emoji.ts index 91d8ab34..a6feec5d 100644 --- a/classes/database/emoji.ts +++ b/classes/database/emoji.ts @@ -160,7 +160,7 @@ export class Emoji extends BaseInterface { return await Emoji.fromVersia(emojiToFetch, foundInstance?.id ?? null); } - public get id() { + public get id(): string { return this.data.id; } diff --git a/classes/database/instance.ts b/classes/database/instance.ts index 274beacf..3cc71513 100644 --- a/classes/database/instance.ts +++ b/classes/database/instance.ts @@ -132,7 +132,7 @@ export class Instance extends BaseInterface { return instance; } - public get id() { + public get id(): string { return this.data.id; } diff --git a/classes/database/like.ts b/classes/database/like.ts index 5d5eb939..083c13cd 100644 --- a/classes/database/like.ts +++ b/classes/database/like.ts @@ -59,7 +59,7 @@ export class Like extends BaseInterface { public static async fromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(Likes.id), - ) { + ): Promise { const found = await db.query.Likes.findFirst({ where: sql, orderBy, @@ -81,7 +81,7 @@ export class Like extends BaseInterface { limit?: number, offset?: number, extra?: Parameters[0], - ) { + ): Promise { const found = await db.query.Likes.findMany({ where: sql, orderBy, @@ -135,7 +135,7 @@ export class Like extends BaseInterface { return role; } - public get id() { + public get id(): string { return this.data.id; } diff --git a/classes/database/note.ts b/classes/database/note.ts index dbc300ed..a46471a2 100644 --- a/classes/database/note.ts +++ b/classes/database/note.ts @@ -276,7 +276,7 @@ export class Note extends BaseInterface { return found.map((s) => new Note(s)); } - public get id() { + public get id(): string { return this.data.id; } @@ -319,7 +319,7 @@ export class Note extends BaseInterface { { with: { relationships: { - where: (relationship, { eq, and }) => + where: (relationship, { eq, and }): SQL | undefined => and( eq(relationship.subjectId, this.data.authorId), eq(relationship.following, true), @@ -339,7 +339,7 @@ export class Note extends BaseInterface { return deduplicatedUsersById; } - public get author() { + public get author(): User { return new User(this.data.author); } @@ -369,7 +369,7 @@ export class Note extends BaseInterface { ); } - public isRemote() { + public isRemote(): boolean { return this.author.isRemote(); } @@ -846,7 +846,7 @@ export class Note extends BaseInterface { if (this.data.visibility === "private") { return user ? !!(await db.query.Relationships.findFirst({ - where: (relationship, { and, eq }) => + where: (relationship, { and, eq }): SQL | undefined => and( eq(relationship.ownerId, user?.id), eq(relationship.subjectId, Notes.authorId), @@ -877,7 +877,7 @@ export class Note extends BaseInterface { // Rewrite all src tags to go through proxy let replacedContent = new HTMLRewriter() .on("[src]", { - element(element) { + element(element): void { element.setAttribute( "src", proxyUrl(element.getAttribute("src") ?? "") ?? "", diff --git a/classes/database/relationship.ts b/classes/database/relationship.ts index 5321cb30..a79ad3f5 100644 --- a/classes/database/relationship.ts +++ b/classes/database/relationship.ts @@ -198,7 +198,7 @@ export class Relationship extends BaseInterface< ownerId: string; }): Promise { let output = await db.query.Relationships.findFirst({ - where: (rel, { and, eq }) => + where: (rel, { and, eq }): SQL | undefined => and( eq(rel.ownerId, oppositeTo.subjectId), eq(rel.subjectId, oppositeTo.ownerId), @@ -286,7 +286,7 @@ export class Relationship extends BaseInterface< return relationship; } - public get id() { + public get id(): string { return this.data.id; } diff --git a/classes/database/role.ts b/classes/database/role.ts index dcd07fdf..37cb1c70 100644 --- a/classes/database/role.ts +++ b/classes/database/role.ts @@ -1,5 +1,8 @@ import { proxyUrl } from "@/response"; -import { RolePermission } from "@versia/client/types"; +import { + type VersiaRole as APIRole, + RolePermission, +} from "@versia/client/types"; import { db } from "@versia/kit/db"; import { RoleToUsers, Roles } from "@versia/kit/tables"; import { @@ -53,7 +56,7 @@ export class Role extends BaseInterface { public static async fromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(Roles.id), - ) { + ): Promise { const found = await db.query.Roles.findFirst({ where: sql, orderBy, @@ -65,10 +68,14 @@ export class Role extends BaseInterface { return new Role(found); } - public static async getUserRoles(userId: string, isAdmin: boolean) { + public static async getUserRoles( + userId: string, + isAdmin: boolean, + ): Promise { return ( await db.query.RoleToUsers.findMany({ - where: (role, { eq }) => eq(role.userId, userId), + where: (role, { eq }): SQL | undefined => + eq(role.userId, userId), with: { role: true, user: { @@ -115,7 +122,7 @@ export class Role extends BaseInterface { limit?: number, offset?: number, extra?: Parameters[0], - ) { + ): Promise { const found = await db.query.Roles.findMany({ where: sql, orderBy, @@ -165,14 +172,14 @@ export class Role extends BaseInterface { return role; } - public async linkUser(userId: string) { + public async linkUser(userId: string): Promise { await db.insert(RoleToUsers).values({ userId, roleId: this.id, }); } - public async unlinkUser(userId: string) { + public async unlinkUser(userId: string): Promise { await db .delete(RoleToUsers) .where( @@ -183,11 +190,11 @@ export class Role extends BaseInterface { ); } - public get id() { + public get id(): string { return this.data.id; } - public toApi() { + public toApi(): APIRole { return { id: this.id, name: this.data.name, diff --git a/classes/database/timeline.ts b/classes/database/timeline.ts index 59dc8c7f..d4ea4190 100644 --- a/classes/database/timeline.ts +++ b/classes/database/timeline.ts @@ -17,7 +17,7 @@ export class Timeline { limit: number, url: string, userId?: string, - ) { + ): Promise<{ link: string; objects: Note[] }> { return new Timeline(TimelineType.Note).fetchTimeline( sql, limit, @@ -30,7 +30,7 @@ export class Timeline { sql: SQL | undefined, limit: number, url: string, - ) { + ): Promise<{ link: string; objects: User[] }> { return new Timeline(TimelineType.User).fetchTimeline( sql, limit, diff --git a/classes/database/user.ts b/classes/database/user.ts index c897893f..42071724 100644 --- a/classes/database/user.ts +++ b/classes/database/user.ts @@ -150,7 +150,7 @@ export class User extends BaseInterface { public static async fromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(Users.id), - ) { + ): Promise { const found = await findManyUsers({ where: sql, orderBy, @@ -168,7 +168,7 @@ export class User extends BaseInterface { limit?: number, offset?: number, extra?: Parameters[0], - ) { + ): Promise { const found = await findManyUsers({ where: sql, orderBy, @@ -180,34 +180,38 @@ export class User extends BaseInterface { return found.map((s) => new User(s)); } - public get id() { + public get id(): string { return this.data.id; } - public isLocal() { + public isLocal(): boolean { return this.data.instanceId === null; } - public isRemote() { + public isRemote(): boolean { return !this.isLocal(); } - public getUri() { + public getUri(): string { return ( this.data.uri || new URL(`/users/${this.data.id}`, config.http.base_url).toString() ); } - public static getUri(id: string, uri: string | null, baseUrl: string) { + public static getUri( + id: string, + uri: string | null, + baseUrl: string, + ): string { return uri || new URL(`/users/${id}`, baseUrl).toString(); } - public hasPermission(permission: RolePermissions) { + public hasPermission(permission: RolePermissions): boolean { return this.getAllPermissions().includes(permission); } - public getAllPermissions() { + public getAllPermissions(): RolePermissions[] { return ( this.data.roles .flatMap((role) => role.permissions) @@ -270,7 +274,10 @@ export class User extends BaseInterface { return foundRelationship; } - public async unfollow(followee: User, relationship: Relationship) { + public async unfollow( + followee: User, + relationship: Relationship, + ): Promise { if (followee.isRemote()) { // TODO: This should reschedule for a later time and maybe notify the server admin if it fails too often const { ok } = await this.federateToUser( @@ -348,7 +355,9 @@ export class User extends BaseInterface { return db.$count(Users, isNull(Users.instanceId)); } - public static async getActiveInPeriod(milliseconds: number) { + public static async getActiveInPeriod( + milliseconds: number, + ): Promise { return ( await db .select({ @@ -376,7 +385,7 @@ export class User extends BaseInterface { } } - public async resetPassword() { + public async resetPassword(): Promise { const resetToken = randomString(32, "hex"); await this.update({ @@ -386,30 +395,22 @@ export class User extends BaseInterface { return resetToken; } - public async pin(note: Note) { - return ( - await db - .insert(UserToPinnedNotes) - .values({ - noteId: note.id, - userId: this.id, - }) - .returning() - )[0]; + public async pin(note: Note): Promise { + await db.insert(UserToPinnedNotes).values({ + noteId: note.id, + userId: this.id, + }); } - public async unpin(note: Note) { - return ( - await db - .delete(UserToPinnedNotes) - .where( - and( - eq(NoteToMentions.noteId, note.id), - eq(NoteToMentions.userId, this.id), - ), - ) - .returning() - )[0]; + public async unpin(note: Note): Promise { + await db + .delete(UserToPinnedNotes) + .where( + and( + eq(NoteToMentions.noteId, note.id), + eq(NoteToMentions.userId, this.id), + ), + ); } public save(): Promise { @@ -434,7 +435,7 @@ export class User extends BaseInterface { > { // Get all linked accounts const accounts = await db.query.OpenIdAccounts.findMany({ - where: (User, { eq }) => eq(User.userId, this.id), + where: (User, { eq }): SQL | undefined => eq(User.userId, this.id), }); return accounts @@ -715,7 +716,7 @@ export class User extends BaseInterface { * @param config The config to use * @returns The raw URL for the user's avatar */ - public getAvatarUrl(config: Config) { + public getAvatarUrl(config: Config): string { if (!this.data.avatar) { return ( config.defaults.avatar || @@ -725,7 +726,10 @@ export class User extends BaseInterface { return this.data.avatar; } - public static async generateKeys() { + public static async generateKeys(): Promise<{ + private_key: string; + public_key: string; + }> { const keys = await crypto.subtle.generateKey("Ed25519", true, [ "sign", "verify", @@ -807,14 +811,14 @@ export class User extends BaseInterface { * @param config The config to use * @returns The raw URL for the user's header */ - public getHeaderUrl(config: Config) { + public getHeaderUrl(config: Config): string { if (!this.data.header) { return config.defaults.header || ""; } return this.data.header; } - public getAcct() { + public getAcct(): string { return this.isLocal() ? this.data.username : `${this.data.username}@${this.data.instance?.baseUrl}`; @@ -824,7 +828,7 @@ export class User extends BaseInterface { isLocal: boolean, username: string, baseUrl?: string, - ) { + ): string { return isLocal ? username : `${username}@${baseUrl}`; } diff --git a/classes/functions/status.ts b/classes/functions/status.ts index 4027ab11..ade0d2a6 100644 --- a/classes/functions/status.ts +++ b/classes/functions/status.ts @@ -261,7 +261,7 @@ export const parseTextMentions = async ( const baseUrlHost = new URL(config.http.base_url).host; - const isLocal = (host?: string) => host === baseUrlHost || !host; + const isLocal = (host?: string): boolean => host === baseUrlHost || !host; const foundUsers = await db .select({ @@ -326,7 +326,7 @@ export const parseTextMentions = async ( return finalList; }; -export const replaceTextMentions = (text: string, mentions: User[]) => { +export const replaceTextMentions = (text: string, mentions: User[]): string => { let finalText = text; for (const mention of mentions) { const user = mention.data; @@ -405,7 +405,7 @@ export const contentToHtml = async ( htmlContent = linkifyHtml(htmlContent, { defaultProtocol: "https", validate: { - email: () => false, + email: (): false => false, }, target: "_blank", rel: "nofollow noopener noreferrer", @@ -414,11 +414,11 @@ export const contentToHtml = async ( return htmlContent; }; -export const markdownParse = async (content: string) => { +export const markdownParse = async (content: string): Promise => { return (await getMarkdownRenderer()).render(content); }; -export const getMarkdownRenderer = () => { +export const getMarkdownRenderer = (): MarkdownIt => { const renderer = MarkdownIt({ html: true, linkify: true, diff --git a/classes/functions/user.ts b/classes/functions/user.ts index 47e5c5d9..78a7b8a7 100644 --- a/classes/functions/user.ts +++ b/classes/functions/user.ts @@ -11,7 +11,7 @@ import { Tokens, type Users, } from "@versia/kit/tables"; -import { type InferSelectModel, eq, sql } from "drizzle-orm"; +import { type InferSelectModel, type SQL, eq, sql } from "drizzle-orm"; import type { ApplicationType } from "~/classes/database/application.ts"; import type { EmojiWithInstance } from "~/classes/database/emoji.ts"; import type { Token } from "./token.ts"; @@ -80,7 +80,13 @@ export const userExtras = { ), }; -export const userExtrasTemplate = (name: string) => ({ +export const userExtrasTemplate = ( + name: string, +): { + followerCount: SQL.Aliased; + followingCount: SQL.Aliased; + statusCount: SQL.Aliased; +} => ({ // @ts-expect-error sql is a template tag, so it gets confused when we use it as a function followerCount: sql([ `(SELECT COUNT(*) FROM "Relationships" "relationships" WHERE ("relationships"."ownerId" = "${name}".id AND "relationships"."following" = true))`, @@ -214,7 +220,8 @@ export const retrieveToken = async ( return ( (await db.query.Tokens.findFirst({ - where: (tokens, { eq }) => eq(tokens.accessToken, accessToken), + where: (tokens, { eq }): SQL | undefined => + eq(tokens.accessToken, accessToken), })) ?? null ); }; diff --git a/classes/inbox/processor.ts b/classes/inbox/processor.ts index d09eba51..82f25dfe 100644 --- a/classes/inbox/processor.ts +++ b/classes/inbox/processor.ts @@ -219,14 +219,18 @@ export class InboxProcessor { try { return await handler.parseBody({ - note: () => this.processNote(), - follow: () => this.processFollowRequest(), - followAccept: () => this.processFollowAccept(), - followReject: () => this.processFollowReject(), - "pub.versia:likes/Like": () => this.processLikeRequest(), - delete: () => this.processDelete(), - user: () => this.processUserRequest(), - unknown: () => + note: (): Promise => this.processNote(), + follow: (): Promise => this.processFollowRequest(), + followAccept: (): Promise => + this.processFollowAccept(), + followReject: (): Promise => + this.processFollowReject(), + "pub.versia:likes/Like": (): Promise => + this.processLikeRequest(), + delete: (): Promise => this.processDelete(), + user: (): Promise => this.processUserRequest(), + unknown: (): Response & + TypedResponse<{ error: string }, 400, "json"> => this.context.json({ error: "Unknown entity type" }, 400), }); } catch (e) { diff --git a/classes/media/drivers/s3.test.ts b/classes/media/drivers/s3.test.ts index 46cd8c15..511e927a 100644 --- a/classes/media/drivers/s3.test.ts +++ b/classes/media/drivers/s3.test.ts @@ -30,7 +30,8 @@ describe("S3MediaDriver", () => { putObject: mock(() => Promise.resolve()), getObject: mock(() => Promise.resolve({ - arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)), + arrayBuffer: (): Promise => + Promise.resolve(new ArrayBuffer(8)), headers: new Headers({ "Content-Type": "image/webp" }), }), ), diff --git a/classes/media/preprocessors/blurhash.test.ts b/classes/media/preprocessors/blurhash.test.ts index 233fd4d0..ce89f4df 100644 --- a/classes/media/preprocessors/blurhash.test.ts +++ b/classes/media/preprocessors/blurhash.test.ts @@ -58,7 +58,7 @@ describe("BlurhashPreprocessor", () => { }); mock.module("blurhash", () => ({ - encode: () => { + encode: (): void => { throw new Error("Test error"); }, })); diff --git a/classes/plugin/loader.test.ts b/classes/plugin/loader.test.ts index fd965e7a..bc673f6e 100644 --- a/classes/plugin/loader.test.ts +++ b/classes/plugin/loader.test.ts @@ -50,9 +50,9 @@ describe("PluginLoader", () => { test("getDirectories should return directories", async () => { mockReaddir.mockResolvedValue([ - { name: "dir1", isDirectory: () => true }, - { name: "file1", isDirectory: () => false }, - { name: "dir2", isDirectory: () => true }, + { name: "dir1", isDirectory: (): true => true }, + { name: "file1", isDirectory: (): false => false }, + { name: "dir2", isDirectory: (): true => true }, ]); // biome-ignore lint/complexity/useLiteralKeys: Private method @@ -80,7 +80,8 @@ describe("PluginLoader", () => { test("parseManifestFile should parse JSON manifest", async () => { const manifestContent = { name: "test-plugin" }; Bun.file = jest.fn().mockReturnValue({ - text: () => Promise.resolve(JSON.stringify(manifestContent)), + text: (): Promise => + Promise.resolve(JSON.stringify(manifestContent)), }); // biome-ignore lint/complexity/useLiteralKeys: Private method @@ -94,8 +95,8 @@ describe("PluginLoader", () => { test("findPlugins should return plugin directories with valid manifest and entrypoint", async () => { mockReaddir .mockResolvedValueOnce([ - { name: "plugin1", isDirectory: () => true }, - { name: "plugin2", isDirectory: () => true }, + { name: "plugin1", isDirectory: (): true => true }, + { name: "plugin2", isDirectory: (): true => true }, ]) .mockResolvedValue(["manifest.json", "index.ts"]); @@ -111,7 +112,8 @@ describe("PluginLoader", () => { }; mockReaddir.mockResolvedValue(["manifest.json"]); Bun.file = jest.fn().mockReturnValue({ - text: () => Promise.resolve(JSON.stringify(manifestContent)), + text: (): Promise => + Promise.resolve(JSON.stringify(manifestContent)), }); manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({ success: true, @@ -141,7 +143,8 @@ describe("PluginLoader", () => { }; mockReaddir.mockResolvedValue(["manifest.json"]); Bun.file = jest.fn().mockReturnValue({ - text: () => Promise.resolve(JSON.stringify(manifestContent)), + text: (): Promise => + Promise.resolve(JSON.stringify(manifestContent)), }); manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({ success: false, @@ -183,12 +186,13 @@ describe("PluginLoader", () => { mockReaddir .mockResolvedValueOnce([ - { name: "plugin1", isDirectory: () => true }, - { name: "plugin2", isDirectory: () => true }, + { name: "plugin1", isDirectory: (): true => true }, + { name: "plugin2", isDirectory: (): true => true }, ]) .mockResolvedValue(["manifest.json", "index.ts"]); Bun.file = jest.fn().mockReturnValue({ - text: () => Promise.resolve(JSON.stringify(manifestContent)), + text: (): Promise => + Promise.resolve(JSON.stringify(manifestContent)), }); manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({ success: true, diff --git a/classes/search/search-manager.ts b/classes/search/search-manager.ts index e23fac3c..fe262f51 100644 --- a/classes/search/search-manager.ts +++ b/classes/search/search-manager.ts @@ -5,6 +5,7 @@ import { getLogger } from "@logtape/logtape"; import { Note, User, db } from "@versia/kit/db"; +import type { SQL, ValueOrArray } from "drizzle-orm"; import { Ingest as SonicChannelIngest, Search as SonicChannelSearch, @@ -63,21 +64,21 @@ export class SonicSearchManager { // Connect to Sonic await new Promise((resolve, reject) => { this.searchChannel.connect({ - connected: () => { + connected: (): void => { !silent && this.logger.info`Connected to Sonic Search Channel`; resolve(true); }, - disconnected: () => + disconnected: (): void => this.logger .error`Disconnected from Sonic Search Channel. You might be using an incorrect password.`, - timeout: () => + timeout: (): void => this.logger .error`Sonic Search Channel connection timed out`, - retrying: () => + retrying: (): void => this.logger .warn`Retrying connection to Sonic Search Channel`, - error: (error) => { + error: (error): void => { this.logger .error`Failed to connect to Sonic Search Channel: ${error}`; reject(error); @@ -87,20 +88,20 @@ export class SonicSearchManager { await new Promise((resolve, reject) => { this.ingestChannel.connect({ - connected: () => { + connected: (): void => { !silent && this.logger.info`Connected to Sonic Ingest Channel`; resolve(true); }, - disconnected: () => + disconnected: (): void => this.logger.error`Disconnected from Sonic Ingest Channel`, - timeout: () => + timeout: (): void => this.logger .error`Sonic Ingest Channel connection timed out`, - retrying: () => + retrying: (): void => this.logger .warn`Retrying connection to Sonic Ingest Channel`, - error: (error) => { + error: (error): void => { this.logger .error`Failed to connect to Sonic Ingest Channel: ${error}`; reject(error); @@ -161,7 +162,7 @@ export class SonicSearchManager { note: true, createdAt: true, }, - orderBy: (user, { asc }) => asc(user.createdAt), + orderBy: (user, { asc }): ValueOrArray => asc(user.createdAt), }); } @@ -182,7 +183,8 @@ export class SonicSearchManager { content: true, createdAt: true, }, - orderBy: (status, { asc }) => asc(status.createdAt), + orderBy: (status, { asc }): ValueOrArray => + asc(status.createdAt), }); } diff --git a/cli/classes.ts b/cli/classes.ts index 84e48638..e2902dd0 100644 --- a/cli/classes.ts +++ b/cli/classes.ts @@ -3,7 +3,13 @@ import { Args, type Command, Flags, type Interfaces } from "@oclif/core"; import { Instance, User, db } from "@versia/kit/db"; import { Emojis, Instances, Users } from "@versia/kit/tables"; import chalk from "chalk"; -import { and, eq, getTableColumns, like } from "drizzle-orm"; +import { + type InferSelectModel, + and, + eq, + getTableColumns, + like, +} from "drizzle-orm"; import { BaseCommand } from "./base.ts"; export type FlagsType = Interfaces.InferredFlags< @@ -203,7 +209,11 @@ export abstract class EmojiFinderCommand< this.args = args as ArgsType; } - public async findEmojis() { + public async findEmojis(): Promise< + (InferSelectModel & { + instanceUrl: string | null; + })[] + > { // Check if there are asterisks in the identifier but no pattern flag, warn the user if so if (this.args.identifier.includes("*") && !this.flags.pattern) { this.log( diff --git a/cli/commands/user/create.ts b/cli/commands/user/create.ts index f9322540..c0712bf1 100644 --- a/cli/commands/user/create.ts +++ b/cli/commands/user/create.ts @@ -75,13 +75,13 @@ export default class UserCreate extends BaseCommand { const password1 = await input({ message: "Please enter the user's password:", // Set whatever the user types to stars - transformer: (value) => "*".repeat(value.length), + transformer: (value): string => "*".repeat(value.length), }); const password2 = await input({ message: "Please confirm the user's password:", // Set whatever the user types to stars - transformer: (value) => "*".repeat(value.length), + transformer: (value): string => "*".repeat(value.length), }); if (password1 !== password2) { diff --git a/drizzle/db.ts b/drizzle/db.ts index 5bbd530b..03a51d41 100644 --- a/drizzle/db.ts +++ b/drizzle/db.ts @@ -40,7 +40,7 @@ export const db = ) : drizzle(primaryDb, { schema }); -export const setupDatabase = async (info = true) => { +export const setupDatabase = async (info = true): Promise => { const logger = getLogger("database"); for (const dbPool of [primaryDb, ...replicas]) { diff --git a/middlewares/bait.ts b/middlewares/bait.ts index cef2b55e..e8b0c92f 100644 --- a/middlewares/bait.ts +++ b/middlewares/bait.ts @@ -1,10 +1,10 @@ import { createMiddleware } from "@hono/hono/factory"; import { getLogger } from "@logtape/logtape"; -import type { SocketAddress } from "bun"; +import type { BunFile, SocketAddress } from "bun"; import { matches } from "ip-matching"; import { config } from "~/packages/config-manager"; -const baitFile = async () => { +const baitFile = async (): Promise => { const file = Bun.file(config.http.bait.send_file || "./beemovie.txt"); if (await file.exists()) { diff --git a/packages/config-manager/index.ts b/packages/config-manager/index.ts index 35e47925..b653f697 100644 --- a/packages/config-manager/index.ts +++ b/packages/config-manager/index.ts @@ -8,6 +8,7 @@ import { loadConfig, watchConfig } from "c12"; import { fromZodError } from "zod-validation-error"; import { type Config, configValidator } from "./config.type"; +export type { Config } from "./config.type"; const { config } = await watchConfig({ configFile: "./config/config.toml", @@ -29,4 +30,3 @@ if (!parsed.success) { const exportedConfig = parsed.data; export { exportedConfig as config }; -export type { Config }; diff --git a/packages/plugin-kit/index.ts b/packages/plugin-kit/index.ts index dbc62319..a545aae5 100644 --- a/packages/plugin-kit/index.ts +++ b/packages/plugin-kit/index.ts @@ -1,6 +1,4 @@ -import { Hooks } from "./hooks.ts"; -import { Plugin } from "./plugin.ts"; -import type { Manifest } from "./schema.ts"; - -export type { Manifest }; -export { Plugin, Hooks }; +// biome-ignore lint/performance/noBarrelFile: +export { Hooks } from "./hooks.ts"; +export { Plugin } from "./plugin.ts"; +export type { Manifest } from "./schema.ts"; diff --git a/packages/plugin-kit/plugin.ts b/packages/plugin-kit/plugin.ts index f607e4b9..1f028fb7 100644 --- a/packages/plugin-kit/plugin.ts +++ b/packages/plugin-kit/plugin.ts @@ -1,5 +1,6 @@ import { createMiddleware } from "@hono/hono/factory"; import type { OpenAPIHono } from "@hono/zod-openapi"; +import type { MiddlewareHandler } from "hono"; import type { z } from "zod"; import { type ZodError, fromZodError } from "zod-validation-error"; import type { HonoEnv } from "~/types/api"; @@ -21,7 +22,7 @@ export class Plugin { public constructor(private configSchema: ConfigSchema) {} - public get middleware() { + public get middleware(): MiddlewareHandler { // Middleware that adds the plugin's configuration to the request object return createMiddleware>( async (context, next) => { @@ -34,7 +35,7 @@ export class Plugin { public registerRoute( path: string, fn: (app: OpenAPIHono>) => void, - ) { + ): void { this.routes.push({ path, fn, @@ -54,7 +55,7 @@ export class Plugin { } } - protected _addToApp(app: OpenAPIHono) { + protected _addToApp(app: OpenAPIHono): void { for (const route of this.routes) { app.use(route.path, this.middleware); route.fn( @@ -66,7 +67,7 @@ export class Plugin { public registerHandler( hook: HookName, handler: ServerHooks[HookName], - ) { + ): void { this.handlers[hook] = handler; } @@ -81,7 +82,7 @@ export class Plugin { /** * Returns the internal configuration object. */ - private getConfig() { + private getConfig(): z.infer { if (!this.store) { throw new Error("Configuration has not been loaded yet."); } diff --git a/packages/plugin-kit/schema.ts b/packages/plugin-kit/schema.ts index fba07278..97288af3 100644 --- a/packages/plugin-kit/schema.ts +++ b/packages/plugin-kit/schema.ts @@ -94,6 +94,7 @@ export type Manifest = { }; // This is a type guard to ensure that the schema and the type are in sync +// biome-ignore lint/nursery/useExplicitType: function assert<_T extends never>() { // ... } diff --git a/plugins/openid/routes/authorize.ts b/plugins/openid/routes/authorize.ts index de16591f..ce769169 100644 --- a/plugins/openid/routes/authorize.ts +++ b/plugins/openid/routes/authorize.ts @@ -63,7 +63,7 @@ const schemas = { }), }; -export default (plugin: PluginType) => +export default (plugin: PluginType): void => plugin.registerRoute("/oauth/authorize", (app) => app.openapi( { diff --git a/plugins/openid/routes/jwks.ts b/plugins/openid/routes/jwks.ts index 7f51d619..ccec05a6 100644 --- a/plugins/openid/routes/jwks.ts +++ b/plugins/openid/routes/jwks.ts @@ -3,7 +3,7 @@ import { createRoute, z } from "@hono/zod-openapi"; import { exportJWK } from "jose"; import type { PluginType } from "../index.ts"; -export default (plugin: PluginType) => { +export default (plugin: PluginType): void => { plugin.registerRoute("/.well-known/jwks", (app) => app.openapi( createRoute({ diff --git a/plugins/openid/routes/oauth/callback.ts b/plugins/openid/routes/oauth/callback.ts index 4cac531d..f5892058 100644 --- a/plugins/openid/routes/oauth/callback.ts +++ b/plugins/openid/routes/oauth/callback.ts @@ -2,7 +2,7 @@ import { randomString } from "@/math.ts"; import { setCookie } from "@hono/hono/cookie"; import { createRoute, z } from "@hono/zod-openapi"; import { User, db } from "@versia/kit/db"; -import { and, eq, isNull } from "@versia/kit/drizzle"; +import { type SQL, and, eq, isNull } from "@versia/kit/drizzle"; import { OpenIdAccounts, RolePermissions, @@ -29,7 +29,7 @@ export const schemas = { }), }; -export default (plugin: PluginType) => { +export default (plugin: PluginType): void => { plugin.registerRoute("/oauth/sso/{issuer}/callback", (app) => { app.openapi( createRoute({ @@ -150,7 +150,7 @@ export default (plugin: PluginType) => { // Check if account is already linked const account = await db.query.OpenIdAccounts.findFirst({ - where: (account, { eq, and }) => + where: (account, { eq, and }): SQL | undefined => and( eq(account.serverId, sub), eq(account.issuerId, issuer.id), @@ -188,7 +188,7 @@ export default (plugin: PluginType) => { let userId = ( await db.query.OpenIdAccounts.findFirst({ - where: (account, { eq, and }) => + where: (account, { eq, and }): SQL | undefined => and( eq(account.serverId, sub), eq(account.issuerId, issuer.id), diff --git a/plugins/openid/routes/oauth/revoke.ts b/plugins/openid/routes/oauth/revoke.ts index 8d26cb71..60559312 100644 --- a/plugins/openid/routes/oauth/revoke.ts +++ b/plugins/openid/routes/oauth/revoke.ts @@ -1,7 +1,7 @@ import { jsonOrForm } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { db } from "@versia/kit/db"; -import { eq } from "@versia/kit/drizzle"; +import { type SQL, eq } from "@versia/kit/drizzle"; import { Tokens } from "@versia/kit/tables"; import type { PluginType } from "../../index.ts"; @@ -13,7 +13,7 @@ export const schemas = { }), }; -export default (plugin: PluginType) => { +export default (plugin: PluginType): void => { plugin.registerRoute("/oauth/revoke", (app) => { app.openapi( createRoute({ @@ -63,7 +63,7 @@ export default (plugin: PluginType) => { context.req.valid("json"); const foundToken = await db.query.Tokens.findFirst({ - where: (tokenTable, { eq, and }) => + where: (tokenTable, { eq, and }): SQL | undefined => and( eq(tokenTable.accessToken, token ?? ""), eq(tokenTable.clientId, client_id), diff --git a/plugins/openid/routes/oauth/sso.ts b/plugins/openid/routes/oauth/sso.ts index 0ffb791c..54180e9c 100644 --- a/plugins/openid/routes/oauth/sso.ts +++ b/plugins/openid/routes/oauth/sso.ts @@ -20,7 +20,7 @@ export const schemas = { }), }; -export default (plugin: PluginType) => { +export default (plugin: PluginType): void => { plugin.registerRoute("/oauth/sso", (app) => { app.openapi( createRoute({ diff --git a/plugins/openid/routes/oauth/token.ts b/plugins/openid/routes/oauth/token.ts index e4696e1a..ded39272 100644 --- a/plugins/openid/routes/oauth/token.ts +++ b/plugins/openid/routes/oauth/token.ts @@ -1,7 +1,7 @@ import { jsonOrForm } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Application, db } from "@versia/kit/db"; -import { eq } from "@versia/kit/drizzle"; +import { type SQL, eq } from "@versia/kit/drizzle"; import { Tokens } from "@versia/kit/tables"; import type { PluginType } from "../../index.ts"; @@ -38,7 +38,7 @@ export const schemas = { }), }; -export default (plugin: PluginType) => { +export default (plugin: PluginType): void => { plugin.registerRoute("/oauth/token", (app) => { app.openapi( createRoute({ @@ -155,7 +155,7 @@ export default (plugin: PluginType) => { } const token = await db.query.Tokens.findFirst({ - where: (token, { eq, and }) => + where: (token, { eq, and }): SQL | undefined => and( eq(token.code, code), eq( diff --git a/plugins/openid/routes/sso/:id/index.ts b/plugins/openid/routes/sso/:id/index.ts index 92f1aa9b..03d71cff 100644 --- a/plugins/openid/routes/sso/:id/index.ts +++ b/plugins/openid/routes/sso/:id/index.ts @@ -2,12 +2,12 @@ import { auth } from "@/api"; import { proxyUrl } from "@/response"; import { createRoute, z } from "@hono/zod-openapi"; import { db } from "@versia/kit/db"; -import { eq } from "@versia/kit/drizzle"; +import { type SQL, eq } from "@versia/kit/drizzle"; import { OpenIdAccounts, RolePermissions } from "@versia/kit/tables"; import type { PluginType } from "~/plugins/openid"; import { ErrorSchema } from "~/types/api"; -export default (plugin: PluginType) => { +export default (plugin: PluginType): void => { plugin.registerRoute("/api/v1/sso", (app) => { app.openapi( createRoute({ @@ -88,7 +88,7 @@ export default (plugin: PluginType) => { } const account = await db.query.OpenIdAccounts.findFirst({ - where: (account, { eq, and }) => + where: (account, { eq, and }): SQL | undefined => and( eq(account.userId, user.id), eq(account.issuerId, issuerId), @@ -181,7 +181,7 @@ export default (plugin: PluginType) => { } const account = await db.query.OpenIdAccounts.findFirst({ - where: (account, { eq, and }) => + where: (account, { eq, and }): SQL | undefined => and( eq(account.userId, user.id), eq(account.issuerId, issuerId), diff --git a/plugins/openid/routes/sso/index.ts b/plugins/openid/routes/sso/index.ts index a6cc8ded..42165e24 100644 --- a/plugins/openid/routes/sso/index.ts +++ b/plugins/openid/routes/sso/index.ts @@ -10,7 +10,7 @@ import { ErrorSchema } from "~/types/api"; import type { PluginType } from "../../index.ts"; import { oauthDiscoveryRequest, oauthRedirectUri } from "../../utils.ts"; -export default (plugin: PluginType) => { +export default (plugin: PluginType): void => { plugin.registerRoute("/api/v1/sso", (app) => { app.openapi( { diff --git a/plugins/openid/utils.ts b/plugins/openid/utils.ts index eb3c6941..068c3525 100644 --- a/plugins/openid/utils.ts +++ b/plugins/openid/utils.ts @@ -1,5 +1,5 @@ import { db } from "@versia/kit/db"; -import type { InferSelectModel } from "@versia/kit/drizzle"; +import type { InferSelectModel, SQL } from "@versia/kit/drizzle"; import type { Applications, OpenIdLoginFlows } from "@versia/kit/tables"; import { type AuthorizationResponseError, @@ -7,6 +7,7 @@ import { ClientSecretPost, type ResponseBodyError, type TokenEndpointResponse, + type UserInfoResponse, authorizationCodeGrantRequest, discoveryRequest, expectNoState, @@ -17,6 +18,7 @@ import { userInfoRequest, validateAuthResponse, } from "oauth4webapi"; +import type { ApplicationType } from "~/classes/database/application"; export const oauthDiscoveryRequest = ( issuerUrl: string | URL, @@ -28,12 +30,19 @@ export const oauthDiscoveryRequest = ( }).then((res) => processDiscoveryResponse(issuerUrlurl, res)); }; -export const oauthRedirectUri = (baseUrl: string, issuer: string) => +export const oauthRedirectUri = (baseUrl: string, issuer: string): string => new URL(`/oauth/sso/${issuer}/callback`, baseUrl).toString(); -const getFlow = (flowId: string) => { +const getFlow = ( + flowId: string, +): Promise< + | (InferSelectModel & { + application?: ApplicationType | null; + }) + | undefined +> => { return db.query.OpenIdLoginFlows.findFirst({ - where: (flow, { eq }) => eq(flow.id, flowId), + where: (flow, { eq }): SQL | undefined => eq(flow.id, flowId), with: { application: true, }, @@ -100,7 +109,7 @@ const getUserInfo = ( clientId: string, accessToken: string, sub: string, -) => { +): Promise => { return userInfoRequest( authServer, { @@ -138,7 +147,16 @@ export const automaticOidcFlow = async ( }) | null, ) => Response, -) => { +): Promise< + | Response + | { + userInfo: UserInfoResponse; + flow: InferSelectModel & { + application?: ApplicationType | null; + }; + claims: Record; + } +> => { const flow = await getFlow(flowId); if (!flow) { diff --git a/tests/api/accounts.test.ts b/tests/api/accounts.test.ts index f17db3c4..401073d5 100644 --- a/tests/api/accounts.test.ts +++ b/tests/api/accounts.test.ts @@ -18,7 +18,9 @@ afterAll(async () => { await deleteUsers(); }); -const getFormData = (object: Record) => +const getFormData = ( + object: Record, +): FormData => Object.keys(object).reduce((formData, key) => { formData.append(key, String(object[key])); return formData; diff --git a/tests/utils.ts b/tests/utils.ts index 4ede9efa..48ee79c4 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -6,6 +6,7 @@ import { solveChallenge } from "altcha-lib"; import { asc, inArray, like } from "drizzle-orm"; import { appFactory } from "~/app"; import type { Status } from "~/classes/functions/status"; +import type { Token } from "~/classes/functions/token"; import { searchManager } from "~/classes/search/search-manager"; import { setupDatabase } from "~/drizzle/db"; import { config } from "~/packages/config-manager"; @@ -17,18 +18,28 @@ if (config.sonic.enabled) { const app = await appFactory(); -export function fakeRequest(url: URL | string, init?: RequestInit) { +export function fakeRequest( + url: URL | string, + init?: RequestInit, +): Promise { return Promise.resolve( app.fetch(new Request(new URL(url, config.http.base_url), init)), ); } -export const deleteOldTestUsers = async () => { +export const deleteOldTestUsers = async (): Promise => { // Deletes all users that match the test username (test-<32 random characters>) await db.delete(Users).where(like(Users.username, "test-%")); }; -export const getTestUsers = async (count: number) => { +export const getTestUsers = async ( + count: number, +): Promise<{ + users: User[]; + tokens: Token[]; + passwords: string[]; + deleteUsers: () => Promise; +}> => { const users: User[] = []; const passwords: string[] = []; @@ -67,7 +78,7 @@ export const getTestUsers = async (count: number) => { users, tokens, passwords, - deleteUsers: async () => { + deleteUsers: async (): Promise => { await db.delete(Users).where( inArray( Users.id, @@ -82,7 +93,7 @@ export const getTestStatuses = async ( count: number, user: User, partial?: Partial, -) => { +): Promise => { const statuses: Note[] = []; for (let i = 0; i < count; i++) { @@ -123,7 +134,7 @@ export const getTestStatuses = async ( * Only to be used in tests * @returns Base64 encoded payload */ -export const getSolvedChallenge = async () => { +export const getSolvedChallenge = async (): Promise => { const { challenge } = await generateChallenge(100); const solution = await solveChallenge( diff --git a/utils/api.ts b/utils/api.ts index f3d21cba..10a8ea60 100644 --- a/utils/api.ts +++ b/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) => void) => fn; +export const apiRoute = (fn: (app: OpenAPIHono) => 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 => createMiddleware(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; +}> { const formData = await context.req.formData(); const urlparams = new URLSearchParams(); const files = new Map(); @@ -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 { 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 => + Promise.resolve(context.req.bodyCache.json); + // biome-ignore lint/suspicious/noExplicitAny: + context.req.json = (): Promise => + 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 => { 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 => { const body = await res.clone().text(); const logger = getLogger("server"); diff --git a/utils/challenges.ts b/utils/challenges.ts index 1809f038..77836596 100644 --- a/utils/challenges.ts +++ b/utils/challenges.ts @@ -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, ); diff --git a/utils/constants.ts b/utils/constants.ts index 3e93f767..1a4698da 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -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(); diff --git a/utils/content_types.ts b/utils/content_types.ts index 540820a0..86c31fc2 100644 --- a/utils/content_types.ts +++ b/utils/content_types.ts @@ -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" }; } diff --git a/utils/init.ts b/utils/init.ts index 64d8291c..ff7ffacb 100644 --- a/utils/init.ts +++ b/utils/init.ts @@ -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 => { 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 => { 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 => { 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 => { const logger = getLogger("server"); if (!(config.instance.keys.public && config.instance.keys.private)) { diff --git a/utils/loggers.ts b/utils/loggers.ts index ff581449..b2d33b0e 100644 --- a/utils/loggers.ts +++ b/utils/loggers.ts @@ -1,4 +1,5 @@ import { + type Stats, appendFileSync, closeSync, existsSync, @@ -63,7 +64,7 @@ export function getBaseRotatingFileSink( 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 = { - 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 = { renameSync, }; -export const configureLoggers = (silent = false) => +export const configureLoggers = (silent = false): Promise => configure({ reset: true, sinks: { diff --git a/utils/markdown.ts b/utils/markdown.ts index b1c7cee0..85ce5ddf 100644 --- a/utils/markdown.ts +++ b/utils/markdown.ts @@ -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); diff --git a/utils/math.ts b/utils/math.ts index 3d7ba601..3ea0978b 100644 --- a/utils/math.ts +++ b/utils/math.ts @@ -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, ); diff --git a/utils/oauth.ts b/utils/oauth.ts index d98a03e8..b8148dc4 100644 --- a/utils/oauth.ts +++ b/utils/oauth.ts @@ -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; } diff --git a/utils/response.ts b/utils/response.ts index 73b3d4a3..a1d95577 100644 --- a/utils/response.ts +++ b/utils/response.ts @@ -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( diff --git a/utils/sanitization.ts b/utils/sanitization.ts index 35fb4f93..a283ede6 100644 --- a/utils/sanitization.ts +++ b/utils/sanitization.ts @@ -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 => { return sanitizeHtml(html, { whiteList: {}, }); @@ -10,7 +10,7 @@ export const sanitizedHtmlStrip = (html: string) => { export const sanitizeHtmlInline = ( html: string, extraConfig?: IFilterXSSOptions, -) => { +): Promise => { 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 => { 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) { diff --git a/utils/server.ts b/utils/server.ts index d9c9952f..5e5128cd 100644 --- a/utils/server.ts +++ b/utils/server.ts @@ -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) => +export const createServer = ( + config: Config, + app: OpenAPIHono, +): Server => Bun.serve({ port: config.http.bind_port, reusePort: true, @@ -18,7 +22,7 @@ export const createServer = (config: Config, app: OpenAPIHono) => } : undefined, hostname: config.http.bind || "0.0.0.0", // defaults to "0.0.0.0" - async fetch(req, server) { + async fetch(req, server): Promise { const output = await app.fetch(req, { ip: server.requestIP(req) }); if (config.logging.log_responses) { diff --git a/utils/timelines.ts b/utils/timelines.ts index b867bce3..06787e21 100644 --- a/utils/timelines.ts +++ b/utils/timelines.ts @@ -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( | Parameters[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( 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( 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, });