mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 00:18:19 +01:00
refactor: ♻️ Always use explicit types in every function
This commit is contained in:
parent
54cea29ce9
commit
c1dcdc78ae
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
2
app.ts
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
||||||
12
biome.json
12
biome.json
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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") ?? "") ?? "",
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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" }),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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]) {
|
||||||
|
|
|
||||||
|
|
@ -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()) {
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>() {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
48
utils/api.ts
48
utils/api.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Context } from "@hono/hono";
|
import type { Context, MiddlewareHandler } from "@hono/hono";
|
||||||
import { createMiddleware } from "@hono/hono/factory";
|
import { 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");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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" };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue