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
import chalk from "chalk";
import { rm, cp, mkdir, exists } from "fs/promises";
console.log(
chalk.red(
"Warning: Build is currently broken due to a bug in Bun causing it not to parse dynamic imports"
)
);
import { rawRoutes } from "~routes";
if (!(await exists("./pages/dist"))) {
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() + "/prisma.ts",
process.cwd() + "/cli.ts",
// Force Bun to include endpoints
...Object.values(rawRoutes),
],
outdir: process.cwd() + "/dist",
target: "bun",
@ -47,4 +43,7 @@ await cp(process.cwd() + "/pages/dist", process.cwd() + "/dist/pages/", {
recursive: true,
});
// Copy the Bee Movie script from pages
await cp(process.cwd() + "/pages/beemovie.txt", process.cwd() + "/dist/pages/");
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 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([
new CliCommand<{
username: string;
@ -270,7 +292,7 @@ const cliBuilder = new CliBuilder([
},
],
async (instance: CliCommand, args) => {
const { admins, help } = args;
const { admins, help, fields = [] } = args;
if (help) {
instance.displayHelp();
@ -281,16 +303,21 @@ const cliBuilder = new CliBuilder([
console.log(`${chalk.red(``)} Invalid format`);
return 1;
}
const users = await client.user.findMany({
where: {
isAdmin: admins || undefined,
},
take: args.limit ?? 200,
include: {
instance: true,
},
});
const users = filterObjects(
await client.user.findMany({
where: {
isAdmin: admins || undefined,
},
take: args.limit ?? 200,
include: {
instance:
fields.length == 0
? true
: fields.includes("instance"),
},
}),
fields
);
if (args.redact) {
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") {
console.log(JSON.stringify(users, null, 4));
return 0;
@ -327,27 +341,20 @@ const cliBuilder = new CliBuilder([
`${chalk.green(``)} Found ${chalk.blue(users.length)} users (limit ${args.limit ?? 200})`
);
const tableHead = {
username: chalk.white(chalk.bold("Username")),
email: chalk.white(chalk.bold("Email")),
displayName: chalk.white(chalk.bold("Display Name")),
isAdmin: chalk.white(chalk.bold("Admin?")),
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);
for (const key of keys) {
if (!args.fields.includes(key)) {
// @ts-expect-error This is fine
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete tableHead[key];
}
}
}
const tableHead = filterObjects(
[
{
username: chalk.white(chalk.bold("Username")),
email: chalk.white(chalk.bold("Email")),
displayName: chalk.white(chalk.bold("Display Name")),
isAdmin: chalk.white(chalk.bold("Admin?")),
instance: chalk.white(chalk.bold("Instance URL")),
createdAt: chalk.white(chalk.bold("Created At")),
id: chalk.white(chalk.bold("Internal UUID")),
},
],
fields
)[0];
const table = new Table({
head: Object.values(tableHead),
@ -364,7 +371,7 @@ const cliBuilder = new CliBuilder([
chalk.blue(
user.instance ? user.instance.base_url : "Local"
),
createdAt: () => chalk.blue(user.createdAt.toISOString()),
createdAt: () => chalk.blue(user.createdAt?.toISOString()),
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);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
process.exit(Number(exitCode ?? 0));
process.exit(Number(exitCode == undefined ? 0 : exitCode));

View file

@ -1,5 +1,5 @@
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 type { User } from "@prisma/client";
import { Prisma } from "@prisma/client";

View file

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

View file

@ -55,6 +55,12 @@ export interface ConfigType {
bind_port: string;
banned_ips: string[];
banned_user_agents: string[];
bait: {
enabled: boolean;
send_file?: string;
bait_ips: string[];
bait_user_agents: string[];
};
};
instance: {
@ -181,6 +187,12 @@ export const configDefaults: ConfigType = {
base_url: "http://lysand.localhost:8000",
banned_ips: [],
banned_user_agents: [],
bait: {
enabled: false,
send_file: "",
bait_ips: [],
bait_user_agents: [],
},
},
database: {
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"]
}
generator json {
provider = "prisma-json-types-generator"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
@ -42,6 +46,7 @@ model Instance {
base_url String
name String
version String
/// [InstanceLogo]
logo Json
emojis Emoji[] // One to many relation with Emoji
statuses Status[] // One to many relation with Status
@ -66,7 +71,9 @@ model LysandObject {
created_at DateTime @default(now())
author LysandObject? @relation("LysandObjectToAuthor", fields: [authorId], references: [id], onDelete: Cascade)
authorId String? @db.Uuid
/// [ObjectData]
extra_data Json
/// [ObjectExtensions]
extensions Json
children LysandObject[] @relation("LysandObjectToAuthor")
}
@ -227,7 +234,9 @@ model User {
email String? @unique // Nullable
note String @default("")
isAdmin Boolean @default(false)
/// [UserEndpoints]
endpoints Json? // Nullable
/// [UserSource]
source Json
avatar 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
// node_modules in production
export const rawRoutes = {
"/api/v1/accounts": await import("./server/api/api/v1/accounts"),
"/api/v1/accounts/familiar_followers": await import(
"./server/api/api/v1/accounts/familiar_followers/index"
),
"/api/v1/accounts/relationships": await import(
"./server/api/api/v1/accounts/relationships/index"
),
"/api/v1/accounts/search": await import(
"./server/api/api/v1/accounts/search/index"
),
"/api/v1/accounts/update_credentials": await import(
"./server/api/api/v1/accounts/update_credentials/index"
),
"/api/v1/accounts/verify_credentials": await import(
"./server/api/api/v1/accounts/verify_credentials/index"
),
"/api/v1/apps": await import("./server/api/api/v1/apps/index"),
"/api/v1/apps/verify_credentials": await import(
"./server/api/api/v1/apps/verify_credentials/index"
),
"/api/v1/blocks": await import("./server/api/api/v1/blocks/index"),
"/api/v1/custom_emojis": await import(
"./server/api/api/v1/custom_emojis/index"
),
"/api/v1/favourites": await import("./server/api/api/v1/favourites/index"),
"/api/v1/follow_requests": await import(
"./server/api/api/v1/follow_requests/index"
),
"/api/v1/instance": await import("./server/api/api/v1/instance/index"),
"/api/v1/media": await import("./server/api/api/v1/media/index"),
"/api/v1/mutes": await import("./server/api/api/v1/mutes/index"),
"/api/v1/notifications": await import(
"./server/api/api/v1/notifications/index"
),
"/api/v1/profile/avatar": await import(
"./server/api/api/v1/profile/avatar"
),
"/api/v1/profile/header": await import(
"./server/api/api/v1/profile/header"
),
"/api/v1/statuses": await import("./server/api/api/v1/statuses/index"),
"/api/v1/timelines/home": await import(
"./server/api/api/v1/timelines/home"
),
"/api/v1/timelines/public": await import(
"./server/api/api/v1/timelines/public"
),
"/api/v2/media": await import("./server/api/api/v2/media/index"),
"/api/v2/search": await import("./server/api/api/v2/search/index"),
"/auth/login": await import("./server/api/auth/login/index"),
"/nodeinfo/2.0": await import("./server/api/nodeinfo/2.0/index"),
"/oauth/authorize-external": await import(
"./server/api/oauth/authorize-external/index"
),
"/oauth/providers": await import("./server/api/oauth/providers/index"),
"/oauth/token": await import("./server/api/oauth/token/index"),
"/api/v1/accounts/[id]": await import(
"./server/api/api/v1/accounts/[id]/index"
),
"/api/v1/accounts/[id]/block": await import(
"./server/api/api/v1/accounts/[id]/block"
),
"/api/v1/accounts/[id]/follow": await import(
"./server/api/api/v1/accounts/[id]/follow"
),
"/api/v1/accounts/[id]/followers": await import(
"./server/api/api/v1/accounts/[id]/followers"
),
"/api/v1/accounts/[id]/following": await import(
"./server/api/api/v1/accounts/[id]/following"
),
"/api/v1/accounts/[id]/mute": await import(
"./server/api/api/v1/accounts/[id]/mute"
),
"/api/v1/accounts/[id]/note": await import(
"./server/api/api/v1/accounts/[id]/note"
),
"/api/v1/accounts/[id]/pin": await import(
"./server/api/api/v1/accounts/[id]/pin"
),
"/api/v1/accounts/[id]/remove_from_followers": await import(
"./server/api/api/v1/accounts/[id]/remove_from_followers"
),
"/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]"),
};
"/api/v1/accounts": "./server/api/api/v1/accounts",
"/api/v1/accounts/familiar_followers":
"+api/v1/accounts/familiar_followers/index",
"/api/v1/accounts/relationships":
"./server/api/api/v1/accounts/relationships/index",
"/api/v1/accounts/search": "./server/api/api/v1/accounts/search/index",
"/api/v1/accounts/update_credentials":
"./server/api/api/v1/accounts/update_credentials/index",
"/api/v1/accounts/verify_credentials":
"./server/api/api/v1/accounts/verify_credentials/index",
"/api/v1/apps": "./server/api/api/v1/apps/index",
"/api/v1/apps/verify_credentials":
"./server/api/api/v1/apps/verify_credentials/index",
"/api/v1/blocks": "./server/api/api/v1/blocks/index",
"/api/v1/custom_emojis": "./server/api/api/v1/custom_emojis/index",
"/api/v1/favourites": "./server/api/api/v1/favourites/index",
"/api/v1/follow_requests": "./server/api/api/v1/follow_requests/index",
"/api/v1/instance": "./server/api/api/v1/instance/index",
"/api/v1/media": "./server/api/api/v1/media/index",
"/api/v1/mutes": "./server/api/api/v1/mutes/index",
"/api/v1/notifications": "./server/api/api/v1/notifications/index",
"/api/v1/profile/avatar": "./server/api/api/v1/profile/avatar",
"/api/v1/profile/header": "./server/api/api/v1/profile/header",
"/api/v1/statuses": "./server/api/api/v1/statuses/index",
"/api/v1/timelines/home": "./server/api/api/v1/timelines/home",
"/api/v1/timelines/public": "./server/api/api/v1/timelines/public",
"/api/v2/media": "./server/api/api/v2/media/index",
"/api/v2/search": "./server/api/api/v2/search/index",
"/auth/login": "./server/api/auth/login/index",
"/nodeinfo/2.0": "./server/api/nodeinfo/2.0/index",
"/oauth/authorize-external": "./server/api/oauth/authorize-external/index",
"/oauth/providers": "./server/api/oauth/providers/index",
"/oauth/token": "./server/api/oauth/token/index",
"/api/v1/accounts/[id]": "./server/api/api/v1/accounts/[id]/index",
"/api/v1/accounts/[id]/block": "./server/api/api/v1/accounts/[id]/block",
"/api/v1/accounts/[id]/follow": "./server/api/api/v1/accounts/[id]/follow",
"/api/v1/accounts/[id]/followers":
"./server/api/api/v1/accounts/[id]/followers",
"/api/v1/accounts/[id]/following":
"./server/api/api/v1/accounts/[id]/following",
"/api/v1/accounts/[id]/mute": "./server/api/api/v1/accounts/[id]/mute",
"/api/v1/accounts/[id]/note": "./server/api/api/v1/accounts/[id]/note",
"/api/v1/accounts/[id]/pin": "./server/api/api/v1/accounts/[id]/pin",
"/api/v1/accounts/[id]/remove_from_followers":
"./server/api/api/v1/accounts/[id]/remove_from_followers",
"/api/v1/accounts/[id]/statuses":
"./server/api/api/v1/accounts/[id]/statuses",
"/api/v1/accounts/[id]/unblock":
"./server/api/api/v1/accounts/[id]/unblock",
"/api/v1/accounts/[id]/unfollow":
"./server/api/api/v1/accounts/[id]/unfollow",
"/api/v1/accounts/[id]/unmute": "./server/api/api/v1/accounts/[id]/unmute",
"/api/v1/accounts/[id]/unpin": "./server/api/api/v1/accounts/[id]/unpin",
"/api/v1/follow_requests/[account_id]/authorize":
"./server/api/api/v1/follow_requests/[account_id]/authorize",
"/api/v1/follow_requests/[account_id]/reject":
"./server/api/api/v1/follow_requests/[account_id]/reject",
"/api/v1/media/[id]": "./server/api/api/v1/media/[id]/index",
"/api/v1/statuses/[id]": "./server/api/api/v1/statuses/[id]/index",
"/api/v1/statuses/[id]/context":
"./server/api/api/v1/statuses/[id]/context",
"/api/v1/statuses/[id]/favourite":
"./server/api/api/v1/statuses/[id]/favourite",
"/api/v1/statuses/[id]/favourited_by":
"./server/api/api/v1/statuses/[id]/favourited_by",
"/api/v1/statuses/[id]/pin": "./server/api/api/v1/statuses/[id]/pin",
"/api/v1/statuses/[id]/reblog": "./server/api/api/v1/statuses/[id]/reblog",
"/api/v1/statuses/[id]/reblogged_by":
"./server/api/api/v1/statuses/[id]/reblogged_by",
"/api/v1/statuses/[id]/source": "./server/api/api/v1/statuses/[id]/source",
"/api/v1/statuses/[id]/unfavourite":
"./server/api/api/v1/statuses/[id]/unfavourite",
"/api/v1/statuses/[id]/unpin": "./server/api/api/v1/statuses/[id]/unpin",
"/api/v1/statuses/[id]/unreblog":
"./server/api/api/v1/statuses/[id]/unreblog",
"/media/[id]": "./server/api/media/[id]/index",
"/oauth/callback/[issuer]": "./server/api/oauth/callback/[issuer]/index",
"/object/[uuid]": "./server/api/object/[uuid]/index",
"/users/[uuid]": "./server/api/users/[uuid]/index",
"/users/[uuid]/inbox": "./server/api/users/[uuid]/inbox/index",
"/users/[uuid]/outbox": "./server/api/users/[uuid]/outbox/index",
"/[...404]": "./server/api/[...404]",
} as Record<string, string>;
// Returns the route filesystem path when given a URL
export const routeMatcher = new Bun.FileSystemRouter({
@ -164,19 +95,15 @@ export const routeMatcher = new Bun.FileSystemRouter({
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);
if (!route) return { file: null, matchedRoute: null };
return {
// @ts-expect-error TypeScript parses this as a defined object instead of an arbitrarily editable route file
file: rawRoutes[route.name] as Promise<
| {
meta: APIRouteMeta;
default: RouteHandler<T>;
}
| undefined
>,
file: (await import(rawRoutes[route.name])) as {
default: RouteHandler<T>;
meta: APIRouteMeta;
},
matchedRoute: route,
};
};

View file

@ -5,6 +5,7 @@ import type { ConfigManager, ConfigType } from "config-manager";
import type { LogManager, MultiLogManager } from "log-manager";
import { LogLevel } from "log-manager";
import { RequestParser } from "request-parser";
import { matchRoute } from "~routes";
export const createServer = (
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) {
await logger.logRequest(
req,
@ -57,13 +107,11 @@ export const createServer = (
return jsonResponse({});
}
// If it isn't dynamically imported, it causes trouble with imports
// There shouldn't be a performance hit after bundling right?
const { matchRoute } = await import("~routes");
const { file: filePromise, matchedRoute } = await matchRoute(
req.url
);
const { file: filePromise, matchedRoute } = matchRoute(req.url);
const file = await filePromise;
const file = filePromise;
if (matchedRoute && file == undefined) {
await logger.log(

View file

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

View file

@ -22,7 +22,7 @@
"experimentalDecorators": true,
"verbatimModuleSyntax": true,
"types": [
"bun-types" // add Bun global
"bun-types", // add Bun global
],
"paths": {
"@*": ["./utils/*"],
@ -32,9 +32,11 @@
},
"include": [
"*.ts",
"*.d.ts",
"*.vue",
"**/*.ts",
"**/*.d.ts",
"**/*.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[];
}
export interface User extends LysandObjectType {
export interface LysandUser extends LysandObjectType {
type: "User";
bio: ContentFormat[];