Add bait mode, fix bugs

This commit is contained in:
Jesse Wierzbinski 2024-03-12 22:10:32 -10:00
parent d633116571
commit 480fcb363f
No known key found for this signature in database
14 changed files with 1627 additions and 236 deletions

View file

@ -1,12 +1,6 @@
// Delete dist directory // Delete dist directory
import chalk from "chalk";
import { rm, cp, mkdir, exists } from "fs/promises"; import { rm, cp, mkdir, exists } from "fs/promises";
import { rawRoutes } from "~routes";
console.log(
chalk.red(
"Warning: Build is currently broken due to a bug in Bun causing it not to parse dynamic imports"
)
);
if (!(await exists("./pages/dist"))) { if (!(await exists("./pages/dist"))) {
console.log("Please build the Vite server first, or use `bun prod-build`"); console.log("Please build the Vite server first, or use `bun prod-build`");
@ -25,6 +19,8 @@ await Bun.build({
process.cwd() + "/index.ts", process.cwd() + "/index.ts",
process.cwd() + "/prisma.ts", process.cwd() + "/prisma.ts",
process.cwd() + "/cli.ts", process.cwd() + "/cli.ts",
// Force Bun to include endpoints
...Object.values(rawRoutes),
], ],
outdir: process.cwd() + "/dist", outdir: process.cwd() + "/dist",
target: "bun", target: "bun",
@ -47,4 +43,7 @@ await cp(process.cwd() + "/pages/dist", process.cwd() + "/dist/pages/", {
recursive: true, recursive: true,
}); });
// Copy the Bee Movie script from pages
await cp(process.cwd() + "/pages/beemovie.txt", process.cwd() + "/dist/pages/");
console.log(`Built!`); console.log(`Built!`);

BIN
bun.lockb

Binary file not shown.

103
cli.ts
View file

@ -19,6 +19,28 @@ const args = process.argv;
const config = await new ConfigManager({}).getConfig(); const config = await new ConfigManager({}).getConfig();
const filterObjects = <T extends object>(output: T[], fields: string[]) => {
if (fields.length === 0) return output;
return output.map(element => {
// If fields is specified, only include provided fields
// This is a bit of a mess
if (fields.length > 0) {
const keys = Object.keys(element);
const filteredKeys = keys.filter(key => fields.includes(key));
return Object.entries(element)
.filter(([key]) => filteredKeys.includes(key))
.reduce((acc, [key, value]) => {
// @ts-expect-error This is fine
acc[key] = value;
return acc;
}, {}) as Partial<T>;
} else {
return element;
}
});
};
const cliBuilder = new CliBuilder([ const cliBuilder = new CliBuilder([
new CliCommand<{ new CliCommand<{
username: string; username: string;
@ -270,7 +292,7 @@ const cliBuilder = new CliBuilder([
}, },
], ],
async (instance: CliCommand, args) => { async (instance: CliCommand, args) => {
const { admins, help } = args; const { admins, help, fields = [] } = args;
if (help) { if (help) {
instance.displayHelp(); instance.displayHelp();
@ -281,16 +303,21 @@ const cliBuilder = new CliBuilder([
console.log(`${chalk.red(``)} Invalid format`); console.log(`${chalk.red(``)} Invalid format`);
return 1; return 1;
} }
const users = filterObjects(
const users = await client.user.findMany({ await client.user.findMany({
where: { where: {
isAdmin: admins || undefined, isAdmin: admins || undefined,
}, },
take: args.limit ?? 200, take: args.limit ?? 200,
include: { include: {
instance: true, instance:
}, fields.length == 0
}); ? true
: fields.includes("instance"),
},
}),
fields
);
if (args.redact) { if (args.redact) {
for (const user of users) { for (const user of users) {
@ -301,19 +328,6 @@ const cliBuilder = new CliBuilder([
} }
} }
if (args.fields) {
for (const user of users) {
const keys = Object.keys(user);
for (const key of keys) {
if (!args.fields.includes(key)) {
// @ts-expect-error Shouldn't cause issues in this case
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete user[key];
}
}
}
}
if (args.format === "json") { if (args.format === "json") {
console.log(JSON.stringify(users, null, 4)); console.log(JSON.stringify(users, null, 4));
return 0; return 0;
@ -327,27 +341,20 @@ const cliBuilder = new CliBuilder([
`${chalk.green(``)} Found ${chalk.blue(users.length)} users (limit ${args.limit ?? 200})` `${chalk.green(``)} Found ${chalk.blue(users.length)} users (limit ${args.limit ?? 200})`
); );
const tableHead = { const tableHead = filterObjects(
username: chalk.white(chalk.bold("Username")), [
email: chalk.white(chalk.bold("Email")), {
displayName: chalk.white(chalk.bold("Display Name")), username: chalk.white(chalk.bold("Username")),
isAdmin: chalk.white(chalk.bold("Admin?")), email: chalk.white(chalk.bold("Email")),
instance: chalk.white(chalk.bold("Instance URL")), displayName: chalk.white(chalk.bold("Display Name")),
createdAt: chalk.white(chalk.bold("Created At")), isAdmin: chalk.white(chalk.bold("Admin?")),
id: chalk.white(chalk.bold("Internal UUID")), instance: chalk.white(chalk.bold("Instance URL")),
}; createdAt: chalk.white(chalk.bold("Created At")),
id: chalk.white(chalk.bold("Internal UUID")),
// Only keep the fields specified if --fields is provided },
if (args.fields) { ],
const keys = Object.keys(tableHead); fields
for (const key of keys) { )[0];
if (!args.fields.includes(key)) {
// @ts-expect-error This is fine
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete tableHead[key];
}
}
}
const table = new Table({ const table = new Table({
head: Object.values(tableHead), head: Object.values(tableHead),
@ -364,7 +371,7 @@ const cliBuilder = new CliBuilder([
chalk.blue( chalk.blue(
user.instance ? user.instance.base_url : "Local" user.instance ? user.instance.base_url : "Local"
), ),
createdAt: () => chalk.blue(user.createdAt.toISOString()), createdAt: () => chalk.blue(user.createdAt?.toISOString()),
id: () => chalk.blue(user.id), id: () => chalk.blue(user.id),
}; };
@ -1744,8 +1751,6 @@ const cliBuilder = new CliBuilder([
), ),
]); ]);
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
const exitCode = await cliBuilder.processArgs(args); const exitCode = await cliBuilder.processArgs(args);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition process.exit(Number(exitCode == undefined ? 0 : exitCode));
process.exit(Number(exitCode ?? 0));

View file

@ -1,5 +1,5 @@
import type { APIAccount } from "~types/entities/account"; import type { APIAccount } from "~types/entities/account";
import type { User as LysandUser } from "~types/lysand/Object"; import type { LysandUser as LysandUser } from "~types/lysand/Object";
import { htmlToText } from "html-to-text"; import { htmlToText } from "html-to-text";
import type { User } from "@prisma/client"; import type { User } from "@prisma/client";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";

View file

@ -113,6 +113,7 @@
"next-route-matcher": "^1.0.1", "next-route-matcher": "^1.0.1",
"oauth4webapi": "^2.4.0", "oauth4webapi": "^2.4.0",
"prisma": "^5.6.0", "prisma": "^5.6.0",
"prisma-json-types-generator": "^3.0.4",
"prisma-redis-middleware": "^4.8.0", "prisma-redis-middleware": "^4.8.0",
"request-parser": "file:packages/request-parser", "request-parser": "file:packages/request-parser",
"semver": "^7.5.4", "semver": "^7.5.4",

View file

@ -55,6 +55,12 @@ export interface ConfigType {
bind_port: string; bind_port: string;
banned_ips: string[]; banned_ips: string[];
banned_user_agents: string[]; banned_user_agents: string[];
bait: {
enabled: boolean;
send_file?: string;
bait_ips: string[];
bait_user_agents: string[];
};
}; };
instance: { instance: {
@ -181,6 +187,12 @@ export const configDefaults: ConfigType = {
base_url: "http://lysand.localhost:8000", base_url: "http://lysand.localhost:8000",
banned_ips: [], banned_ips: [],
banned_user_agents: [], banned_user_agents: [],
bait: {
enabled: false,
send_file: "",
bait_ips: [],
bait_user_agents: [],
},
}, },
database: { database: {
host: "localhost", host: "localhost",

1363
pages/beemovie.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,10 @@ generator client {
binaryTargets = ["native", "debian-openssl-3.0.x"] binaryTargets = ["native", "debian-openssl-3.0.x"]
} }
generator json {
provider = "prisma-json-types-generator"
}
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
@ -42,6 +46,7 @@ model Instance {
base_url String base_url String
name String name String
version String version String
/// [InstanceLogo]
logo Json logo Json
emojis Emoji[] // One to many relation with Emoji emojis Emoji[] // One to many relation with Emoji
statuses Status[] // One to many relation with Status statuses Status[] // One to many relation with Status
@ -66,7 +71,9 @@ model LysandObject {
created_at DateTime @default(now()) created_at DateTime @default(now())
author LysandObject? @relation("LysandObjectToAuthor", fields: [authorId], references: [id], onDelete: Cascade) author LysandObject? @relation("LysandObjectToAuthor", fields: [authorId], references: [id], onDelete: Cascade)
authorId String? @db.Uuid authorId String? @db.Uuid
/// [ObjectData]
extra_data Json extra_data Json
/// [ObjectExtensions]
extensions Json extensions Json
children LysandObject[] @relation("LysandObjectToAuthor") children LysandObject[] @relation("LysandObjectToAuthor")
} }
@ -227,7 +234,9 @@ model User {
email String? @unique // Nullable email String? @unique // Nullable
note String @default("") note String @default("")
isAdmin Boolean @default(false) isAdmin Boolean @default(false)
/// [UserEndpoints]
endpoints Json? // Nullable endpoints Json? // Nullable
/// [UserSource]
source Json source Json
avatar String avatar String
header String header String

249
routes.ts
View file

@ -5,158 +5,89 @@ import type { APIRouteMeta } from "./types/api";
// This is to allow for compilation of the routes, so that we can minify them and // This is to allow for compilation of the routes, so that we can minify them and
// node_modules in production // node_modules in production
export const rawRoutes = { export const rawRoutes = {
"/api/v1/accounts": await import("./server/api/api/v1/accounts"), "/api/v1/accounts": "./server/api/api/v1/accounts",
"/api/v1/accounts/familiar_followers": await import( "/api/v1/accounts/familiar_followers":
"./server/api/api/v1/accounts/familiar_followers/index" "+api/v1/accounts/familiar_followers/index",
), "/api/v1/accounts/relationships":
"/api/v1/accounts/relationships": await import( "./server/api/api/v1/accounts/relationships/index",
"./server/api/api/v1/accounts/relationships/index" "/api/v1/accounts/search": "./server/api/api/v1/accounts/search/index",
), "/api/v1/accounts/update_credentials":
"/api/v1/accounts/search": await import( "./server/api/api/v1/accounts/update_credentials/index",
"./server/api/api/v1/accounts/search/index" "/api/v1/accounts/verify_credentials":
), "./server/api/api/v1/accounts/verify_credentials/index",
"/api/v1/accounts/update_credentials": await import( "/api/v1/apps": "./server/api/api/v1/apps/index",
"./server/api/api/v1/accounts/update_credentials/index" "/api/v1/apps/verify_credentials":
), "./server/api/api/v1/apps/verify_credentials/index",
"/api/v1/accounts/verify_credentials": await import( "/api/v1/blocks": "./server/api/api/v1/blocks/index",
"./server/api/api/v1/accounts/verify_credentials/index" "/api/v1/custom_emojis": "./server/api/api/v1/custom_emojis/index",
), "/api/v1/favourites": "./server/api/api/v1/favourites/index",
"/api/v1/apps": await import("./server/api/api/v1/apps/index"), "/api/v1/follow_requests": "./server/api/api/v1/follow_requests/index",
"/api/v1/apps/verify_credentials": await import( "/api/v1/instance": "./server/api/api/v1/instance/index",
"./server/api/api/v1/apps/verify_credentials/index" "/api/v1/media": "./server/api/api/v1/media/index",
), "/api/v1/mutes": "./server/api/api/v1/mutes/index",
"/api/v1/blocks": await import("./server/api/api/v1/blocks/index"), "/api/v1/notifications": "./server/api/api/v1/notifications/index",
"/api/v1/custom_emojis": await import( "/api/v1/profile/avatar": "./server/api/api/v1/profile/avatar",
"./server/api/api/v1/custom_emojis/index" "/api/v1/profile/header": "./server/api/api/v1/profile/header",
), "/api/v1/statuses": "./server/api/api/v1/statuses/index",
"/api/v1/favourites": await import("./server/api/api/v1/favourites/index"), "/api/v1/timelines/home": "./server/api/api/v1/timelines/home",
"/api/v1/follow_requests": await import( "/api/v1/timelines/public": "./server/api/api/v1/timelines/public",
"./server/api/api/v1/follow_requests/index" "/api/v2/media": "./server/api/api/v2/media/index",
), "/api/v2/search": "./server/api/api/v2/search/index",
"/api/v1/instance": await import("./server/api/api/v1/instance/index"), "/auth/login": "./server/api/auth/login/index",
"/api/v1/media": await import("./server/api/api/v1/media/index"), "/nodeinfo/2.0": "./server/api/nodeinfo/2.0/index",
"/api/v1/mutes": await import("./server/api/api/v1/mutes/index"), "/oauth/authorize-external": "./server/api/oauth/authorize-external/index",
"/api/v1/notifications": await import( "/oauth/providers": "./server/api/oauth/providers/index",
"./server/api/api/v1/notifications/index" "/oauth/token": "./server/api/oauth/token/index",
), "/api/v1/accounts/[id]": "./server/api/api/v1/accounts/[id]/index",
"/api/v1/profile/avatar": await import( "/api/v1/accounts/[id]/block": "./server/api/api/v1/accounts/[id]/block",
"./server/api/api/v1/profile/avatar" "/api/v1/accounts/[id]/follow": "./server/api/api/v1/accounts/[id]/follow",
), "/api/v1/accounts/[id]/followers":
"/api/v1/profile/header": await import( "./server/api/api/v1/accounts/[id]/followers",
"./server/api/api/v1/profile/header" "/api/v1/accounts/[id]/following":
), "./server/api/api/v1/accounts/[id]/following",
"/api/v1/statuses": await import("./server/api/api/v1/statuses/index"), "/api/v1/accounts/[id]/mute": "./server/api/api/v1/accounts/[id]/mute",
"/api/v1/timelines/home": await import( "/api/v1/accounts/[id]/note": "./server/api/api/v1/accounts/[id]/note",
"./server/api/api/v1/timelines/home" "/api/v1/accounts/[id]/pin": "./server/api/api/v1/accounts/[id]/pin",
), "/api/v1/accounts/[id]/remove_from_followers":
"/api/v1/timelines/public": await import( "./server/api/api/v1/accounts/[id]/remove_from_followers",
"./server/api/api/v1/timelines/public" "/api/v1/accounts/[id]/statuses":
), "./server/api/api/v1/accounts/[id]/statuses",
"/api/v2/media": await import("./server/api/api/v2/media/index"), "/api/v1/accounts/[id]/unblock":
"/api/v2/search": await import("./server/api/api/v2/search/index"), "./server/api/api/v1/accounts/[id]/unblock",
"/auth/login": await import("./server/api/auth/login/index"), "/api/v1/accounts/[id]/unfollow":
"/nodeinfo/2.0": await import("./server/api/nodeinfo/2.0/index"), "./server/api/api/v1/accounts/[id]/unfollow",
"/oauth/authorize-external": await import( "/api/v1/accounts/[id]/unmute": "./server/api/api/v1/accounts/[id]/unmute",
"./server/api/oauth/authorize-external/index" "/api/v1/accounts/[id]/unpin": "./server/api/api/v1/accounts/[id]/unpin",
), "/api/v1/follow_requests/[account_id]/authorize":
"/oauth/providers": await import("./server/api/oauth/providers/index"), "./server/api/api/v1/follow_requests/[account_id]/authorize",
"/oauth/token": await import("./server/api/oauth/token/index"), "/api/v1/follow_requests/[account_id]/reject":
"/api/v1/accounts/[id]": await import( "./server/api/api/v1/follow_requests/[account_id]/reject",
"./server/api/api/v1/accounts/[id]/index" "/api/v1/media/[id]": "./server/api/api/v1/media/[id]/index",
), "/api/v1/statuses/[id]": "./server/api/api/v1/statuses/[id]/index",
"/api/v1/accounts/[id]/block": await import( "/api/v1/statuses/[id]/context":
"./server/api/api/v1/accounts/[id]/block" "./server/api/api/v1/statuses/[id]/context",
), "/api/v1/statuses/[id]/favourite":
"/api/v1/accounts/[id]/follow": await import( "./server/api/api/v1/statuses/[id]/favourite",
"./server/api/api/v1/accounts/[id]/follow" "/api/v1/statuses/[id]/favourited_by":
), "./server/api/api/v1/statuses/[id]/favourited_by",
"/api/v1/accounts/[id]/followers": await import( "/api/v1/statuses/[id]/pin": "./server/api/api/v1/statuses/[id]/pin",
"./server/api/api/v1/accounts/[id]/followers" "/api/v1/statuses/[id]/reblog": "./server/api/api/v1/statuses/[id]/reblog",
), "/api/v1/statuses/[id]/reblogged_by":
"/api/v1/accounts/[id]/following": await import( "./server/api/api/v1/statuses/[id]/reblogged_by",
"./server/api/api/v1/accounts/[id]/following" "/api/v1/statuses/[id]/source": "./server/api/api/v1/statuses/[id]/source",
), "/api/v1/statuses/[id]/unfavourite":
"/api/v1/accounts/[id]/mute": await import( "./server/api/api/v1/statuses/[id]/unfavourite",
"./server/api/api/v1/accounts/[id]/mute" "/api/v1/statuses/[id]/unpin": "./server/api/api/v1/statuses/[id]/unpin",
), "/api/v1/statuses/[id]/unreblog":
"/api/v1/accounts/[id]/note": await import( "./server/api/api/v1/statuses/[id]/unreblog",
"./server/api/api/v1/accounts/[id]/note" "/media/[id]": "./server/api/media/[id]/index",
), "/oauth/callback/[issuer]": "./server/api/oauth/callback/[issuer]/index",
"/api/v1/accounts/[id]/pin": await import( "/object/[uuid]": "./server/api/object/[uuid]/index",
"./server/api/api/v1/accounts/[id]/pin" "/users/[uuid]": "./server/api/users/[uuid]/index",
), "/users/[uuid]/inbox": "./server/api/users/[uuid]/inbox/index",
"/api/v1/accounts/[id]/remove_from_followers": await import( "/users/[uuid]/outbox": "./server/api/users/[uuid]/outbox/index",
"./server/api/api/v1/accounts/[id]/remove_from_followers" "/[...404]": "./server/api/[...404]",
), } as Record<string, string>;
"/api/v1/accounts/[id]/statuses": await import(
"./server/api/api/v1/accounts/[id]/statuses"
),
"/api/v1/accounts/[id]/unblock": await import(
"./server/api/api/v1/accounts/[id]/unblock"
),
"/api/v1/accounts/[id]/unfollow": await import(
"./server/api/api/v1/accounts/[id]/unfollow"
),
"/api/v1/accounts/[id]/unmute": await import(
"./server/api/api/v1/accounts/[id]/unmute"
),
"/api/v1/accounts/[id]/unpin": await import(
"./server/api/api/v1/accounts/[id]/unpin"
),
"/api/v1/follow_requests/[account_id]/authorize": await import(
"./server/api/api/v1/follow_requests/[account_id]/authorize"
),
"/api/v1/follow_requests/[account_id]/reject": await import(
"./server/api/api/v1/follow_requests/[account_id]/reject"
),
"/api/v1/media/[id]": await import("./server/api/api/v1/media/[id]/index"),
"/api/v1/statuses/[id]": await import(
"./server/api/api/v1/statuses/[id]/index"
),
"/api/v1/statuses/[id]/context": await import(
"./server/api/api/v1/statuses/[id]/context"
),
"/api/v1/statuses/[id]/favourite": await import(
"./server/api/api/v1/statuses/[id]/favourite"
),
"/api/v1/statuses/[id]/favourited_by": await import(
"./server/api/api/v1/statuses/[id]/favourited_by"
),
"/api/v1/statuses/[id]/pin": await import(
"./server/api/api/v1/statuses/[id]/pin"
),
"/api/v1/statuses/[id]/reblog": await import(
"./server/api/api/v1/statuses/[id]/reblog"
),
"/api/v1/statuses/[id]/reblogged_by": await import(
"./server/api/api/v1/statuses/[id]/reblogged_by"
),
"/api/v1/statuses/[id]/source": await import(
"./server/api/api/v1/statuses/[id]/source"
),
"/api/v1/statuses/[id]/unfavourite": await import(
"./server/api/api/v1/statuses/[id]/unfavourite"
),
"/api/v1/statuses/[id]/unpin": await import(
"./server/api/api/v1/statuses/[id]/unpin"
),
"/api/v1/statuses/[id]/unreblog": await import(
"./server/api/api/v1/statuses/[id]/unreblog"
),
"/media/[id]": await import("./server/api/media/[id]/index"),
"/oauth/callback/[issuer]": await import(
"./server/api/oauth/callback/[issuer]/index"
),
"/object/[uuid]": await import("./server/api/object/[uuid]/index"),
"/users/[uuid]": await import("./server/api/users/[uuid]/index"),
"/users/[uuid]/inbox": await import(
"./server/api/users/[uuid]/inbox/index"
),
"/users/[uuid]/outbox": await import(
"./server/api/users/[uuid]/outbox/index"
),
"/[...404]": await import("./server/api/[...404]"),
};
// Returns the route filesystem path when given a URL // Returns the route filesystem path when given a URL
export const routeMatcher = new Bun.FileSystemRouter({ export const routeMatcher = new Bun.FileSystemRouter({
@ -164,19 +95,15 @@ export const routeMatcher = new Bun.FileSystemRouter({
dir: process.cwd() + "/server/api", dir: process.cwd() + "/server/api",
}); });
export const matchRoute = <T = Record<string, never>>(url: string) => { export const matchRoute = async <T = Record<string, never>>(url: string) => {
const route = routeMatcher.match(url); const route = routeMatcher.match(url);
if (!route) return { file: null, matchedRoute: null }; if (!route) return { file: null, matchedRoute: null };
return { return {
// @ts-expect-error TypeScript parses this as a defined object instead of an arbitrarily editable route file file: (await import(rawRoutes[route.name])) as {
file: rawRoutes[route.name] as Promise< default: RouteHandler<T>;
| { meta: APIRouteMeta;
meta: APIRouteMeta; },
default: RouteHandler<T>;
}
| undefined
>,
matchedRoute: route, matchedRoute: route,
}; };
}; };

View file

@ -5,6 +5,7 @@ import type { ConfigManager, ConfigType } from "config-manager";
import type { LogManager, MultiLogManager } from "log-manager"; import type { LogManager, MultiLogManager } from "log-manager";
import { LogLevel } from "log-manager"; import { LogLevel } from "log-manager";
import { RequestParser } from "request-parser"; import { RequestParser } from "request-parser";
import { matchRoute } from "~routes";
export const createServer = ( export const createServer = (
config: ConfigType, config: ConfigType,
@ -45,6 +46,55 @@ export const createServer = (
} }
} }
if (config.http.bait.enabled) {
// Check for bait IPs
for (const ip of config.http.bait.bait_ips) {
try {
if (matches(ip, request_ip)) {
const file = Bun.file(
config.http.bait.send_file ||
"./pages/beemovie.txt"
);
if (await file.exists()) {
return new Response(file);
} else {
await logger.log(
LogLevel.ERROR,
"Server.Bait",
`Bait file not found: ${config.http.bait.send_file}`
);
}
}
} catch (e) {
console.error(
`[-] Error while parsing bait IP "${ip}" `
);
throw e;
}
}
// Check for bait user agents (regex)
for (const agent of config.http.bait.bait_user_agents) {
console.log(agent);
if (new RegExp(agent).test(ua)) {
const file = Bun.file(
config.http.bait.send_file || "./pages/beemovie.txt"
);
if (await file.exists()) {
return new Response(file);
} else {
await logger.log(
LogLevel.ERROR,
"Server.Bait",
`Bait file not found: ${config.http.bait.send_file}`
);
}
}
}
}
if (config.logging.log_requests) { if (config.logging.log_requests) {
await logger.logRequest( await logger.logRequest(
req, req,
@ -57,13 +107,11 @@ export const createServer = (
return jsonResponse({}); return jsonResponse({});
} }
// If it isn't dynamically imported, it causes trouble with imports const { file: filePromise, matchedRoute } = await matchRoute(
// There shouldn't be a performance hit after bundling right? req.url
const { matchRoute } = await import("~routes"); );
const { file: filePromise, matchedRoute } = matchRoute(req.url); const file = filePromise;
const file = await filePromise;
if (matchedRoute && file == undefined) { if (matchedRoute && file == undefined) {
await logger.log( await logger.log(

View file

@ -64,14 +64,14 @@ export default apiRoute<{
ALLOWED_ATTR: [], ALLOWED_ATTR: [],
}); });
if (!user.source) { /* if (!user.source) {
user.source = { user.source = {
privacy: "public", privacy: "public",
sensitive: false, sensitive: false,
language: "en", language: "en",
note: "", note: "",
}; };
} } */
let mediaManager: MediaBackend; let mediaManager: MediaBackend;
@ -132,7 +132,7 @@ export default apiRoute<{
return errorResponse("Bio contains blocked words", 422); return errorResponse("Bio contains blocked words", 422);
} }
(user.source as unknown as APISource).note = sanitizedNote; (user.source as APISource).note = sanitizedNote;
// TODO: Convert note to HTML // TODO: Convert note to HTML
user.note = await convertTextToHtml(sanitizedNote); user.note = await convertTextToHtml(sanitizedNote);
} }
@ -150,8 +150,7 @@ export default apiRoute<{
); );
} }
// @ts-expect-error Prisma Typescript doesn't include relations (user.source as APISource).privacy = source_privacy;
user.source.privacy = source_privacy;
} }
if (source_sensitive && user.source) { if (source_sensitive && user.source) {
@ -160,8 +159,7 @@ export default apiRoute<{
return errorResponse("Sensitive must be a boolean", 422); return errorResponse("Sensitive must be a boolean", 422);
} }
// @ts-expect-error Prisma Typescript doesn't include relations (user.source as APISource).sensitive = source_sensitive === "true";
user.source.sensitive = source_sensitive === "true";
} }
if (source_language && user.source) { if (source_language && user.source) {
@ -172,8 +170,7 @@ export default apiRoute<{
); );
} }
// @ts-expect-error Prisma Typescript doesn't include relations (user.source as APISource).language = source_language;
user.source.language = source_language;
} }
if (avatar) { if (avatar) {

View file

@ -22,7 +22,7 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"types": [ "types": [
"bun-types" // add Bun global "bun-types", // add Bun global
], ],
"paths": { "paths": {
"@*": ["./utils/*"], "@*": ["./utils/*"],
@ -32,9 +32,11 @@
}, },
"include": [ "include": [
"*.ts", "*.ts",
"*.d.ts",
"*.vue", "*.vue",
"**/*.ts", "**/*.ts",
"**/*.d.ts",
"**/*.vue", "**/*.vue",
"server/api/.well-known/**/*.ts" "server/api/.well-known/**/*.ts",
] ]
} }

28
types.d.ts vendored Normal file
View file

@ -0,0 +1,28 @@
import type { LysandObject } from "@prisma/client";
import type { APIAccount } from "~types/entities/account";
import type { APIField } from "~types/entities/field";
import type { ContentFormat } from "~types/lysand/Object";
declare namespace global {
namespace PrismaJson {
type InstanceLogo = ContentFormat[];
type ObjectData = LysandObject;
type ObjectExtensions = LysandObject["extensions"];
interface UserEndpoints {
inbox: string;
liked: string;
outbox: string;
disliked: string;
featured: string;
followers: string;
following: string;
}
interface UserSource {
note: string;
fields: APIField[];
privacy: APIAccount["privacy"];
language: string;
sensitive: boolean;
}
}
}

View file

@ -41,7 +41,7 @@ export interface Collection<T> {
items: T[]; items: T[];
} }
export interface User extends LysandObjectType { export interface LysandUser extends LysandObjectType {
type: "User"; type: "User";
bio: ContentFormat[]; bio: ContentFormat[];