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

@ -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(); const searchParams = new URLSearchParams();
// Add all data that is not undefined except email and password // Add all data that is not undefined except email and password

View file

@ -49,7 +49,7 @@ export default apiRoute((app) =>
app.openapi(route, async (context) => { app.openapi(route, async (context) => {
const { redirect_uri, client_id, code } = context.req.valid("query"); const { redirect_uri, client_id, code } = context.req.valid("query");
const redirectToLogin = (error: string) => const redirectToLogin = (error: string): Response =>
context.redirect( context.redirect(
`${config.frontend.routes.login}?${new URLSearchParams({ `${config.frontend.routes.login}?${new URLSearchParams({
...context.req.query, ...context.req.query,

View file

@ -54,7 +54,7 @@ const returnError = (
token: string, token: string,
error: string, error: string,
description: string, description: string,
) => { ): Response => {
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
searchParams.append("error", error); searchParams.append("error", error);

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig, auth, qsQuery } from "@/api";
import { createRoute } from "@hono/zod-openapi"; import { createRoute } from "@hono/zod-openapi";
import { User, db } from "@versia/kit/db"; import { User, db } from "@versia/kit/db";
import { RolePermissions, Users } from "@versia/kit/tables"; import { RolePermissions, Users } from "@versia/kit/tables";
import { inArray } from "drizzle-orm"; import { type SQL, inArray } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { ErrorSchema } from "~/types/api"; import { ErrorSchema } from "~/types/api";
@ -70,7 +70,7 @@ export default apiRoute((app) =>
columns: { columns: {
ownerId: true, ownerId: true,
}, },
where: (relationship, { inArray, and, eq }) => where: (relationship, { inArray, and, eq }): SQL | undefined =>
and( and(
inArray( inArray(
relationship.subjectId, relationship.subjectId,
@ -89,7 +89,7 @@ export default apiRoute((app) =>
columns: { columns: {
subjectId: true, subjectId: true,
}, },
where: (relationship, { inArray, and, eq }) => where: (relationship, { inArray, and, eq }): SQL | undefined =>
and( and(
eq(relationship.ownerId, self.id), eq(relationship.ownerId, self.id),
inArray( inArray(

View file

@ -3,7 +3,7 @@ import { createRoute } from "@hono/zod-openapi";
import type { Marker as ApiMarker } from "@versia/client/types"; import type { Marker as ApiMarker } from "@versia/client/types";
import { db } from "@versia/kit/db"; import { db } from "@versia/kit/db";
import { Markers, RolePermissions } from "@versia/kit/tables"; 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 { z } from "zod";
import { ErrorSchema } from "~/types/api"; import { ErrorSchema } from "~/types/api";
@ -133,7 +133,7 @@ export default apiRoute((app) => {
if (timeline.includes("home")) { if (timeline.includes("home")) {
const found = await db.query.Markers.findFirst({ const found = await db.query.Markers.findFirst({
where: (marker, { and, eq }) => where: (marker, { and, eq }): SQL | undefined =>
and( and(
eq(marker.userId, user.id), eq(marker.userId, user.id),
eq(marker.timeline, "home"), eq(marker.timeline, "home"),
@ -156,7 +156,7 @@ export default apiRoute((app) => {
if (timeline.includes("notifications")) { if (timeline.includes("notifications")) {
const found = await db.query.Markers.findFirst({ const found = await db.query.Markers.findFirst({
where: (marker, { and, eq }) => where: (marker, { and, eq }): SQL | undefined =>
and( and(
eq(marker.userId, user.id), eq(marker.userId, user.id),
eq(marker.timeline, "notifications"), eq(marker.timeline, "notifications"),

View file

@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth } from "@/api";
import { createRoute } from "@hono/zod-openapi"; import { createRoute } from "@hono/zod-openapi";
import { Note, User } from "@versia/kit/db"; import { Note, User } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables"; import { RolePermissions } from "@versia/kit/tables";
import type { SQL } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { import {
findManyNotifications, findManyNotifications,
@ -103,7 +104,8 @@ export default apiRoute((app) =>
const notification = ( const notification = (
await findManyNotifications( await findManyNotifications(
{ {
where: (notification, { eq }) => eq(notification.id, id), where: (notification, { eq }): SQL | undefined =>
eq(notification.id, id),
limit: 1, limit: 1,
}, },
user.id, user.id,

View file

@ -3,7 +3,9 @@ import type { Notification as ApiNotification } from "@versia/client/types";
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils"; import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
import { meta } from "./index.ts"; import { meta } from "./index.ts";
const getFormData = (object: Record<string, string | number | boolean>) => const getFormData = (
object: Record<string, string | number | boolean>,
): FormData =>
Object.keys(object).reduce((formData, key) => { Object.keys(object).reduce((formData, key) => {
formData.append(key, String(object[key])); formData.append(key, String(object[key]));
return formData; return formData;

View file

@ -3,7 +3,7 @@ import { fetchTimeline } from "@/timelines";
import { createRoute } from "@hono/zod-openapi"; import { createRoute } from "@hono/zod-openapi";
import { Note, User } from "@versia/kit/db"; import { Note, User } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables"; import { RolePermissions } from "@versia/kit/tables";
import { sql } from "drizzle-orm"; import { type SQL, sql } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { import {
findManyNotifications, findManyNotifications,
@ -159,7 +159,7 @@ export default apiRoute((app) =>
notification, notification,
// @ts-expect-error Yes I KNOW the types are wrong // @ts-expect-error Yes I KNOW the types are wrong
{ lt, gte, gt, and, eq, not, inArray }, { lt, gte, gt, and, eq, not, inArray },
) => ): SQL | undefined =>
and( and(
max_id ? lt(notification.id, max_id) : undefined, max_id ? lt(notification.id, max_id) : undefined,
since_id since_id
@ -200,7 +200,8 @@ export default apiRoute((app) =>
), ),
limit, limit,
// @ts-expect-error Yes I KNOW the types are wrong // @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, context.req.raw,
user.id, user.id,

View file

@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth, idValidator } from "@/api";
import { createRoute } from "@hono/zod-openapi"; import { createRoute } from "@hono/zod-openapi";
import { Note, db } from "@versia/kit/db"; import { Note, db } from "@versia/kit/db";
import { RolePermissions } from "@versia/kit/tables"; import { RolePermissions } from "@versia/kit/tables";
import type { SQL } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { ErrorSchema } from "~/types/api"; import { ErrorSchema } from "~/types/api";
@ -90,7 +91,7 @@ export default apiRoute((app) =>
if ( if (
await db.query.UserToPinnedNotes.findFirst({ await db.query.UserToPinnedNotes.findFirst({
where: (userPinnedNote, { and, eq }) => where: (userPinnedNote, { and, eq }): SQL | undefined =>
and( and(
eq(userPinnedNote.noteId, foundStatus.data.id), eq(userPinnedNote.noteId, foundStatus.data.id),
eq(userPinnedNote.userId, user.id), eq(userPinnedNote.userId, user.id),

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api";
import { createRoute } from "@hono/zod-openapi"; import { createRoute } from "@hono/zod-openapi";
import { db } from "@versia/kit/db"; import { db } from "@versia/kit/db";
import { FilterKeywords, Filters, RolePermissions } from "@versia/kit/tables"; 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 { z } from "zod";
import { ErrorSchema } from "~/types/api"; import { ErrorSchema } from "~/types/api";
@ -204,7 +204,7 @@ export default apiRoute((app) => {
} }
const userFilter = await db.query.Filters.findFirst({ 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)), and(eq(filter.userId, user.id), eq(filter.id, id)),
with: { with: {
keywords: true, keywords: true,
@ -300,7 +300,7 @@ export default apiRoute((app) => {
} }
const updatedFilter = await db.query.Filters.findFirst({ 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)), and(eq(filter.userId, user.id), eq(filter.id, id)),
with: { with: {
keywords: true, keywords: true,

View file

@ -2,6 +2,7 @@ import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api";
import { createRoute } from "@hono/zod-openapi"; import { createRoute } from "@hono/zod-openapi";
import { db } from "@versia/kit/db"; import { db } from "@versia/kit/db";
import { FilterKeywords, Filters, RolePermissions } from "@versia/kit/tables"; import { FilterKeywords, Filters, RolePermissions } from "@versia/kit/tables";
import type { SQL } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { ErrorSchema } from "~/types/api"; import { ErrorSchema } from "~/types/api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -139,7 +140,8 @@ export default apiRoute((app) => {
} }
const userFilters = await db.query.Filters.findMany({ 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: { with: {
keywords: true, keywords: true,
}, },

2
app.ts
View file

@ -23,7 +23,7 @@ import { logger } from "./middlewares/logger.ts";
import { routes } from "./routes.ts"; import { routes } from "./routes.ts";
import type { ApiRouteExports, HonoEnv } from "./types/api.ts"; import type { ApiRouteExports, HonoEnv } from "./types/api.ts";
export const appFactory = async () => { export const appFactory = async (): Promise<OpenAPIHono<HonoEnv>> => {
await configureLoggers(); await configureLoggers();
const serverLogger = getLogger("server"); const serverLogger = getLogger("server");

View file

@ -66,7 +66,17 @@
"options": { "options": {
"accessibility": "explicit" "accessibility": "explicit"
} }
} },
"noCommonJs": "warn",
"noDynamicNamespaceImportAccess": "warn",
"noExportedImports": "warn",
"noIrregularWhitespace": "warn",
"noSubstr": "warn",
"noTemplateCurlyInString": "warn",
"noUselessEscapeInRegex": "warn",
"noUselessStringRaw": "warn",
"useAdjacentOverloadSignatures": "warn",
"useExplicitType": "warn"
} }
} }
}, },

View file

@ -82,7 +82,8 @@ export class Application extends BaseInterface<typeof Applications> {
token: string, token: string,
): Promise<Application | null> { ): Promise<Application | null> {
const result = await db.query.Tokens.findFirst({ const result = await db.query.Tokens.findFirst({
where: (tokens, { eq }) => eq(tokens.accessToken, token), where: (tokens, { eq }): SQL | undefined =>
eq(tokens.accessToken, token),
with: { with: {
application: true, application: true,
}, },
@ -141,7 +142,7 @@ export class Application extends BaseInterface<typeof Applications> {
return application; return application;
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }

View file

@ -148,11 +148,11 @@ export class Attachment extends BaseInterface<typeof Attachments> {
return attachment; return attachment;
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }
public static getUrl(name: string) { public static getUrl(name: string): string {
if (config.media.backend === MediaBackendType.Local) { if (config.media.backend === MediaBackendType.Local) {
return new URL(`/media/${name}`, config.http.base_url).toString(); return new URL(`/media/${name}`, config.http.base_url).toString();
} }

View file

@ -160,7 +160,7 @@ export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
return await Emoji.fromVersia(emojiToFetch, foundInstance?.id ?? null); return await Emoji.fromVersia(emojiToFetch, foundInstance?.id ?? null);
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }

View file

@ -132,7 +132,7 @@ export class Instance extends BaseInterface<typeof Instances> {
return instance; return instance;
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }

View file

@ -59,7 +59,7 @@ export class Like extends BaseInterface<typeof Likes, LikeType> {
public static async fromSql( public static async fromSql(
sql: SQL<unknown> | undefined, sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Likes.id), orderBy: SQL<unknown> | undefined = desc(Likes.id),
) { ): Promise<Like | null> {
const found = await db.query.Likes.findFirst({ const found = await db.query.Likes.findFirst({
where: sql, where: sql,
orderBy, orderBy,
@ -81,7 +81,7 @@ export class Like extends BaseInterface<typeof Likes, LikeType> {
limit?: number, limit?: number,
offset?: number, offset?: number,
extra?: Parameters<typeof db.query.Likes.findMany>[0], extra?: Parameters<typeof db.query.Likes.findMany>[0],
) { ): Promise<Like[]> {
const found = await db.query.Likes.findMany({ const found = await db.query.Likes.findMany({
where: sql, where: sql,
orderBy, orderBy,
@ -135,7 +135,7 @@ export class Like extends BaseInterface<typeof Likes, LikeType> {
return role; return role;
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }

View file

@ -276,7 +276,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
return found.map((s) => new Note(s)); return found.map((s) => new Note(s));
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }
@ -319,7 +319,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
{ {
with: { with: {
relationships: { relationships: {
where: (relationship, { eq, and }) => where: (relationship, { eq, and }): SQL | undefined =>
and( and(
eq(relationship.subjectId, this.data.authorId), eq(relationship.subjectId, this.data.authorId),
eq(relationship.following, true), eq(relationship.following, true),
@ -339,7 +339,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
return deduplicatedUsersById; return deduplicatedUsersById;
} }
public get author() { public get author(): User {
return new User(this.data.author); return new User(this.data.author);
} }
@ -369,7 +369,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
); );
} }
public isRemote() { public isRemote(): boolean {
return this.author.isRemote(); return this.author.isRemote();
} }
@ -846,7 +846,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
if (this.data.visibility === "private") { if (this.data.visibility === "private") {
return user return user
? !!(await db.query.Relationships.findFirst({ ? !!(await db.query.Relationships.findFirst({
where: (relationship, { and, eq }) => where: (relationship, { and, eq }): SQL | undefined =>
and( and(
eq(relationship.ownerId, user?.id), eq(relationship.ownerId, user?.id),
eq(relationship.subjectId, Notes.authorId), eq(relationship.subjectId, Notes.authorId),
@ -877,7 +877,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
// Rewrite all src tags to go through proxy // Rewrite all src tags to go through proxy
let replacedContent = new HTMLRewriter() let replacedContent = new HTMLRewriter()
.on("[src]", { .on("[src]", {
element(element) { element(element): void {
element.setAttribute( element.setAttribute(
"src", "src",
proxyUrl(element.getAttribute("src") ?? "") ?? "", proxyUrl(element.getAttribute("src") ?? "") ?? "",

View file

@ -198,7 +198,7 @@ export class Relationship extends BaseInterface<
ownerId: string; ownerId: string;
}): Promise<RelationshipType> { }): Promise<RelationshipType> {
let output = await db.query.Relationships.findFirst({ let output = await db.query.Relationships.findFirst({
where: (rel, { and, eq }) => where: (rel, { and, eq }): SQL | undefined =>
and( and(
eq(rel.ownerId, oppositeTo.subjectId), eq(rel.ownerId, oppositeTo.subjectId),
eq(rel.subjectId, oppositeTo.ownerId), eq(rel.subjectId, oppositeTo.ownerId),
@ -286,7 +286,7 @@ export class Relationship extends BaseInterface<
return relationship; return relationship;
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }

View file

@ -1,5 +1,8 @@
import { proxyUrl } from "@/response"; 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 { db } from "@versia/kit/db";
import { RoleToUsers, Roles } from "@versia/kit/tables"; import { RoleToUsers, Roles } from "@versia/kit/tables";
import { import {
@ -53,7 +56,7 @@ export class Role extends BaseInterface<typeof Roles> {
public static async fromSql( public static async fromSql(
sql: SQL<unknown> | undefined, sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Roles.id), orderBy: SQL<unknown> | undefined = desc(Roles.id),
) { ): Promise<Role | null> {
const found = await db.query.Roles.findFirst({ const found = await db.query.Roles.findFirst({
where: sql, where: sql,
orderBy, orderBy,
@ -65,10 +68,14 @@ export class Role extends BaseInterface<typeof Roles> {
return new Role(found); return new Role(found);
} }
public static async getUserRoles(userId: string, isAdmin: boolean) { public static async getUserRoles(
userId: string,
isAdmin: boolean,
): Promise<Role[]> {
return ( return (
await db.query.RoleToUsers.findMany({ await db.query.RoleToUsers.findMany({
where: (role, { eq }) => eq(role.userId, userId), where: (role, { eq }): SQL | undefined =>
eq(role.userId, userId),
with: { with: {
role: true, role: true,
user: { user: {
@ -115,7 +122,7 @@ export class Role extends BaseInterface<typeof Roles> {
limit?: number, limit?: number,
offset?: number, offset?: number,
extra?: Parameters<typeof db.query.Roles.findMany>[0], extra?: Parameters<typeof db.query.Roles.findMany>[0],
) { ): Promise<Role[]> {
const found = await db.query.Roles.findMany({ const found = await db.query.Roles.findMany({
where: sql, where: sql,
orderBy, orderBy,
@ -165,14 +172,14 @@ export class Role extends BaseInterface<typeof Roles> {
return role; return role;
} }
public async linkUser(userId: string) { public async linkUser(userId: string): Promise<void> {
await db.insert(RoleToUsers).values({ await db.insert(RoleToUsers).values({
userId, userId,
roleId: this.id, roleId: this.id,
}); });
} }
public async unlinkUser(userId: string) { public async unlinkUser(userId: string): Promise<void> {
await db await db
.delete(RoleToUsers) .delete(RoleToUsers)
.where( .where(
@ -183,11 +190,11 @@ export class Role extends BaseInterface<typeof Roles> {
); );
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }
public toApi() { public toApi(): APIRole {
return { return {
id: this.id, id: this.id,
name: this.data.name, name: this.data.name,

View file

@ -17,7 +17,7 @@ export class Timeline<Type extends Note | User> {
limit: number, limit: number,
url: string, url: string,
userId?: string, userId?: string,
) { ): Promise<{ link: string; objects: Note[] }> {
return new Timeline<Note>(TimelineType.Note).fetchTimeline( return new Timeline<Note>(TimelineType.Note).fetchTimeline(
sql, sql,
limit, limit,
@ -30,7 +30,7 @@ export class Timeline<Type extends Note | User> {
sql: SQL<unknown> | undefined, sql: SQL<unknown> | undefined,
limit: number, limit: number,
url: string, url: string,
) { ): Promise<{ link: string; objects: User[] }> {
return new Timeline<User>(TimelineType.User).fetchTimeline( return new Timeline<User>(TimelineType.User).fetchTimeline(
sql, sql,
limit, limit,

View file

@ -150,7 +150,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
public static async fromSql( public static async fromSql(
sql: SQL<unknown> | undefined, sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Users.id), orderBy: SQL<unknown> | undefined = desc(Users.id),
) { ): Promise<User | null> {
const found = await findManyUsers({ const found = await findManyUsers({
where: sql, where: sql,
orderBy, orderBy,
@ -168,7 +168,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
limit?: number, limit?: number,
offset?: number, offset?: number,
extra?: Parameters<typeof db.query.Users.findMany>[0], extra?: Parameters<typeof db.query.Users.findMany>[0],
) { ): Promise<User[]> {
const found = await findManyUsers({ const found = await findManyUsers({
where: sql, where: sql,
orderBy, orderBy,
@ -180,34 +180,38 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return found.map((s) => new User(s)); return found.map((s) => new User(s));
} }
public get id() { public get id(): string {
return this.data.id; return this.data.id;
} }
public isLocal() { public isLocal(): boolean {
return this.data.instanceId === null; return this.data.instanceId === null;
} }
public isRemote() { public isRemote(): boolean {
return !this.isLocal(); return !this.isLocal();
} }
public getUri() { public getUri(): string {
return ( return (
this.data.uri || this.data.uri ||
new URL(`/users/${this.data.id}`, config.http.base_url).toString() 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(); return uri || new URL(`/users/${id}`, baseUrl).toString();
} }
public hasPermission(permission: RolePermissions) { public hasPermission(permission: RolePermissions): boolean {
return this.getAllPermissions().includes(permission); return this.getAllPermissions().includes(permission);
} }
public getAllPermissions() { public getAllPermissions(): RolePermissions[] {
return ( return (
this.data.roles this.data.roles
.flatMap((role) => role.permissions) .flatMap((role) => role.permissions)
@ -270,7 +274,10 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return foundRelationship; return foundRelationship;
} }
public async unfollow(followee: User, relationship: Relationship) { public async unfollow(
followee: User,
relationship: Relationship,
): Promise<boolean> {
if (followee.isRemote()) { if (followee.isRemote()) {
// TODO: This should reschedule for a later time and maybe notify the server admin if it fails too often // TODO: This should reschedule for a later time and maybe notify the server admin if it fails too often
const { ok } = await this.federateToUser( const { ok } = await this.federateToUser(
@ -348,7 +355,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return db.$count(Users, isNull(Users.instanceId)); return db.$count(Users, isNull(Users.instanceId));
} }
public static async getActiveInPeriod(milliseconds: number) { public static async getActiveInPeriod(
milliseconds: number,
): Promise<number> {
return ( return (
await db await db
.select({ .select({
@ -376,7 +385,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
} }
} }
public async resetPassword() { public async resetPassword(): Promise<string> {
const resetToken = randomString(32, "hex"); const resetToken = randomString(32, "hex");
await this.update({ await this.update({
@ -386,30 +395,22 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return resetToken; return resetToken;
} }
public async pin(note: Note) { public async pin(note: Note): Promise<void> {
return ( await db.insert(UserToPinnedNotes).values({
await db noteId: note.id,
.insert(UserToPinnedNotes) userId: this.id,
.values({ });
noteId: note.id,
userId: this.id,
})
.returning()
)[0];
} }
public async unpin(note: Note) { public async unpin(note: Note): Promise<void> {
return ( await db
await db .delete(UserToPinnedNotes)
.delete(UserToPinnedNotes) .where(
.where( and(
and( eq(NoteToMentions.noteId, note.id),
eq(NoteToMentions.noteId, note.id), eq(NoteToMentions.userId, this.id),
eq(NoteToMentions.userId, this.id), ),
), );
)
.returning()
)[0];
} }
public save(): Promise<UserWithRelations> { public save(): Promise<UserWithRelations> {
@ -434,7 +435,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
> { > {
// Get all linked accounts // Get all linked accounts
const accounts = await db.query.OpenIdAccounts.findMany({ 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 return accounts
@ -715,7 +716,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
* @param config The config to use * @param config The config to use
* @returns The raw URL for the user's avatar * @returns The raw URL for the user's avatar
*/ */
public getAvatarUrl(config: Config) { public getAvatarUrl(config: Config): string {
if (!this.data.avatar) { if (!this.data.avatar) {
return ( return (
config.defaults.avatar || config.defaults.avatar ||
@ -725,7 +726,10 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return this.data.avatar; 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, [ const keys = await crypto.subtle.generateKey("Ed25519", true, [
"sign", "sign",
"verify", "verify",
@ -807,14 +811,14 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
* @param config The config to use * @param config The config to use
* @returns The raw URL for the user's header * @returns The raw URL for the user's header
*/ */
public getHeaderUrl(config: Config) { public getHeaderUrl(config: Config): string {
if (!this.data.header) { if (!this.data.header) {
return config.defaults.header || ""; return config.defaults.header || "";
} }
return this.data.header; return this.data.header;
} }
public getAcct() { public getAcct(): string {
return this.isLocal() return this.isLocal()
? this.data.username ? this.data.username
: `${this.data.username}@${this.data.instance?.baseUrl}`; : `${this.data.username}@${this.data.instance?.baseUrl}`;
@ -824,7 +828,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
isLocal: boolean, isLocal: boolean,
username: string, username: string,
baseUrl?: string, baseUrl?: string,
) { ): string {
return isLocal ? username : `${username}@${baseUrl}`; return isLocal ? username : `${username}@${baseUrl}`;
} }

View file

@ -261,7 +261,7 @@ export const parseTextMentions = async (
const baseUrlHost = new URL(config.http.base_url).host; 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 const foundUsers = await db
.select({ .select({
@ -326,7 +326,7 @@ export const parseTextMentions = async (
return finalList; return finalList;
}; };
export const replaceTextMentions = (text: string, mentions: User[]) => { export const replaceTextMentions = (text: string, mentions: User[]): string => {
let finalText = text; let finalText = text;
for (const mention of mentions) { for (const mention of mentions) {
const user = mention.data; const user = mention.data;
@ -405,7 +405,7 @@ export const contentToHtml = async (
htmlContent = linkifyHtml(htmlContent, { htmlContent = linkifyHtml(htmlContent, {
defaultProtocol: "https", defaultProtocol: "https",
validate: { validate: {
email: () => false, email: (): false => false,
}, },
target: "_blank", target: "_blank",
rel: "nofollow noopener noreferrer", rel: "nofollow noopener noreferrer",
@ -414,11 +414,11 @@ export const contentToHtml = async (
return htmlContent; return htmlContent;
}; };
export const markdownParse = async (content: string) => { export const markdownParse = async (content: string): Promise<string> => {
return (await getMarkdownRenderer()).render(content); return (await getMarkdownRenderer()).render(content);
}; };
export const getMarkdownRenderer = () => { export const getMarkdownRenderer = (): MarkdownIt => {
const renderer = MarkdownIt({ const renderer = MarkdownIt({
html: true, html: true,
linkify: true, linkify: true,

View file

@ -11,7 +11,7 @@ import {
Tokens, Tokens,
type Users, type Users,
} from "@versia/kit/tables"; } 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 { ApplicationType } from "~/classes/database/application.ts";
import type { EmojiWithInstance } from "~/classes/database/emoji.ts"; import type { EmojiWithInstance } from "~/classes/database/emoji.ts";
import type { Token } from "./token.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<unknown>;
followingCount: SQL.Aliased<unknown>;
statusCount: SQL.Aliased<unknown>;
} => ({
// @ts-expect-error sql is a template tag, so it gets confused when we use it as a function // @ts-expect-error sql is a template tag, so it gets confused when we use it as a function
followerCount: sql([ followerCount: sql([
`(SELECT COUNT(*) FROM "Relationships" "relationships" WHERE ("relationships"."ownerId" = "${name}".id AND "relationships"."following" = true))`, `(SELECT COUNT(*) FROM "Relationships" "relationships" WHERE ("relationships"."ownerId" = "${name}".id AND "relationships"."following" = true))`,
@ -214,7 +220,8 @@ export const retrieveToken = async (
return ( return (
(await db.query.Tokens.findFirst({ (await db.query.Tokens.findFirst({
where: (tokens, { eq }) => eq(tokens.accessToken, accessToken), where: (tokens, { eq }): SQL | undefined =>
eq(tokens.accessToken, accessToken),
})) ?? null })) ?? null
); );
}; };

View file

@ -219,14 +219,18 @@ export class InboxProcessor {
try { try {
return await handler.parseBody<Response>({ return await handler.parseBody<Response>({
note: () => this.processNote(), note: (): Promise<Response> => this.processNote(),
follow: () => this.processFollowRequest(), follow: (): Promise<Response> => this.processFollowRequest(),
followAccept: () => this.processFollowAccept(), followAccept: (): Promise<Response> =>
followReject: () => this.processFollowReject(), this.processFollowAccept(),
"pub.versia:likes/Like": () => this.processLikeRequest(), followReject: (): Promise<Response> =>
delete: () => this.processDelete(), this.processFollowReject(),
user: () => this.processUserRequest(), "pub.versia:likes/Like": (): Promise<Response> =>
unknown: () => this.processLikeRequest(),
delete: (): Promise<Response> => this.processDelete(),
user: (): Promise<Response> => this.processUserRequest(),
unknown: (): Response &
TypedResponse<{ error: string }, 400, "json"> =>
this.context.json({ error: "Unknown entity type" }, 400), this.context.json({ error: "Unknown entity type" }, 400),
}); });
} catch (e) { } catch (e) {

View file

@ -30,7 +30,8 @@ describe("S3MediaDriver", () => {
putObject: mock(() => Promise.resolve()), putObject: mock(() => Promise.resolve()),
getObject: mock(() => getObject: mock(() =>
Promise.resolve({ Promise.resolve({
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)), arrayBuffer: (): Promise<ArrayBuffer> =>
Promise.resolve(new ArrayBuffer(8)),
headers: new Headers({ "Content-Type": "image/webp" }), headers: new Headers({ "Content-Type": "image/webp" }),
}), }),
), ),

View file

@ -58,7 +58,7 @@ describe("BlurhashPreprocessor", () => {
}); });
mock.module("blurhash", () => ({ mock.module("blurhash", () => ({
encode: () => { encode: (): void => {
throw new Error("Test error"); throw new Error("Test error");
}, },
})); }));

View file

@ -50,9 +50,9 @@ describe("PluginLoader", () => {
test("getDirectories should return directories", async () => { test("getDirectories should return directories", async () => {
mockReaddir.mockResolvedValue([ mockReaddir.mockResolvedValue([
{ name: "dir1", isDirectory: () => true }, { name: "dir1", isDirectory: (): true => true },
{ name: "file1", isDirectory: () => false }, { name: "file1", isDirectory: (): false => false },
{ name: "dir2", isDirectory: () => true }, { name: "dir2", isDirectory: (): true => true },
]); ]);
// biome-ignore lint/complexity/useLiteralKeys: Private method // biome-ignore lint/complexity/useLiteralKeys: Private method
@ -80,7 +80,8 @@ describe("PluginLoader", () => {
test("parseManifestFile should parse JSON manifest", async () => { test("parseManifestFile should parse JSON manifest", async () => {
const manifestContent = { name: "test-plugin" }; const manifestContent = { name: "test-plugin" };
Bun.file = jest.fn().mockReturnValue({ Bun.file = jest.fn().mockReturnValue({
text: () => Promise.resolve(JSON.stringify(manifestContent)), text: (): Promise<string> =>
Promise.resolve(JSON.stringify(manifestContent)),
}); });
// biome-ignore lint/complexity/useLiteralKeys: Private method // 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 () => { test("findPlugins should return plugin directories with valid manifest and entrypoint", async () => {
mockReaddir mockReaddir
.mockResolvedValueOnce([ .mockResolvedValueOnce([
{ name: "plugin1", isDirectory: () => true }, { name: "plugin1", isDirectory: (): true => true },
{ name: "plugin2", isDirectory: () => true }, { name: "plugin2", isDirectory: (): true => true },
]) ])
.mockResolvedValue(["manifest.json", "index.ts"]); .mockResolvedValue(["manifest.json", "index.ts"]);
@ -111,7 +112,8 @@ describe("PluginLoader", () => {
}; };
mockReaddir.mockResolvedValue(["manifest.json"]); mockReaddir.mockResolvedValue(["manifest.json"]);
Bun.file = jest.fn().mockReturnValue({ Bun.file = jest.fn().mockReturnValue({
text: () => Promise.resolve(JSON.stringify(manifestContent)), text: (): Promise<string> =>
Promise.resolve(JSON.stringify(manifestContent)),
}); });
manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({ manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({
success: true, success: true,
@ -141,7 +143,8 @@ describe("PluginLoader", () => {
}; };
mockReaddir.mockResolvedValue(["manifest.json"]); mockReaddir.mockResolvedValue(["manifest.json"]);
Bun.file = jest.fn().mockReturnValue({ Bun.file = jest.fn().mockReturnValue({
text: () => Promise.resolve(JSON.stringify(manifestContent)), text: (): Promise<string> =>
Promise.resolve(JSON.stringify(manifestContent)),
}); });
manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({ manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({
success: false, success: false,
@ -183,12 +186,13 @@ describe("PluginLoader", () => {
mockReaddir mockReaddir
.mockResolvedValueOnce([ .mockResolvedValueOnce([
{ name: "plugin1", isDirectory: () => true }, { name: "plugin1", isDirectory: (): true => true },
{ name: "plugin2", isDirectory: () => true }, { name: "plugin2", isDirectory: (): true => true },
]) ])
.mockResolvedValue(["manifest.json", "index.ts"]); .mockResolvedValue(["manifest.json", "index.ts"]);
Bun.file = jest.fn().mockReturnValue({ Bun.file = jest.fn().mockReturnValue({
text: () => Promise.resolve(JSON.stringify(manifestContent)), text: (): Promise<string> =>
Promise.resolve(JSON.stringify(manifestContent)),
}); });
manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({ manifestSchema.safeParseAsync = jest.fn().mockResolvedValue({
success: true, success: true,

View file

@ -5,6 +5,7 @@
import { getLogger } from "@logtape/logtape"; import { getLogger } from "@logtape/logtape";
import { Note, User, db } from "@versia/kit/db"; import { Note, User, db } from "@versia/kit/db";
import type { SQL, ValueOrArray } from "drizzle-orm";
import { import {
Ingest as SonicChannelIngest, Ingest as SonicChannelIngest,
Search as SonicChannelSearch, Search as SonicChannelSearch,
@ -63,21 +64,21 @@ export class SonicSearchManager {
// Connect to Sonic // Connect to Sonic
await new Promise<boolean>((resolve, reject) => { await new Promise<boolean>((resolve, reject) => {
this.searchChannel.connect({ this.searchChannel.connect({
connected: () => { connected: (): void => {
!silent && !silent &&
this.logger.info`Connected to Sonic Search Channel`; this.logger.info`Connected to Sonic Search Channel`;
resolve(true); resolve(true);
}, },
disconnected: () => disconnected: (): void =>
this.logger this.logger
.error`Disconnected from Sonic Search Channel. You might be using an incorrect password.`, .error`Disconnected from Sonic Search Channel. You might be using an incorrect password.`,
timeout: () => timeout: (): void =>
this.logger this.logger
.error`Sonic Search Channel connection timed out`, .error`Sonic Search Channel connection timed out`,
retrying: () => retrying: (): void =>
this.logger this.logger
.warn`Retrying connection to Sonic Search Channel`, .warn`Retrying connection to Sonic Search Channel`,
error: (error) => { error: (error): void => {
this.logger this.logger
.error`Failed to connect to Sonic Search Channel: ${error}`; .error`Failed to connect to Sonic Search Channel: ${error}`;
reject(error); reject(error);
@ -87,20 +88,20 @@ export class SonicSearchManager {
await new Promise<boolean>((resolve, reject) => { await new Promise<boolean>((resolve, reject) => {
this.ingestChannel.connect({ this.ingestChannel.connect({
connected: () => { connected: (): void => {
!silent && !silent &&
this.logger.info`Connected to Sonic Ingest Channel`; this.logger.info`Connected to Sonic Ingest Channel`;
resolve(true); resolve(true);
}, },
disconnected: () => disconnected: (): void =>
this.logger.error`Disconnected from Sonic Ingest Channel`, this.logger.error`Disconnected from Sonic Ingest Channel`,
timeout: () => timeout: (): void =>
this.logger this.logger
.error`Sonic Ingest Channel connection timed out`, .error`Sonic Ingest Channel connection timed out`,
retrying: () => retrying: (): void =>
this.logger this.logger
.warn`Retrying connection to Sonic Ingest Channel`, .warn`Retrying connection to Sonic Ingest Channel`,
error: (error) => { error: (error): void => {
this.logger this.logger
.error`Failed to connect to Sonic Ingest Channel: ${error}`; .error`Failed to connect to Sonic Ingest Channel: ${error}`;
reject(error); reject(error);
@ -161,7 +162,7 @@ export class SonicSearchManager {
note: true, note: true,
createdAt: true, createdAt: true,
}, },
orderBy: (user, { asc }) => asc(user.createdAt), orderBy: (user, { asc }): ValueOrArray<SQL> => asc(user.createdAt),
}); });
} }
@ -182,7 +183,8 @@ export class SonicSearchManager {
content: true, content: true,
createdAt: true, createdAt: true,
}, },
orderBy: (status, { asc }) => asc(status.createdAt), orderBy: (status, { asc }): ValueOrArray<SQL> =>
asc(status.createdAt),
}); });
} }

View file

@ -3,7 +3,13 @@ import { Args, type Command, Flags, type Interfaces } from "@oclif/core";
import { Instance, User, db } from "@versia/kit/db"; import { Instance, User, db } from "@versia/kit/db";
import { Emojis, Instances, Users } from "@versia/kit/tables"; import { Emojis, Instances, Users } from "@versia/kit/tables";
import chalk from "chalk"; 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"; import { BaseCommand } from "./base.ts";
export type FlagsType<T extends typeof Command> = Interfaces.InferredFlags< export type FlagsType<T extends typeof Command> = Interfaces.InferredFlags<
@ -203,7 +209,11 @@ export abstract class EmojiFinderCommand<
this.args = args as ArgsType<T>; this.args = args as ArgsType<T>;
} }
public async findEmojis() { public async findEmojis(): Promise<
(InferSelectModel<typeof Emojis> & {
instanceUrl: string | null;
})[]
> {
// Check if there are asterisks in the identifier but no pattern flag, warn the user if so // 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) { if (this.args.identifier.includes("*") && !this.flags.pattern) {
this.log( this.log(

View file

@ -75,13 +75,13 @@ export default class UserCreate extends BaseCommand<typeof UserCreate> {
const password1 = await input({ const password1 = await input({
message: "Please enter the user's password:", message: "Please enter the user's password:",
// Set whatever the user types to stars // Set whatever the user types to stars
transformer: (value) => "*".repeat(value.length), transformer: (value): string => "*".repeat(value.length),
}); });
const password2 = await input({ const password2 = await input({
message: "Please confirm the user's password:", message: "Please confirm the user's password:",
// Set whatever the user types to stars // Set whatever the user types to stars
transformer: (value) => "*".repeat(value.length), transformer: (value): string => "*".repeat(value.length),
}); });
if (password1 !== password2) { if (password1 !== password2) {

View file

@ -40,7 +40,7 @@ export const db =
) )
: drizzle(primaryDb, { schema }); : drizzle(primaryDb, { schema });
export const setupDatabase = async (info = true) => { export const setupDatabase = async (info = true): Promise<void> => {
const logger = getLogger("database"); const logger = getLogger("database");
for (const dbPool of [primaryDb, ...replicas]) { for (const dbPool of [primaryDb, ...replicas]) {

View file

@ -1,10 +1,10 @@
import { createMiddleware } from "@hono/hono/factory"; import { createMiddleware } from "@hono/hono/factory";
import { getLogger } from "@logtape/logtape"; import { getLogger } from "@logtape/logtape";
import type { SocketAddress } from "bun"; import type { BunFile, SocketAddress } from "bun";
import { matches } from "ip-matching"; import { matches } from "ip-matching";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
const baitFile = async () => { const baitFile = async (): Promise<BunFile | undefined> => {
const file = Bun.file(config.http.bait.send_file || "./beemovie.txt"); const file = Bun.file(config.http.bait.send_file || "./beemovie.txt");
if (await file.exists()) { if (await file.exists()) {

View file

@ -8,6 +8,7 @@
import { loadConfig, watchConfig } from "c12"; import { loadConfig, watchConfig } from "c12";
import { fromZodError } from "zod-validation-error"; import { fromZodError } from "zod-validation-error";
import { type Config, configValidator } from "./config.type"; import { type Config, configValidator } from "./config.type";
export type { Config } from "./config.type";
const { config } = await watchConfig({ const { config } = await watchConfig({
configFile: "./config/config.toml", configFile: "./config/config.toml",
@ -29,4 +30,3 @@ if (!parsed.success) {
const exportedConfig = parsed.data; const exportedConfig = parsed.data;
export { exportedConfig as config }; export { exportedConfig as config };
export type { Config };

View file

@ -1,6 +1,4 @@
import { Hooks } from "./hooks.ts"; // biome-ignore lint/performance/noBarrelFile: <explanation>
import { Plugin } from "./plugin.ts"; export { Hooks } from "./hooks.ts";
import type { Manifest } from "./schema.ts"; export { Plugin } from "./plugin.ts";
export type { Manifest } from "./schema.ts";
export type { Manifest };
export { Plugin, Hooks };

View file

@ -1,5 +1,6 @@
import { createMiddleware } from "@hono/hono/factory"; import { createMiddleware } from "@hono/hono/factory";
import type { OpenAPIHono } from "@hono/zod-openapi"; import type { OpenAPIHono } from "@hono/zod-openapi";
import type { MiddlewareHandler } from "hono";
import type { z } from "zod"; import type { z } from "zod";
import { type ZodError, fromZodError } from "zod-validation-error"; import { type ZodError, fromZodError } from "zod-validation-error";
import type { HonoEnv } from "~/types/api"; import type { HonoEnv } from "~/types/api";
@ -21,7 +22,7 @@ export class Plugin<ConfigSchema extends z.ZodTypeAny> {
public constructor(private configSchema: ConfigSchema) {} public constructor(private configSchema: ConfigSchema) {}
public get middleware() { public get middleware(): MiddlewareHandler {
// Middleware that adds the plugin's configuration to the request object // Middleware that adds the plugin's configuration to the request object
return createMiddleware<HonoPluginEnv<ConfigSchema>>( return createMiddleware<HonoPluginEnv<ConfigSchema>>(
async (context, next) => { async (context, next) => {
@ -34,7 +35,7 @@ export class Plugin<ConfigSchema extends z.ZodTypeAny> {
public registerRoute( public registerRoute(
path: string, path: string,
fn: (app: OpenAPIHono<HonoPluginEnv<ConfigSchema>>) => void, fn: (app: OpenAPIHono<HonoPluginEnv<ConfigSchema>>) => void,
) { ): void {
this.routes.push({ this.routes.push({
path, path,
fn, fn,
@ -54,7 +55,7 @@ export class Plugin<ConfigSchema extends z.ZodTypeAny> {
} }
} }
protected _addToApp(app: OpenAPIHono<HonoEnv>) { protected _addToApp(app: OpenAPIHono<HonoEnv>): void {
for (const route of this.routes) { for (const route of this.routes) {
app.use(route.path, this.middleware); app.use(route.path, this.middleware);
route.fn( route.fn(
@ -66,7 +67,7 @@ export class Plugin<ConfigSchema extends z.ZodTypeAny> {
public registerHandler<HookName extends keyof ServerHooks>( public registerHandler<HookName extends keyof ServerHooks>(
hook: HookName, hook: HookName,
handler: ServerHooks[HookName], handler: ServerHooks[HookName],
) { ): void {
this.handlers[hook] = handler; this.handlers[hook] = handler;
} }
@ -81,7 +82,7 @@ export class Plugin<ConfigSchema extends z.ZodTypeAny> {
/** /**
* Returns the internal configuration object. * Returns the internal configuration object.
*/ */
private getConfig() { private getConfig(): z.infer<ConfigSchema> {
if (!this.store) { if (!this.store) {
throw new Error("Configuration has not been loaded yet."); throw new Error("Configuration has not been loaded yet.");
} }

View file

@ -94,6 +94,7 @@ export type Manifest = {
}; };
// This is a type guard to ensure that the schema and the type are in sync // This is a type guard to ensure that the schema and the type are in sync
// biome-ignore lint/nursery/useExplicitType: <explanation>
function assert<_T extends never>() { function assert<_T extends never>() {
// ... // ...
} }

View file

@ -63,7 +63,7 @@ const schemas = {
}), }),
}; };
export default (plugin: PluginType) => export default (plugin: PluginType): void =>
plugin.registerRoute("/oauth/authorize", (app) => plugin.registerRoute("/oauth/authorize", (app) =>
app.openapi( app.openapi(
{ {

View file

@ -3,7 +3,7 @@ import { createRoute, z } from "@hono/zod-openapi";
import { exportJWK } from "jose"; import { exportJWK } from "jose";
import type { PluginType } from "../index.ts"; import type { PluginType } from "../index.ts";
export default (plugin: PluginType) => { export default (plugin: PluginType): void => {
plugin.registerRoute("/.well-known/jwks", (app) => plugin.registerRoute("/.well-known/jwks", (app) =>
app.openapi( app.openapi(
createRoute({ createRoute({

View file

@ -2,7 +2,7 @@ import { randomString } from "@/math.ts";
import { setCookie } from "@hono/hono/cookie"; import { setCookie } from "@hono/hono/cookie";
import { createRoute, z } from "@hono/zod-openapi"; import { createRoute, z } from "@hono/zod-openapi";
import { User, db } from "@versia/kit/db"; 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 { import {
OpenIdAccounts, OpenIdAccounts,
RolePermissions, RolePermissions,
@ -29,7 +29,7 @@ export const schemas = {
}), }),
}; };
export default (plugin: PluginType) => { export default (plugin: PluginType): void => {
plugin.registerRoute("/oauth/sso/{issuer}/callback", (app) => { plugin.registerRoute("/oauth/sso/{issuer}/callback", (app) => {
app.openapi( app.openapi(
createRoute({ createRoute({
@ -150,7 +150,7 @@ export default (plugin: PluginType) => {
// Check if account is already linked // Check if account is already linked
const account = await db.query.OpenIdAccounts.findFirst({ const account = await db.query.OpenIdAccounts.findFirst({
where: (account, { eq, and }) => where: (account, { eq, and }): SQL | undefined =>
and( and(
eq(account.serverId, sub), eq(account.serverId, sub),
eq(account.issuerId, issuer.id), eq(account.issuerId, issuer.id),
@ -188,7 +188,7 @@ export default (plugin: PluginType) => {
let userId = ( let userId = (
await db.query.OpenIdAccounts.findFirst({ await db.query.OpenIdAccounts.findFirst({
where: (account, { eq, and }) => where: (account, { eq, and }): SQL | undefined =>
and( and(
eq(account.serverId, sub), eq(account.serverId, sub),
eq(account.issuerId, issuer.id), eq(account.issuerId, issuer.id),

View file

@ -1,7 +1,7 @@
import { jsonOrForm } from "@/api"; import { jsonOrForm } from "@/api";
import { createRoute, z } from "@hono/zod-openapi"; import { createRoute, z } from "@hono/zod-openapi";
import { db } from "@versia/kit/db"; 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 { Tokens } from "@versia/kit/tables";
import type { PluginType } from "../../index.ts"; 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) => { plugin.registerRoute("/oauth/revoke", (app) => {
app.openapi( app.openapi(
createRoute({ createRoute({
@ -63,7 +63,7 @@ export default (plugin: PluginType) => {
context.req.valid("json"); context.req.valid("json");
const foundToken = await db.query.Tokens.findFirst({ const foundToken = await db.query.Tokens.findFirst({
where: (tokenTable, { eq, and }) => where: (tokenTable, { eq, and }): SQL | undefined =>
and( and(
eq(tokenTable.accessToken, token ?? ""), eq(tokenTable.accessToken, token ?? ""),
eq(tokenTable.clientId, client_id), eq(tokenTable.clientId, client_id),

View file

@ -20,7 +20,7 @@ export const schemas = {
}), }),
}; };
export default (plugin: PluginType) => { export default (plugin: PluginType): void => {
plugin.registerRoute("/oauth/sso", (app) => { plugin.registerRoute("/oauth/sso", (app) => {
app.openapi( app.openapi(
createRoute({ createRoute({

View file

@ -1,7 +1,7 @@
import { jsonOrForm } from "@/api"; import { jsonOrForm } from "@/api";
import { createRoute, z } from "@hono/zod-openapi"; import { createRoute, z } from "@hono/zod-openapi";
import { Application, db } from "@versia/kit/db"; 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 { Tokens } from "@versia/kit/tables";
import type { PluginType } from "../../index.ts"; 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) => { plugin.registerRoute("/oauth/token", (app) => {
app.openapi( app.openapi(
createRoute({ createRoute({
@ -155,7 +155,7 @@ export default (plugin: PluginType) => {
} }
const token = await db.query.Tokens.findFirst({ const token = await db.query.Tokens.findFirst({
where: (token, { eq, and }) => where: (token, { eq, and }): SQL | undefined =>
and( and(
eq(token.code, code), eq(token.code, code),
eq( eq(

View file

@ -2,12 +2,12 @@ import { auth } from "@/api";
import { proxyUrl } from "@/response"; import { proxyUrl } from "@/response";
import { createRoute, z } from "@hono/zod-openapi"; import { createRoute, z } from "@hono/zod-openapi";
import { db } from "@versia/kit/db"; 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 { OpenIdAccounts, RolePermissions } from "@versia/kit/tables";
import type { PluginType } from "~/plugins/openid"; import type { PluginType } from "~/plugins/openid";
import { ErrorSchema } from "~/types/api"; import { ErrorSchema } from "~/types/api";
export default (plugin: PluginType) => { export default (plugin: PluginType): void => {
plugin.registerRoute("/api/v1/sso", (app) => { plugin.registerRoute("/api/v1/sso", (app) => {
app.openapi( app.openapi(
createRoute({ createRoute({
@ -88,7 +88,7 @@ export default (plugin: PluginType) => {
} }
const account = await db.query.OpenIdAccounts.findFirst({ const account = await db.query.OpenIdAccounts.findFirst({
where: (account, { eq, and }) => where: (account, { eq, and }): SQL | undefined =>
and( and(
eq(account.userId, user.id), eq(account.userId, user.id),
eq(account.issuerId, issuerId), eq(account.issuerId, issuerId),
@ -181,7 +181,7 @@ export default (plugin: PluginType) => {
} }
const account = await db.query.OpenIdAccounts.findFirst({ const account = await db.query.OpenIdAccounts.findFirst({
where: (account, { eq, and }) => where: (account, { eq, and }): SQL | undefined =>
and( and(
eq(account.userId, user.id), eq(account.userId, user.id),
eq(account.issuerId, issuerId), eq(account.issuerId, issuerId),

View file

@ -10,7 +10,7 @@ import { ErrorSchema } from "~/types/api";
import type { PluginType } from "../../index.ts"; import type { PluginType } from "../../index.ts";
import { oauthDiscoveryRequest, oauthRedirectUri } from "../../utils.ts"; import { oauthDiscoveryRequest, oauthRedirectUri } from "../../utils.ts";
export default (plugin: PluginType) => { export default (plugin: PluginType): void => {
plugin.registerRoute("/api/v1/sso", (app) => { plugin.registerRoute("/api/v1/sso", (app) => {
app.openapi( app.openapi(
{ {

View file

@ -1,5 +1,5 @@
import { db } from "@versia/kit/db"; 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 { Applications, OpenIdLoginFlows } from "@versia/kit/tables";
import { import {
type AuthorizationResponseError, type AuthorizationResponseError,
@ -7,6 +7,7 @@ import {
ClientSecretPost, ClientSecretPost,
type ResponseBodyError, type ResponseBodyError,
type TokenEndpointResponse, type TokenEndpointResponse,
type UserInfoResponse,
authorizationCodeGrantRequest, authorizationCodeGrantRequest,
discoveryRequest, discoveryRequest,
expectNoState, expectNoState,
@ -17,6 +18,7 @@ import {
userInfoRequest, userInfoRequest,
validateAuthResponse, validateAuthResponse,
} from "oauth4webapi"; } from "oauth4webapi";
import type { ApplicationType } from "~/classes/database/application";
export const oauthDiscoveryRequest = ( export const oauthDiscoveryRequest = (
issuerUrl: string | URL, issuerUrl: string | URL,
@ -28,12 +30,19 @@ export const oauthDiscoveryRequest = (
}).then((res) => processDiscoveryResponse(issuerUrlurl, res)); }).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(); new URL(`/oauth/sso/${issuer}/callback`, baseUrl).toString();
const getFlow = (flowId: string) => { const getFlow = (
flowId: string,
): Promise<
| (InferSelectModel<typeof OpenIdLoginFlows> & {
application?: ApplicationType | null;
})
| undefined
> => {
return db.query.OpenIdLoginFlows.findFirst({ return db.query.OpenIdLoginFlows.findFirst({
where: (flow, { eq }) => eq(flow.id, flowId), where: (flow, { eq }): SQL | undefined => eq(flow.id, flowId),
with: { with: {
application: true, application: true,
}, },
@ -100,7 +109,7 @@ const getUserInfo = (
clientId: string, clientId: string,
accessToken: string, accessToken: string,
sub: string, sub: string,
) => { ): Promise<UserInfoResponse> => {
return userInfoRequest( return userInfoRequest(
authServer, authServer,
{ {
@ -138,7 +147,16 @@ export const automaticOidcFlow = async (
}) })
| null, | null,
) => Response, ) => Response,
) => { ): Promise<
| Response
| {
userInfo: UserInfoResponse;
flow: InferSelectModel<typeof OpenIdLoginFlows> & {
application?: ApplicationType | null;
};
claims: Record<string, unknown>;
}
> => {
const flow = await getFlow(flowId); const flow = await getFlow(flowId);
if (!flow) { if (!flow) {

View file

@ -18,7 +18,9 @@ afterAll(async () => {
await deleteUsers(); await deleteUsers();
}); });
const getFormData = (object: Record<string, string | number | boolean>) => const getFormData = (
object: Record<string, string | number | boolean>,
): FormData =>
Object.keys(object).reduce((formData, key) => { Object.keys(object).reduce((formData, key) => {
formData.append(key, String(object[key])); formData.append(key, String(object[key]));
return formData; return formData;

View file

@ -6,6 +6,7 @@ import { solveChallenge } from "altcha-lib";
import { asc, inArray, like } from "drizzle-orm"; import { asc, inArray, like } from "drizzle-orm";
import { appFactory } from "~/app"; import { appFactory } from "~/app";
import type { Status } from "~/classes/functions/status"; import type { Status } from "~/classes/functions/status";
import type { Token } from "~/classes/functions/token";
import { searchManager } from "~/classes/search/search-manager"; import { searchManager } from "~/classes/search/search-manager";
import { setupDatabase } from "~/drizzle/db"; import { setupDatabase } from "~/drizzle/db";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
@ -17,18 +18,28 @@ if (config.sonic.enabled) {
const app = await appFactory(); const app = await appFactory();
export function fakeRequest(url: URL | string, init?: RequestInit) { export function fakeRequest(
url: URL | string,
init?: RequestInit,
): Promise<Response> {
return Promise.resolve( return Promise.resolve(
app.fetch(new Request(new URL(url, config.http.base_url), init)), app.fetch(new Request(new URL(url, config.http.base_url), init)),
); );
} }
export const deleteOldTestUsers = async () => { export const deleteOldTestUsers = async (): Promise<void> => {
// Deletes all users that match the test username (test-<32 random characters>) // Deletes all users that match the test username (test-<32 random characters>)
await db.delete(Users).where(like(Users.username, "test-%")); 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<void>;
}> => {
const users: User[] = []; const users: User[] = [];
const passwords: string[] = []; const passwords: string[] = [];
@ -67,7 +78,7 @@ export const getTestUsers = async (count: number) => {
users, users,
tokens, tokens,
passwords, passwords,
deleteUsers: async () => { deleteUsers: async (): Promise<void> => {
await db.delete(Users).where( await db.delete(Users).where(
inArray( inArray(
Users.id, Users.id,
@ -82,7 +93,7 @@ export const getTestStatuses = async (
count: number, count: number,
user: User, user: User,
partial?: Partial<Status>, partial?: Partial<Status>,
) => { ): Promise<Note["data"][]> => {
const statuses: Note[] = []; const statuses: Note[] = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@ -123,7 +134,7 @@ export const getTestStatuses = async (
* Only to be used in tests * Only to be used in tests
* @returns Base64 encoded payload * @returns Base64 encoded payload
*/ */
export const getSolvedChallenge = async () => { export const getSolvedChallenge = async (): Promise<string> => {
const { challenge } = await generateChallenge(100); const { challenge } = await generateChallenge(100);
const solution = await solveChallenge( const solution = await solveChallenge(

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

View file

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

View file

@ -1,4 +1,4 @@
import { config } from "~/packages/config-manager/index.ts"; 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(); 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 { lookup } from "mime-types";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
export const getBestContentType = (content?: ContentFormat | null) => { export const getBestContentType = (
content?: ContentFormat | null,
): {
content: string;
format: string;
} => {
if (!content) { if (!content) {
return { content: "", format: "text/plain" }; return { content: "", format: "text/plain" };
} }

View file

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

View file

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

View file

@ -5,7 +5,10 @@ import { sentry } from "./sentry.ts";
export const renderMarkdownInPath = async ( export const renderMarkdownInPath = async (
path: string, path: string,
defaultText?: string, defaultText?: string,
) => { ): Promise<{
content: string;
lastModified: Date;
}> => {
let content = await markdownParse(defaultText ?? ""); let content = await markdownParse(defaultText ?? "");
let lastModified = new Date(1970, 0, 0); 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( Buffer.from(crypto.getRandomValues(new Uint8Array(length))).toString(
encoding, encoding,
); );

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
import type { db } from "@versia/kit/db"; import type { db } from "@versia/kit/db";
import type { SQL } from "drizzle-orm";
import type { import type {
Notification, Notification,
findManyNotifications, findManyNotifications,
@ -18,7 +19,10 @@ export async function fetchTimeline<T extends UserType | Status | Notification>(
| Parameters<typeof db.query.Notifications.findMany>[0], | Parameters<typeof db.query.Notifications.findMany>[0],
req: Request, req: Request,
userId?: string, userId?: string,
) { ): Promise<{
link: string;
objects: T[];
}> {
// BEFORE: Before in a top-to-bottom order, so the most recent posts // 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 // 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 // @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({ const objectsBefore = await model({
...args, ...args,
// @ts-expect-error this hack breaks typing :( // @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, limit: 1,
}); });
@ -56,7 +61,8 @@ export async function fetchTimeline<T extends UserType | Status | Notification>(
const objectsAfter = await model({ const objectsAfter = await model({
...args, ...args,
// @ts-expect-error this hack breaks typing :( // @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, limit: 1,
}); });