mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 00:18:19 +01:00
refactor: 🚨 Turn every linter rule on and fix issues (there were a LOT :3)
This commit is contained in:
parent
2e98859153
commit
a1e02d0d78
48
biome.json
48
biome.json
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true,
|
||||
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
|
||||
|
|
@ -7,7 +7,48 @@
|
|||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
"all": true,
|
||||
"correctness": {
|
||||
"noNodejsModules": "off"
|
||||
},
|
||||
"style": {
|
||||
"noDefaultExport": "off",
|
||||
"noParameterProperties": "off",
|
||||
"noNamespaceImport": "off",
|
||||
"useFilenamingConvention": "off",
|
||||
"useNamingConvention": {
|
||||
"level": "warn",
|
||||
"options": {
|
||||
"requireAscii": false,
|
||||
"strictCase": false,
|
||||
"conventions": [
|
||||
{
|
||||
"selector": {
|
||||
"kind": "typeProperty"
|
||||
},
|
||||
"formats": [
|
||||
"camelCase",
|
||||
"CONSTANT_CASE",
|
||||
"PascalCase",
|
||||
"snake_case"
|
||||
]
|
||||
},
|
||||
{
|
||||
"selector": {
|
||||
"kind": "objectLiteralProperty",
|
||||
"scope": "any"
|
||||
},
|
||||
"formats": [
|
||||
"camelCase",
|
||||
"CONSTANT_CASE",
|
||||
"PascalCase",
|
||||
"snake_case"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
|
||||
},
|
||||
|
|
@ -16,5 +57,8 @@
|
|||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
|
||||
},
|
||||
"javascript": {
|
||||
"globals": ["Bun", "HTMLRewriter"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
build.ts
8
build.ts
|
|
@ -1,5 +1,4 @@
|
|||
import { $ } from "bun";
|
||||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
import { routes } from "~/routes";
|
||||
|
||||
|
|
@ -21,7 +20,6 @@ await Bun.build({
|
|||
external: ["unzipit"],
|
||||
}).then((output) => {
|
||||
if (!output.success) {
|
||||
console.log(output.logs);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
|
@ -56,9 +54,3 @@ await $`cp package.json dist/package.json`;
|
|||
await $`cp cli/theme.json dist/cli/theme.json`;
|
||||
|
||||
buildSpinner.stop();
|
||||
|
||||
console.log(
|
||||
`${chalk.green("✓")} Built project. You can now run it with ${chalk.green(
|
||||
"bun run dist/index.js",
|
||||
)}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { consoleLogger } from "@/loggers";
|
|||
import { Command } from "@oclif/core";
|
||||
import { setupDatabase } from "~/drizzle/db";
|
||||
|
||||
export abstract class BaseCommand<T extends typeof Command> extends Command {
|
||||
export abstract class BaseCommand<_T extends typeof Command> extends Command {
|
||||
protected async init(): Promise<void> {
|
||||
await super.init();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Args } from "@oclif/core";
|
|||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
import { BaseCommand } from "~/cli/base";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { getUrl } from "~/database/entities/attachment";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Emojis } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { lookup } from "mime-types";
|
|||
import ora from "ora";
|
||||
import { unzip } from "unzipit";
|
||||
import { BaseCommand } from "~/cli/base";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { getUrl } from "~/database/entities/attachment";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Emojis } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ export default class Start extends BaseCommand<typeof Start> {
|
|||
public async run(): Promise<void> {
|
||||
const { flags } = await this.parse(Start);
|
||||
|
||||
const numCPUs = flags["all-threads"] ? os.cpus().length : flags.threads;
|
||||
const numCpUs = flags["all-threads"] ? os.cpus().length : flags.threads;
|
||||
|
||||
// Check if index is a JS or TS file (depending on the environment)
|
||||
const index = (await Bun.file("index.ts").exists())
|
||||
? "index.ts"
|
||||
: "index.js";
|
||||
|
||||
for (let i = 0; i < numCPUs; i++) {
|
||||
for (let i = 0; i < numCpUs; i++) {
|
||||
const args = ["bun", index];
|
||||
if (i !== 0 || flags.silent) {
|
||||
args.push("--silent");
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ export default class UserCreate extends BaseCommand<typeof UserCreate> {
|
|||
),
|
||||
);
|
||||
|
||||
if (!flags.format && !flags["set-password"]) {
|
||||
if (!(flags.format || flags["set-password"])) {
|
||||
const link = "";
|
||||
|
||||
this.log(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import { db } from "~/drizzle/db";
|
||||
import type { Applications } from "~/drizzle/schema";
|
||||
import type { Application as APIApplication } from "~/types/mastodon/application";
|
||||
import type { Application as apiApplication } from "~/types/mastodon/application";
|
||||
|
||||
export type Application = InferSelectModel<typeof Applications>;
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ export const getFromToken = async (
|
|||
* Converts this application to an API application.
|
||||
* @returns The API application representation of this application.
|
||||
*/
|
||||
export const applicationToAPI = (app: Application): APIApplication => {
|
||||
export const applicationToApi = (app: Application): apiApplication => {
|
||||
return {
|
||||
name: app.name,
|
||||
website: app.website,
|
||||
|
|
@ -2,7 +2,7 @@ import { MediaBackendType } from "media-manager";
|
|||
import type { Config } from "~/packages/config-manager";
|
||||
|
||||
export const getUrl = (name: string, config: Config) => {
|
||||
if (config.media.backend === MediaBackendType.LOCAL) {
|
||||
if (config.media.backend === MediaBackendType.Local) {
|
||||
return new URL(`/media/${name}`, config.http.base_url).toString();
|
||||
}
|
||||
if (config.media.backend === MediaBackendType.S3) {
|
||||
|
|
@ -4,8 +4,8 @@ import type { EntityValidator } from "@lysand-org/federation";
|
|||
import { type InferSelectModel, and, eq } from "drizzle-orm";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Emojis, Instances } from "~/drizzle/schema";
|
||||
import type { Emoji as APIEmoji } from "~/types/mastodon/emoji";
|
||||
import { addInstanceIfNotExists } from "./Instance";
|
||||
import type { Emoji as apiEmoji } from "~/types/mastodon/emoji";
|
||||
import { addInstanceIfNotExists } from "./instance";
|
||||
|
||||
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
|
||||
instance: InferSelectModel<typeof Instances> | null;
|
||||
|
|
@ -18,7 +18,9 @@ export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
|
|||
*/
|
||||
export const parseEmojis = async (text: string) => {
|
||||
const matches = text.match(emojiValidatorWithColons);
|
||||
if (!matches) return [];
|
||||
if (!matches) {
|
||||
return [];
|
||||
}
|
||||
const emojis = await db.query.Emojis.findMany({
|
||||
where: (emoji, { eq, or }) =>
|
||||
or(
|
||||
|
|
@ -56,11 +58,12 @@ export const fetchEmoji = async (
|
|||
)
|
||||
.limit(1);
|
||||
|
||||
if (existingEmoji[0])
|
||||
if (existingEmoji[0]) {
|
||||
return {
|
||||
...existingEmoji[0].Emojis,
|
||||
instance: existingEmoji[0].Instances,
|
||||
};
|
||||
}
|
||||
|
||||
const foundInstance = host ? await addInstanceIfNotExists(host) : null;
|
||||
|
||||
|
|
@ -90,7 +93,7 @@ export const fetchEmoji = async (
|
|||
* Converts the emoji to an APIEmoji object.
|
||||
* @returns The APIEmoji object.
|
||||
*/
|
||||
export const emojiToAPI = (emoji: EmojiWithInstance): APIEmoji => {
|
||||
export const emojiToApi = (emoji: EmojiWithInstance): apiEmoji => {
|
||||
return {
|
||||
// @ts-expect-error ID is not in regular Mastodon API
|
||||
id: emoji.id,
|
||||
|
|
@ -7,7 +7,7 @@ import { config } from "config-manager";
|
|||
import type { User } from "~/packages/database-interface/user";
|
||||
import { LogLevel, LogManager } from "~/packages/log-manager";
|
||||
|
||||
export const localObjectURI = (id: string) =>
|
||||
export const localObjectUri = (id: string) =>
|
||||
new URL(`/objects/${id}`, config.http.base_url).toString();
|
||||
|
||||
export const objectToInboxRequest = async (
|
||||
|
|
@ -52,14 +52,14 @@ export const objectToInboxRequest = async (
|
|||
|
||||
// Log public key
|
||||
new LogManager(Bun.stdout).log(
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.Debug,
|
||||
"Inbox.Signature",
|
||||
`Sender public key: ${author.data.publicKey}`,
|
||||
);
|
||||
|
||||
// Log signed string
|
||||
new LogManager(Bun.stdout).log(
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.Debug,
|
||||
"Inbox.Signature",
|
||||
`Signed string:\n${signedString}`,
|
||||
);
|
||||
|
|
@ -19,9 +19,9 @@ export const addInstanceIfNotExists = async (url: string) => {
|
|||
where: (instance, { eq }) => eq(instance.baseUrl, host),
|
||||
});
|
||||
|
||||
if (found) return found;
|
||||
|
||||
console.log(`Fetching instance metadata for ${origin}`);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
|
||||
// Fetch the instance configuration
|
||||
const metadata = (await fetch(new URL("/.well-known/lysand", origin)).then(
|
||||
|
|
@ -3,14 +3,14 @@ import { db } from "~/drizzle/db";
|
|||
import type { Notifications } from "~/drizzle/schema";
|
||||
import { Note } from "~/packages/database-interface/note";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
import type { Notification as APINotification } from "~/types/mastodon/notification";
|
||||
import type { StatusWithRelations } from "./Status";
|
||||
import type { Notification as apiNotification } from "~/types/mastodon/notification";
|
||||
import type { StatusWithRelations } from "./status";
|
||||
import {
|
||||
type UserWithRelations,
|
||||
transformOutputToUserWithRelations,
|
||||
userExtrasTemplate,
|
||||
userRelations,
|
||||
} from "./User";
|
||||
} from "./user";
|
||||
|
||||
export type Notification = InferSelectModel<typeof Notifications>;
|
||||
|
||||
|
|
@ -48,17 +48,17 @@ export const findManyNotifications = async (
|
|||
);
|
||||
};
|
||||
|
||||
export const notificationToAPI = async (
|
||||
export const notificationToApi = async (
|
||||
notification: NotificationWithRelations,
|
||||
): Promise<APINotification> => {
|
||||
): Promise<apiNotification> => {
|
||||
const account = new User(notification.account);
|
||||
return {
|
||||
account: account.toAPI(),
|
||||
account: account.toApi(),
|
||||
created_at: new Date(notification.createdAt).toISOString(),
|
||||
id: notification.id,
|
||||
type: notification.type,
|
||||
status: notification.status
|
||||
? await new Note(notification.status).toAPI(account)
|
||||
? await new Note(notification.status).toApi(account)
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@ import type { InferSelectModel } from "drizzle-orm";
|
|||
import { db } from "~/drizzle/db";
|
||||
import { Relationships } from "~/drizzle/schema";
|
||||
import type { User } from "~/packages/database-interface/user";
|
||||
import type { Relationship as APIRelationship } from "~/types/mastodon/relationship";
|
||||
import type { Relationship as apiRelationship } from "~/types/mastodon/relationship";
|
||||
|
||||
export type Relationship = InferSelectModel<typeof Relationships> & {
|
||||
requestedBy: boolean;
|
||||
|
|
@ -76,7 +76,7 @@ export const checkForBidirectionalRelationships = async (
|
|||
* Converts the relationship to an API-friendly format.
|
||||
* @returns The API-friendly relationship.
|
||||
*/
|
||||
export const relationshipToAPI = (rel: Relationship): APIRelationship => {
|
||||
export const relationshipToApi = (rel: Relationship): apiRelationship => {
|
||||
return {
|
||||
blocked_by: rel.blockedBy,
|
||||
blocking: rel.blocking,
|
||||
|
|
@ -36,9 +36,9 @@ import {
|
|||
import type { Note } from "~/packages/database-interface/note";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
import { LogLevel } from "~/packages/log-manager";
|
||||
import type { Application } from "./Application";
|
||||
import type { EmojiWithInstance } from "./Emoji";
|
||||
import { objectToInboxRequest } from "./Federation";
|
||||
import type { Application } from "./application";
|
||||
import type { EmojiWithInstance } from "./emoji";
|
||||
import { objectToInboxRequest } from "./federation";
|
||||
import {
|
||||
type UserWithInstance,
|
||||
type UserWithRelations,
|
||||
|
|
@ -46,7 +46,7 @@ import {
|
|||
transformOutputToUserWithRelations,
|
||||
userExtrasTemplate,
|
||||
userRelations,
|
||||
} from "./User";
|
||||
} from "./user";
|
||||
|
||||
export type Status = InferSelectModel<typeof Notes>;
|
||||
|
||||
|
|
@ -258,7 +258,9 @@ export const findManyNotes = async (
|
|||
*/
|
||||
export const parseTextMentions = async (text: string): Promise<User[]> => {
|
||||
const mentionedPeople = [...text.matchAll(mentionValidator)] ?? [];
|
||||
if (mentionedPeople.length === 0) return [];
|
||||
if (mentionedPeople.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const baseUrlHost = new URL(config.http.base_url).host;
|
||||
|
||||
|
|
@ -287,11 +289,13 @@ export const parseTextMentions = async (text: string): Promise<User[]> => {
|
|||
|
||||
const notFoundRemoteUsers = mentionedPeople.filter(
|
||||
(person) =>
|
||||
!isLocal(person?.[2]) &&
|
||||
!foundUsers.find(
|
||||
(user) =>
|
||||
user.username === person?.[1] &&
|
||||
user.baseUrl === person?.[2],
|
||||
!(
|
||||
isLocal(person?.[2]) ||
|
||||
foundUsers.find(
|
||||
(user) =>
|
||||
user.username === person?.[1] &&
|
||||
user.baseUrl === person?.[2],
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -320,7 +324,7 @@ export const parseTextMentions = async (text: string): Promise<User[]> => {
|
|||
return finalList;
|
||||
};
|
||||
|
||||
export const replaceTextMentions = async (text: string, mentions: User[]) => {
|
||||
export const replaceTextMentions = (text: string, mentions: User[]) => {
|
||||
let finalText = text;
|
||||
for (const mention of mentions) {
|
||||
const user = mention.data;
|
||||
|
|
@ -412,7 +416,7 @@ export const markdownParse = async (content: string) => {
|
|||
return (await getMarkdownRenderer()).render(content);
|
||||
};
|
||||
|
||||
export const getMarkdownRenderer = async () => {
|
||||
export const getMarkdownRenderer = () => {
|
||||
const renderer = MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
|
|
@ -448,12 +452,12 @@ export const federateNote = async (note: Note) => {
|
|||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.Debug,
|
||||
"Federation.Status",
|
||||
await response.text(),
|
||||
);
|
||||
dualLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Federation.Status",
|
||||
`Failed to federate status ${note.data.id} to ${user.getUri()}`,
|
||||
);
|
||||
|
|
@ -5,7 +5,7 @@ import type { Tokens } from "~/drizzle/schema";
|
|||
* The type of token.
|
||||
*/
|
||||
export enum TokenType {
|
||||
BEARER = "Bearer",
|
||||
Bearer = "Bearer",
|
||||
}
|
||||
|
||||
export type Token = InferSelectModel<typeof Tokens>;
|
||||
|
|
@ -14,15 +14,15 @@ import {
|
|||
} from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
import { LogLevel } from "~/packages/log-manager";
|
||||
import type { Application } from "./Application";
|
||||
import type { EmojiWithInstance } from "./Emoji";
|
||||
import { objectToInboxRequest } from "./Federation";
|
||||
import type { Application } from "./application";
|
||||
import type { EmojiWithInstance } from "./emoji";
|
||||
import { objectToInboxRequest } from "./federation";
|
||||
import {
|
||||
type Relationship,
|
||||
checkForBidirectionalRelationships,
|
||||
createNewRelationship,
|
||||
} from "./Relationship";
|
||||
import type { Token } from "./Token";
|
||||
} from "./relationship";
|
||||
import type { Token } from "./token";
|
||||
|
||||
export type UserType = InferSelectModel<typeof Users>;
|
||||
|
||||
|
|
@ -175,13 +175,13 @@ export const followRequestUser = async (
|
|||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.Debug,
|
||||
"Federation.FollowRequest",
|
||||
await response.text(),
|
||||
);
|
||||
|
||||
dualLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Federation.FollowRequest",
|
||||
`Failed to federate follow request from ${
|
||||
follower.id
|
||||
|
|
@ -230,13 +230,13 @@ export const sendFollowAccept = async (follower: User, followee: User) => {
|
|||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.Debug,
|
||||
"Federation.FollowAccept",
|
||||
await response.text(),
|
||||
);
|
||||
|
||||
dualLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Federation.FollowAccept",
|
||||
`Failed to federate follow accept from ${
|
||||
followee.id
|
||||
|
|
@ -258,13 +258,13 @@ export const sendFollowReject = async (follower: User, followee: User) => {
|
|||
|
||||
if (!response.ok) {
|
||||
dualLogger.log(
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.Debug,
|
||||
"Federation.FollowReject",
|
||||
await response.text(),
|
||||
);
|
||||
|
||||
dualLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Federation.FollowReject",
|
||||
`Failed to federate follow reject from ${
|
||||
followee.id
|
||||
|
|
@ -352,7 +352,9 @@ export const findFirstUser = async (
|
|||
},
|
||||
});
|
||||
|
||||
if (!output) return null;
|
||||
if (!output) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return transformOutputToUserWithRelations(output);
|
||||
};
|
||||
|
|
@ -373,7 +375,9 @@ export const resolveWebFinger = async (
|
|||
.where(and(eq(Users.username, identifier), eq(Instances.baseUrl, host)))
|
||||
.limit(1);
|
||||
|
||||
if (foundUser[0]) return await User.fromId(foundUser[0].Users.id);
|
||||
if (foundUser[0]) {
|
||||
return await User.fromId(foundUser[0].Users.id);
|
||||
}
|
||||
|
||||
const hostWithProtocol = host.startsWith("http") ? host : `https://${host}`;
|
||||
|
||||
|
|
@ -405,7 +409,7 @@ export const resolveWebFinger = async (
|
|||
}[];
|
||||
};
|
||||
|
||||
if (!data.subject || !data.links) {
|
||||
if (!(data.subject && data.links)) {
|
||||
throw new Error(
|
||||
"Invalid WebFinger data (missing subject or links from response)",
|
||||
);
|
||||
|
|
@ -428,13 +432,17 @@ export const resolveWebFinger = async (
|
|||
* @returns The user associated with the given access token.
|
||||
*/
|
||||
export const retrieveUserFromToken = async (
|
||||
access_token: string,
|
||||
accessToken: string,
|
||||
): Promise<User | null> => {
|
||||
if (!access_token) return null;
|
||||
if (!accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const token = await retrieveToken(access_token);
|
||||
const token = await retrieveToken(accessToken);
|
||||
|
||||
if (!token || !token.userId) return null;
|
||||
if (!token?.userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await User.fromId(token.userId);
|
||||
|
||||
|
|
@ -442,12 +450,14 @@ export const retrieveUserFromToken = async (
|
|||
};
|
||||
|
||||
export const retrieveUserAndApplicationFromToken = async (
|
||||
access_token: string,
|
||||
accessToken: string,
|
||||
): Promise<{
|
||||
user: User | null;
|
||||
application: Application | null;
|
||||
}> => {
|
||||
if (!access_token) return { user: null, application: null };
|
||||
if (!accessToken) {
|
||||
return { user: null, application: null };
|
||||
}
|
||||
|
||||
const output = (
|
||||
await db
|
||||
|
|
@ -457,11 +467,13 @@ export const retrieveUserAndApplicationFromToken = async (
|
|||
})
|
||||
.from(Tokens)
|
||||
.leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
|
||||
.where(eq(Tokens.accessToken, access_token))
|
||||
.where(eq(Tokens.accessToken, accessToken))
|
||||
.limit(1)
|
||||
)[0];
|
||||
|
||||
if (!output?.token.userId) return { user: null, application: null };
|
||||
if (!output?.token.userId) {
|
||||
return { user: null, application: null };
|
||||
}
|
||||
|
||||
const user = await User.fromId(output.token.userId);
|
||||
|
||||
|
|
@ -469,13 +481,15 @@ export const retrieveUserAndApplicationFromToken = async (
|
|||
};
|
||||
|
||||
export const retrieveToken = async (
|
||||
access_token: string,
|
||||
accessToken: string,
|
||||
): Promise<Token | null> => {
|
||||
if (!access_token) return null;
|
||||
if (!accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(await db.query.Tokens.findFirst({
|
||||
where: (tokens, { eq }) => eq(tokens.accessToken, access_token),
|
||||
where: (tokens, { eq }) => eq(tokens.accessToken, accessToken),
|
||||
})) ?? null
|
||||
);
|
||||
};
|
||||
|
|
@ -23,13 +23,14 @@ export const setupDatabase = async (
|
|||
if (
|
||||
(e as Error).message ===
|
||||
"Client has already been connected. You cannot reuse a client."
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await logger.logError(LogLevel.CRITICAL, "Database", e as Error);
|
||||
await logger.logError(LogLevel.Critical, "Database", e as Error);
|
||||
|
||||
await logger.log(
|
||||
LogLevel.CRITICAL,
|
||||
LogLevel.Critical,
|
||||
"Database",
|
||||
"Failed to connect to database. Please check your configuration.",
|
||||
);
|
||||
|
|
@ -38,23 +39,23 @@ export const setupDatabase = async (
|
|||
|
||||
// Migrate the database
|
||||
info &&
|
||||
(await logger.log(LogLevel.INFO, "Database", "Migrating database..."));
|
||||
(await logger.log(LogLevel.Info, "Database", "Migrating database..."));
|
||||
|
||||
try {
|
||||
await migrate(db, {
|
||||
migrationsFolder: "./drizzle/migrations",
|
||||
});
|
||||
} catch (e) {
|
||||
await logger.logError(LogLevel.CRITICAL, "Database", e as Error);
|
||||
await logger.logError(LogLevel.Critical, "Database", e as Error);
|
||||
await logger.log(
|
||||
LogLevel.CRITICAL,
|
||||
LogLevel.Critical,
|
||||
"Database",
|
||||
"Failed to migrate database. Please check your configuration.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
info && (await logger.log(LogLevel.INFO, "Database", "Database migrated"));
|
||||
info && (await logger.log(LogLevel.Info, "Database", "Database migrated"));
|
||||
};
|
||||
|
||||
export const db = drizzle(client, { schema });
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
uniqueIndex,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import type { Source as APISource } from "~/types/mastodon/source";
|
||||
import type { Source as apiSource } from "~/types/mastodon/source";
|
||||
|
||||
export const CaptchaChallenges = pgTable("CaptchaChallenges", {
|
||||
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||
|
|
@ -386,7 +386,7 @@ export const Users = pgTable(
|
|||
inbox: string;
|
||||
outbox: string;
|
||||
}> | null>(),
|
||||
source: jsonb("source").notNull().$type<APISource>(),
|
||||
source: jsonb("source").notNull().$type<apiSource>(),
|
||||
avatar: text("avatar").notNull(),
|
||||
header: text("header").notNull(),
|
||||
createdAt: timestamp("created_at", { precision: 3, mode: "string" })
|
||||
|
|
@ -508,100 +508,100 @@ export const ModTags = pgTable("ModTags", {
|
|||
* - Owner: Only manage their own resources
|
||||
*/
|
||||
export enum RolePermissions {
|
||||
MANAGE_NOTES = "notes",
|
||||
MANAGE_OWN_NOTES = "owner:note",
|
||||
VIEW_NOTES = "read:note",
|
||||
VIEW_NOTE_LIKES = "read:note_likes",
|
||||
VIEW_NOTE_BOOSTS = "read:note_boosts",
|
||||
MANAGE_ACCOUNTS = "accounts",
|
||||
MANAGE_OWN_ACCOUNT = "owner:account",
|
||||
VIEW_ACCOUNT_FOLLOWS = "read:account_follows",
|
||||
MANAGE_LIKES = "likes",
|
||||
MANAGE_OWN_LIKES = "owner:like",
|
||||
MANAGE_BOOSTS = "boosts",
|
||||
MANAGE_OWN_BOOSTS = "owner:boost",
|
||||
VIEW_ACCOUNTS = "read:account",
|
||||
MANAGE_EMOJIS = "emojis",
|
||||
VIEW_EMOJIS = "read:emoji",
|
||||
MANAGE_OWN_EMOJIS = "owner:emoji",
|
||||
MANAGE_MEDIA = "media",
|
||||
MANAGE_OWN_MEDIA = "owner:media",
|
||||
MANAGE_BLOCKS = "blocks",
|
||||
MANAGE_OWN_BLOCKS = "owner:block",
|
||||
MANAGE_FILTERS = "filters",
|
||||
MANAGE_OWN_FILTERS = "owner:filter",
|
||||
MANAGE_MUTES = "mutes",
|
||||
MANAGE_OWN_MUTES = "owner:mute",
|
||||
MANAGE_REPORTS = "reports",
|
||||
MANAGE_OWN_REPORTS = "owner:report",
|
||||
MANAGE_SETTINGS = "settings",
|
||||
MANAGE_OWN_SETTINGS = "owner:settings",
|
||||
MANAGE_ROLES = "roles",
|
||||
MANAGE_NOTIFICATIONS = "notifications",
|
||||
MANAGE_OWN_NOTIFICATIONS = "owner:notification",
|
||||
MANAGE_FOLLOWS = "follows",
|
||||
MANAGE_OWN_FOLLOWS = "owner:follow",
|
||||
MANAGE_OWN_APPS = "owner:app",
|
||||
SEARCH = "search",
|
||||
VIEW_PUBLIC_TIMELINES = "public_timelines",
|
||||
VIEW_PRIVATE_TIMELINES = "private_timelines",
|
||||
IGNORE_RATE_LIMITS = "ignore_rate_limits",
|
||||
IMPERSONATE = "impersonate",
|
||||
MANAGE_INSTANCE = "instance",
|
||||
MANAGE_INSTANCE_FEDERATION = "instance:federation",
|
||||
MANAGE_INSTANCE_SETTINGS = "instance:settings",
|
||||
ManageNotes = "notes",
|
||||
ManageOwnNotes = "owner:note",
|
||||
ViewNotes = "read:note",
|
||||
ViewNoteLikes = "read:note_likes",
|
||||
ViewNoteBoosts = "read:note_boosts",
|
||||
ManageAccounts = "accounts",
|
||||
ManageOwnAccount = "owner:account",
|
||||
ViewAccountFollows = "read:account_follows",
|
||||
ManageLikes = "likes",
|
||||
ManageOwnLikes = "owner:like",
|
||||
ManageBoosts = "boosts",
|
||||
ManageOwnBoosts = "owner:boost",
|
||||
ViewAccounts = "read:account",
|
||||
ManageEmojis = "emojis",
|
||||
ViewEmojis = "read:emoji",
|
||||
ManageOwnEmojis = "owner:emoji",
|
||||
ManageMedia = "media",
|
||||
ManageOwnMedia = "owner:media",
|
||||
ManageBlocks = "blocks",
|
||||
ManageOwnBlocks = "owner:block",
|
||||
ManageFilters = "filters",
|
||||
ManageOwnFilters = "owner:filter",
|
||||
ManageMutes = "mutes",
|
||||
ManageOwnMutes = "owner:mute",
|
||||
ManageReports = "reports",
|
||||
ManageOwnReports = "owner:report",
|
||||
ManageSettings = "settings",
|
||||
ManageOwnSettings = "owner:settings",
|
||||
ManageRoles = "roles",
|
||||
ManageNotifications = "notifications",
|
||||
ManageOwnNotifications = "owner:notification",
|
||||
ManageFollows = "follows",
|
||||
ManageOwnFollows = "owner:follow",
|
||||
ManageOwnApps = "owner:app",
|
||||
Search = "search",
|
||||
ViewPublicTimelines = "public_timelines",
|
||||
ViewPrimateTimelines = "private_timelines",
|
||||
IgnoreRateLimits = "ignore_rate_limits",
|
||||
Impersonate = "impersonate",
|
||||
ManageInstance = "instance",
|
||||
ManageInstanceFederation = "instance:federation",
|
||||
ManageInstanceSettings = "instance:settings",
|
||||
/** Users who do not have this permission will not be able to login! */
|
||||
OAUTH = "oauth",
|
||||
OAuth = "oauth",
|
||||
}
|
||||
|
||||
export const DEFAULT_ROLES = [
|
||||
RolePermissions.MANAGE_OWN_NOTES,
|
||||
RolePermissions.VIEW_NOTES,
|
||||
RolePermissions.VIEW_NOTE_LIKES,
|
||||
RolePermissions.VIEW_NOTE_BOOSTS,
|
||||
RolePermissions.MANAGE_OWN_ACCOUNT,
|
||||
RolePermissions.VIEW_ACCOUNT_FOLLOWS,
|
||||
RolePermissions.MANAGE_OWN_LIKES,
|
||||
RolePermissions.MANAGE_OWN_BOOSTS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.MANAGE_OWN_EMOJIS,
|
||||
RolePermissions.VIEW_EMOJIS,
|
||||
RolePermissions.MANAGE_OWN_MEDIA,
|
||||
RolePermissions.MANAGE_OWN_BLOCKS,
|
||||
RolePermissions.MANAGE_OWN_FILTERS,
|
||||
RolePermissions.MANAGE_OWN_MUTES,
|
||||
RolePermissions.MANAGE_OWN_REPORTS,
|
||||
RolePermissions.MANAGE_OWN_SETTINGS,
|
||||
RolePermissions.MANAGE_OWN_NOTIFICATIONS,
|
||||
RolePermissions.MANAGE_OWN_FOLLOWS,
|
||||
RolePermissions.MANAGE_OWN_APPS,
|
||||
RolePermissions.SEARCH,
|
||||
RolePermissions.VIEW_PUBLIC_TIMELINES,
|
||||
RolePermissions.VIEW_PRIVATE_TIMELINES,
|
||||
RolePermissions.OAUTH,
|
||||
RolePermissions.ManageOwnNotes,
|
||||
RolePermissions.ViewNotes,
|
||||
RolePermissions.ViewNoteLikes,
|
||||
RolePermissions.ViewNoteBoosts,
|
||||
RolePermissions.ManageOwnAccount,
|
||||
RolePermissions.ViewAccountFollows,
|
||||
RolePermissions.ManageOwnLikes,
|
||||
RolePermissions.ManageOwnBoosts,
|
||||
RolePermissions.ViewAccounts,
|
||||
RolePermissions.ManageOwnEmojis,
|
||||
RolePermissions.ViewEmojis,
|
||||
RolePermissions.ManageOwnMedia,
|
||||
RolePermissions.ManageOwnBlocks,
|
||||
RolePermissions.ManageOwnFilters,
|
||||
RolePermissions.ManageOwnMutes,
|
||||
RolePermissions.ManageOwnReports,
|
||||
RolePermissions.ManageOwnSettings,
|
||||
RolePermissions.ManageOwnNotifications,
|
||||
RolePermissions.ManageOwnFollows,
|
||||
RolePermissions.ManageOwnApps,
|
||||
RolePermissions.Search,
|
||||
RolePermissions.ViewPublicTimelines,
|
||||
RolePermissions.ViewPrimateTimelines,
|
||||
RolePermissions.OAuth,
|
||||
];
|
||||
|
||||
export const ADMIN_ROLES = [
|
||||
...DEFAULT_ROLES,
|
||||
RolePermissions.MANAGE_NOTES,
|
||||
RolePermissions.MANAGE_ACCOUNTS,
|
||||
RolePermissions.MANAGE_LIKES,
|
||||
RolePermissions.MANAGE_BOOSTS,
|
||||
RolePermissions.MANAGE_EMOJIS,
|
||||
RolePermissions.MANAGE_MEDIA,
|
||||
RolePermissions.MANAGE_BLOCKS,
|
||||
RolePermissions.MANAGE_FILTERS,
|
||||
RolePermissions.MANAGE_MUTES,
|
||||
RolePermissions.MANAGE_REPORTS,
|
||||
RolePermissions.MANAGE_SETTINGS,
|
||||
RolePermissions.MANAGE_ROLES,
|
||||
RolePermissions.MANAGE_NOTIFICATIONS,
|
||||
RolePermissions.MANAGE_FOLLOWS,
|
||||
RolePermissions.IMPERSONATE,
|
||||
RolePermissions.IGNORE_RATE_LIMITS,
|
||||
RolePermissions.MANAGE_INSTANCE,
|
||||
RolePermissions.MANAGE_INSTANCE_FEDERATION,
|
||||
RolePermissions.MANAGE_INSTANCE_SETTINGS,
|
||||
RolePermissions.ManageNotes,
|
||||
RolePermissions.ManageAccounts,
|
||||
RolePermissions.ManageLikes,
|
||||
RolePermissions.ManageBoosts,
|
||||
RolePermissions.ManageEmojis,
|
||||
RolePermissions.ManageMedia,
|
||||
RolePermissions.ManageBlocks,
|
||||
RolePermissions.ManageFilters,
|
||||
RolePermissions.ManageMutes,
|
||||
RolePermissions.ManageReports,
|
||||
RolePermissions.ManageSettings,
|
||||
RolePermissions.ManageRoles,
|
||||
RolePermissions.ManageNotifications,
|
||||
RolePermissions.ManageFollows,
|
||||
RolePermissions.Impersonate,
|
||||
RolePermissions.IgnoreRateLimits,
|
||||
RolePermissions.ManageInstance,
|
||||
RolePermissions.ManageInstanceFederation,
|
||||
RolePermissions.ManageInstanceSettings,
|
||||
];
|
||||
|
||||
export const Roles = pgTable("Roles", {
|
||||
|
|
|
|||
43
index.ts
43
index.ts
|
|
@ -15,7 +15,7 @@ import { Note } from "~/packages/database-interface/note";
|
|||
import { handleGlitchRequest } from "~/packages/glitch-server/main";
|
||||
import { routes } from "~/routes";
|
||||
import { createServer } from "~/server";
|
||||
import type { APIRouteExports } from "~/types/api";
|
||||
import type { ApiRouteExports } from "~/types/api";
|
||||
|
||||
const timeAtStart = performance.now();
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ if (isEntry) {
|
|||
dualServerLogger = dualLogger;
|
||||
}
|
||||
|
||||
await dualServerLogger.log(LogLevel.INFO, "Lysand", "Starting Lysand...");
|
||||
await dualServerLogger.log(LogLevel.Info, "Lysand", "Starting Lysand...");
|
||||
|
||||
await setupDatabase(dualServerLogger);
|
||||
|
||||
|
|
@ -45,12 +45,12 @@ if (isEntry) {
|
|||
// Check if JWT private key is set in config
|
||||
if (!config.oidc.jwt_key) {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.CRITICAL,
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The JWT private key is not set in the config",
|
||||
);
|
||||
await dualServerLogger.log(
|
||||
LogLevel.CRITICAL,
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"Below is a generated key for you to copy in the config at oidc.jwt_key",
|
||||
);
|
||||
|
|
@ -69,7 +69,7 @@ if (isEntry) {
|
|||
).toString("base64");
|
||||
|
||||
await dualServerLogger.log(
|
||||
LogLevel.CRITICAL,
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
chalk.gray(`${privateKey};${publicKey}`),
|
||||
);
|
||||
|
|
@ -100,7 +100,7 @@ if (isEntry) {
|
|||
|
||||
if (privateKey instanceof Error || publicKey instanceof Error) {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.CRITICAL,
|
||||
LogLevel.Critical,
|
||||
"Server",
|
||||
"The JWT key could not be imported! You may generate a new one by removing the old one from the config and restarting the server (this will invalidate all current JWTs).",
|
||||
);
|
||||
|
|
@ -123,16 +123,16 @@ app.use(boundaryCheck);
|
|||
// Inject own filesystem router
|
||||
for (const [, path] of Object.entries(routes)) {
|
||||
// use app.get(path, handler) to add routes
|
||||
const route: APIRouteExports = await import(path);
|
||||
const route: ApiRouteExports = await import(path);
|
||||
|
||||
if (!route.meta || !route.default) {
|
||||
if (!(route.meta && route.default)) {
|
||||
throw new Error(`Route ${path} does not have the correct exports.`);
|
||||
}
|
||||
|
||||
route.default(app);
|
||||
}
|
||||
|
||||
app.options("*", async () => {
|
||||
app.options("*", () => {
|
||||
return response(null);
|
||||
});
|
||||
|
||||
|
|
@ -145,17 +145,14 @@ app.all("*", async (context) => {
|
|||
}
|
||||
}
|
||||
|
||||
const base_url_with_http = config.http.base_url.replace(
|
||||
"https://",
|
||||
"http://",
|
||||
);
|
||||
const baseUrlWithHttp = config.http.base_url.replace("https://", "http://");
|
||||
|
||||
const replacedUrl = context.req.url
|
||||
.replace(config.http.base_url, config.frontend.url)
|
||||
.replace(base_url_with_http, config.frontend.url);
|
||||
.replace(baseUrlWithHttp, config.frontend.url);
|
||||
|
||||
await dualLogger.log(
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.Debug,
|
||||
"Server.Proxy",
|
||||
`Proxying ${replacedUrl}`,
|
||||
);
|
||||
|
|
@ -168,9 +165,9 @@ app.all("*", async (context) => {
|
|||
},
|
||||
redirect: "manual",
|
||||
}).catch(async (e) => {
|
||||
await dualLogger.logError(LogLevel.ERROR, "Server.Proxy", e as Error);
|
||||
await dualLogger.logError(LogLevel.Error, "Server.Proxy", e as Error);
|
||||
await dualLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server.Proxy",
|
||||
`The Frontend is not running or the route is not found: ${replacedUrl}`,
|
||||
);
|
||||
|
|
@ -192,13 +189,13 @@ app.all("*", async (context) => {
|
|||
createServer(config, app);
|
||||
|
||||
await dualServerLogger.log(
|
||||
LogLevel.INFO,
|
||||
LogLevel.Info,
|
||||
"Server",
|
||||
`Lysand started at ${config.http.bind}:${config.http.bind_port} in ${(performance.now() - timeAtStart).toFixed(0)}ms`,
|
||||
);
|
||||
|
||||
await dualServerLogger.log(
|
||||
LogLevel.INFO,
|
||||
LogLevel.Info,
|
||||
"Database",
|
||||
`Database is online, now serving ${postCount} posts`,
|
||||
);
|
||||
|
|
@ -206,7 +203,7 @@ await dualServerLogger.log(
|
|||
if (config.frontend.enabled) {
|
||||
if (!URL.canParse(config.frontend.url)) {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server",
|
||||
`Frontend URL is not a valid URL: ${config.frontend.url}`,
|
||||
);
|
||||
|
|
@ -220,19 +217,19 @@ if (config.frontend.enabled) {
|
|||
|
||||
if (!response) {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server",
|
||||
`Frontend is unreachable at ${config.frontend.url}`,
|
||||
);
|
||||
await dualServerLogger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server",
|
||||
"Please ensure the frontend is online and reachable",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await dualServerLogger.log(
|
||||
LogLevel.WARNING,
|
||||
LogLevel.Warning,
|
||||
"Server",
|
||||
"Frontend is disabled, skipping check",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ import { config } from "~/packages/config-manager";
|
|||
import { LogLevel } from "~/packages/log-manager";
|
||||
|
||||
export const bait = createMiddleware(async (context, next) => {
|
||||
const request_ip = context.env?.ip as SocketAddress | undefined | null;
|
||||
const requestIp = context.env?.ip as SocketAddress | undefined | null;
|
||||
|
||||
if (config.http.bait.enabled) {
|
||||
// Check for bait IPs
|
||||
if (request_ip?.address) {
|
||||
if (requestIp?.address) {
|
||||
for (const ip of config.http.bait.bait_ips) {
|
||||
try {
|
||||
if (matches(ip, request_ip.address)) {
|
||||
if (matches(ip, requestIp.address)) {
|
||||
const file = Bun.file(
|
||||
config.http.bait.send_file || "./beemovie.txt",
|
||||
);
|
||||
|
|
@ -23,19 +23,19 @@ export const bait = createMiddleware(async (context, next) => {
|
|||
return response(file);
|
||||
}
|
||||
await logger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server.Bait",
|
||||
`Bait file not found: ${config.http.bait.send_file}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server.IPCheck",
|
||||
`Error while parsing bait IP "${ip}" `,
|
||||
);
|
||||
logger.logError(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server.IPCheck",
|
||||
e as Error,
|
||||
);
|
||||
|
|
@ -61,7 +61,7 @@ export const bait = createMiddleware(async (context, next) => {
|
|||
return response(file);
|
||||
}
|
||||
await logger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server.Bait",
|
||||
`Bait file not found: ${config.http.bait.send_file}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,25 +9,25 @@ import { LogLevel } from "~/packages/log-manager";
|
|||
export const ipBans = createMiddleware(async (context, next) => {
|
||||
// Check for banned IPs
|
||||
|
||||
const request_ip = context.env?.ip as SocketAddress | undefined | null;
|
||||
const requestIp = context.env?.ip as SocketAddress | undefined | null;
|
||||
|
||||
if (!request_ip?.address) {
|
||||
if (!requestIp?.address) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const ip of config.http.banned_ips) {
|
||||
try {
|
||||
if (matches(ip, request_ip?.address)) {
|
||||
if (matches(ip, requestIp?.address)) {
|
||||
return errorResponse("Forbidden", 403);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.log(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Server.IPCheck",
|
||||
`Error while parsing banned IP "${ip}" `,
|
||||
);
|
||||
logger.logError(LogLevel.ERROR, "Server.IPCheck", e as Error);
|
||||
logger.logError(LogLevel.Error, "Server.IPCheck", e as Error);
|
||||
|
||||
return errorResponse(
|
||||
`A server error occured: ${(e as Error).message}`,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { createMiddleware } from "hono/factory";
|
|||
import { config } from "~/packages/config-manager";
|
||||
|
||||
export const logger = createMiddleware(async (context, next) => {
|
||||
const request_ip = context.env?.ip as SocketAddress | undefined | null;
|
||||
const requestIp = context.env?.ip as SocketAddress | undefined | null;
|
||||
|
||||
if (config.logging.log_requests) {
|
||||
await dualLogger.logRequest(
|
||||
context.req.raw,
|
||||
config.logging.log_ip ? request_ip?.address : undefined,
|
||||
config.logging.log_ip ? requestIp?.address : undefined,
|
||||
config.logging.log_requests_verbose,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { z } from "zod";
|
|||
import { ADMIN_ROLES, DEFAULT_ROLES, RolePermissions } from "~/drizzle/schema";
|
||||
|
||||
export enum MediaBackendType {
|
||||
LOCAL = "local",
|
||||
Local = "local",
|
||||
S3 = "s3",
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ export const configValidator = z.object({
|
|||
.object({
|
||||
backend: z
|
||||
.nativeEnum(MediaBackendType)
|
||||
.default(MediaBackendType.LOCAL),
|
||||
.default(MediaBackendType.Local),
|
||||
deduplicate_media: z.boolean().default(true),
|
||||
local_uploads_folder: z.string().min(1).default("uploads"),
|
||||
conversion: z
|
||||
|
|
@ -234,7 +234,7 @@ export const configValidator = z.object({
|
|||
}),
|
||||
})
|
||||
.default({
|
||||
backend: MediaBackendType.LOCAL,
|
||||
backend: MediaBackendType.Local,
|
||||
deduplicate_media: true,
|
||||
local_uploads_folder: "uploads",
|
||||
conversion: {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
|
||||
import { loadConfig, watchConfig } from "c12";
|
||||
import chalk from "chalk";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { type Config, configValidator } from "./config.type";
|
||||
|
||||
const { config } = await watchConfig({
|
||||
|
|
@ -23,21 +21,6 @@ const { config } = await watchConfig({
|
|||
const parsed = await configValidator.safeParseAsync(config);
|
||||
|
||||
if (!parsed.success) {
|
||||
console.log(
|
||||
`${chalk.bgRed.white(
|
||||
" CRITICAL ",
|
||||
)} There was an error parsing the config file at ${chalk.bold(
|
||||
"./config/config.toml",
|
||||
)}. Please fix the file and try again.`,
|
||||
);
|
||||
console.log(
|
||||
`${chalk.bgRed.white(
|
||||
" CRITICAL ",
|
||||
)} Follow the installation intructions and get a sample config file from the repository if needed.`,
|
||||
);
|
||||
console.log(
|
||||
`${chalk.bgRed.white(" CRITICAL ")} ${fromError(parsed.error).message}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from "drizzle-orm";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Attachments } from "~/drizzle/schema";
|
||||
import type { AsyncAttachment as APIAsyncAttachment } from "~/types/mastodon/async_attachment";
|
||||
import type { AsyncAttachment as apiAsyncAttachment } from "~/types/mastodon/async_attachment";
|
||||
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
|
||||
import { BaseInterface } from "./base";
|
||||
|
||||
|
|
@ -28,7 +28,9 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
|||
}
|
||||
|
||||
public static async fromId(id: string | null): Promise<Attachment | null> {
|
||||
if (!id) return null;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await Attachment.fromSql(eq(Attachments.id, id));
|
||||
}
|
||||
|
|
@ -46,7 +48,9 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
|||
orderBy,
|
||||
});
|
||||
|
||||
if (!found) return null;
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
return new Attachment(found);
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +90,7 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
|||
return updated.data;
|
||||
}
|
||||
|
||||
async save(): Promise<AttachmentType> {
|
||||
save(): Promise<AttachmentType> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
|
|
@ -120,52 +124,60 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
|||
return this.data.id;
|
||||
}
|
||||
|
||||
public toAPI(): APIAttachment | APIAsyncAttachment {
|
||||
let type = "unknown";
|
||||
|
||||
public getMastodonType(): APIAttachment["type"] {
|
||||
if (this.data.mimeType.startsWith("image/")) {
|
||||
type = "image";
|
||||
} else if (this.data.mimeType.startsWith("video/")) {
|
||||
type = "video";
|
||||
} else if (this.data.mimeType.startsWith("audio/")) {
|
||||
type = "audio";
|
||||
return "image";
|
||||
}
|
||||
if (this.data.mimeType.startsWith("video/")) {
|
||||
return "video";
|
||||
}
|
||||
if (this.data.mimeType.startsWith("audio/")) {
|
||||
return "audio";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
public toApiMeta(): APIAttachment["meta"] {
|
||||
return {
|
||||
id: this.data.id,
|
||||
type: type as "image" | "video" | "audio" | "unknown",
|
||||
url: proxyUrl(this.data.url) ?? "",
|
||||
remote_url: proxyUrl(this.data.remoteUrl),
|
||||
preview_url: proxyUrl(this.data.thumbnailUrl || this.data.url),
|
||||
text_url: null,
|
||||
meta: {
|
||||
width: this.data.width || undefined,
|
||||
height: this.data.height || undefined,
|
||||
fps: this.data.fps || undefined,
|
||||
size:
|
||||
this.data.width && this.data.height
|
||||
? `${this.data.width}x${this.data.height}`
|
||||
: undefined,
|
||||
duration: this.data.duration || undefined,
|
||||
length: undefined,
|
||||
aspect:
|
||||
this.data.width && this.data.height
|
||||
? this.data.width / this.data.height
|
||||
: undefined,
|
||||
original: {
|
||||
width: this.data.width || undefined,
|
||||
height: this.data.height || undefined,
|
||||
fps: this.data.fps || undefined,
|
||||
size:
|
||||
this.data.width && this.data.height
|
||||
? `${this.data.width}x${this.data.height}`
|
||||
: undefined,
|
||||
duration: this.data.duration || undefined,
|
||||
length: undefined,
|
||||
aspect:
|
||||
this.data.width && this.data.height
|
||||
? this.data.width / this.data.height
|
||||
: undefined,
|
||||
original: {
|
||||
width: this.data.width || undefined,
|
||||
height: this.data.height || undefined,
|
||||
size:
|
||||
this.data.width && this.data.height
|
||||
? `${this.data.width}x${this.data.height}`
|
||||
: undefined,
|
||||
aspect:
|
||||
this.data.width && this.data.height
|
||||
? this.data.width / this.data.height
|
||||
: undefined,
|
||||
},
|
||||
// Idk whether size or length is the right value
|
||||
},
|
||||
// Idk whether size or length is the right value
|
||||
};
|
||||
}
|
||||
|
||||
public toApi(): APIAttachment | apiAsyncAttachment {
|
||||
return {
|
||||
id: this.data.id,
|
||||
type: this.getMastodonType(),
|
||||
url: proxyUrl(this.data.url) ?? "",
|
||||
remote_url: proxyUrl(this.data.remoteUrl),
|
||||
preview_url: proxyUrl(this.data.thumbnailUrl || this.data.url),
|
||||
text_url: null,
|
||||
meta: this.toApiMeta(),
|
||||
description: this.data.description,
|
||||
blurhash: this.data.blurhash,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,21 +19,21 @@ import { LogLevel } from "log-manager";
|
|||
import { createRegExp, exactly, global } from "magic-regexp";
|
||||
import {
|
||||
type Application,
|
||||
applicationToAPI,
|
||||
} from "~/database/entities/Application";
|
||||
applicationToApi,
|
||||
} from "~/database/entities/application";
|
||||
import {
|
||||
type EmojiWithInstance,
|
||||
emojiToAPI,
|
||||
emojiToApi,
|
||||
emojiToLysand,
|
||||
fetchEmoji,
|
||||
parseEmojis,
|
||||
} from "~/database/entities/Emoji";
|
||||
import { localObjectURI } from "~/database/entities/Federation";
|
||||
} from "~/database/entities/emoji";
|
||||
import { localObjectUri } from "~/database/entities/federation";
|
||||
import {
|
||||
type StatusWithRelations,
|
||||
contentToHtml,
|
||||
findManyNotes,
|
||||
} from "~/database/entities/Status";
|
||||
} from "~/database/entities/status";
|
||||
import { db } from "~/drizzle/db";
|
||||
import {
|
||||
Attachments,
|
||||
|
|
@ -44,8 +44,8 @@ import {
|
|||
Users,
|
||||
} from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
|
||||
import type { Status as APIStatus } from "~/types/mastodon/status";
|
||||
import type { Attachment as apiAttachment } from "~/types/mastodon/attachment";
|
||||
import type { Status as apiStatus } from "~/types/mastodon/status";
|
||||
import { Attachment } from "./attachment";
|
||||
import { BaseInterface } from "./base";
|
||||
import { User } from "./user";
|
||||
|
|
@ -54,7 +54,7 @@ import { User } from "./user";
|
|||
* Gives helpers to fetch notes from database in a nice format
|
||||
*/
|
||||
export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||
async save(): Promise<StatusWithRelations> {
|
||||
save(): Promise<StatusWithRelations> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +87,9 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
id: string | null,
|
||||
userRequestingNoteId?: string,
|
||||
): Promise<Note | null> {
|
||||
if (!id) return null;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await Note.fromSql(
|
||||
eq(Notes.id, id),
|
||||
|
|
@ -123,7 +125,9 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
userId,
|
||||
);
|
||||
|
||||
if (!found[0]) return null;
|
||||
if (!found[0]) {
|
||||
return null;
|
||||
}
|
||||
return new Note(found[0]);
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +229,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
);
|
||||
}
|
||||
|
||||
async isRemote() {
|
||||
isRemote() {
|
||||
return this.author.isRemote();
|
||||
}
|
||||
|
||||
|
|
@ -248,14 +252,14 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
static async fromData(
|
||||
author: User,
|
||||
content: typeof EntityValidator.$ContentFormat,
|
||||
visibility: APIStatus["visibility"],
|
||||
is_sensitive: boolean,
|
||||
spoiler_text: string,
|
||||
visibility: apiStatus["visibility"],
|
||||
isSensitive: boolean,
|
||||
spoilerText: string,
|
||||
emojis: EmojiWithInstance[],
|
||||
uri?: string,
|
||||
mentions?: User[],
|
||||
/** List of IDs of database Attachment objects */
|
||||
media_attachments?: string[],
|
||||
mediaAttachments?: string[],
|
||||
replyId?: string,
|
||||
quoteId?: string,
|
||||
application?: Application,
|
||||
|
|
@ -284,8 +288,8 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
"",
|
||||
contentType: "text/html",
|
||||
visibility,
|
||||
sensitive: is_sensitive,
|
||||
spoilerText: await sanitizedHtmlStrip(spoiler_text),
|
||||
sensitive: isSensitive,
|
||||
spoilerText: await sanitizedHtmlStrip(spoilerText),
|
||||
uri: uri || null,
|
||||
replyId: replyId ?? null,
|
||||
quotingId: quoteId ?? null,
|
||||
|
|
@ -315,13 +319,13 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
}
|
||||
|
||||
// Set attachment parents
|
||||
if (media_attachments && media_attachments.length > 0) {
|
||||
if (mediaAttachments && mediaAttachments.length > 0) {
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set({
|
||||
noteId: newNote.id,
|
||||
})
|
||||
.where(inArray(Attachments.id, media_attachments));
|
||||
.where(inArray(Attachments.id, mediaAttachments));
|
||||
}
|
||||
|
||||
// Send notifications for mentioned local users
|
||||
|
|
@ -341,13 +345,13 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
|
||||
async updateFromData(
|
||||
content?: typeof EntityValidator.$ContentFormat,
|
||||
visibility?: APIStatus["visibility"],
|
||||
is_sensitive?: boolean,
|
||||
spoiler_text?: string,
|
||||
visibility?: apiStatus["visibility"],
|
||||
isSensitive?: boolean,
|
||||
spoilerText?: string,
|
||||
emojis: EmojiWithInstance[] = [],
|
||||
mentions: User[] = [],
|
||||
/** List of IDs of database Attachment objects */
|
||||
media_attachments: string[] = [],
|
||||
mediaAttachments: string[] = [],
|
||||
replyId?: string,
|
||||
quoteId?: string,
|
||||
application?: Application,
|
||||
|
|
@ -378,8 +382,8 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
: undefined,
|
||||
contentType: "text/html",
|
||||
visibility,
|
||||
sensitive: is_sensitive,
|
||||
spoilerText: spoiler_text,
|
||||
sensitive: isSensitive,
|
||||
spoilerText: spoilerText,
|
||||
replyId,
|
||||
quotingId: quoteId,
|
||||
applicationId: application?.id,
|
||||
|
|
@ -416,7 +420,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
}
|
||||
|
||||
// Set attachment parents
|
||||
if (media_attachments) {
|
||||
if (mediaAttachments) {
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set({
|
||||
|
|
@ -424,13 +428,14 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
})
|
||||
.where(eq(Attachments.noteId, this.data.id));
|
||||
|
||||
if (media_attachments.length > 0)
|
||||
if (mediaAttachments.length > 0) {
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set({
|
||||
noteId: this.data.id,
|
||||
})
|
||||
.where(inArray(Attachments.id, media_attachments));
|
||||
.where(inArray(Attachments.id, mediaAttachments));
|
||||
}
|
||||
}
|
||||
|
||||
return await Note.fromId(newNote.id, newNote.authorId);
|
||||
|
|
@ -443,13 +448,15 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
// Check if note not already in database
|
||||
const foundNote = uri && (await Note.fromSql(eq(Notes.uri, uri)));
|
||||
|
||||
if (foundNote) return foundNote;
|
||||
if (foundNote) {
|
||||
return foundNote;
|
||||
}
|
||||
|
||||
// Check if URI is of a local note
|
||||
if (uri?.startsWith(config.http.base_url)) {
|
||||
const uuid = uri.match(idValidator);
|
||||
|
||||
if (!uuid || !uuid[0]) {
|
||||
if (!uuid?.[0]) {
|
||||
throw new Error(
|
||||
`URI ${uri} is of a local note, but it could not be parsed`,
|
||||
);
|
||||
|
|
@ -465,7 +472,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
uri?: string,
|
||||
providedNote?: typeof EntityValidator.$Note,
|
||||
): Promise<Note | null> {
|
||||
if (!uri && !providedNote) {
|
||||
if (!(uri || providedNote)) {
|
||||
throw new Error("No URI or note provided");
|
||||
}
|
||||
|
||||
|
|
@ -515,7 +522,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
attachment,
|
||||
).catch((e) => {
|
||||
dualLogger.logError(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Federation.StatusResolver",
|
||||
e,
|
||||
);
|
||||
|
|
@ -533,7 +540,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
?.emojis ?? []) {
|
||||
const resolvedEmoji = await fetchEmoji(emoji).catch((e) => {
|
||||
dualLogger.logError(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"Federation.StatusResolver",
|
||||
e,
|
||||
);
|
||||
|
|
@ -552,7 +559,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
content: "",
|
||||
},
|
||||
},
|
||||
note.visibility as APIStatus["visibility"],
|
||||
note.visibility as apiStatus["visibility"],
|
||||
note.is_sensitive ?? false,
|
||||
note.subject ?? "",
|
||||
emojis,
|
||||
|
|
@ -582,7 +589,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
content: "",
|
||||
},
|
||||
},
|
||||
note.visibility as APIStatus["visibility"],
|
||||
note.visibility as apiStatus["visibility"],
|
||||
note.is_sensitive ?? false,
|
||||
note.subject ?? "",
|
||||
emojis,
|
||||
|
|
@ -638,9 +645,15 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
* @returns Whether this status is viewable by the user.
|
||||
*/
|
||||
async isViewableByUser(user: User | null) {
|
||||
if (this.author.id === user?.id) return true;
|
||||
if (this.data.visibility === "public") return true;
|
||||
if (this.data.visibility === "unlisted") return true;
|
||||
if (this.author.id === user?.id) {
|
||||
return true;
|
||||
}
|
||||
if (this.data.visibility === "public") {
|
||||
return true;
|
||||
}
|
||||
if (this.data.visibility === "unlisted") {
|
||||
return true;
|
||||
}
|
||||
if (this.data.visibility === "private") {
|
||||
return user
|
||||
? await db.query.Relationships.findFirst({
|
||||
|
|
@ -658,7 +671,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
);
|
||||
}
|
||||
|
||||
async toAPI(userFetching?: User | null): Promise<APIStatus> {
|
||||
async toApi(userFetching?: User | null): Promise<apiStatus> {
|
||||
const data = this.data;
|
||||
|
||||
// Convert mentions of local users from @username@host to @username
|
||||
|
|
@ -696,18 +709,18 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
id: data.id,
|
||||
in_reply_to_id: data.replyId || null,
|
||||
in_reply_to_account_id: data.reply?.authorId || null,
|
||||
account: this.author.toAPI(userFetching?.id === data.authorId),
|
||||
account: this.author.toApi(userFetching?.id === data.authorId),
|
||||
created_at: new Date(data.createdAt).toISOString(),
|
||||
application: data.application
|
||||
? applicationToAPI(data.application)
|
||||
? applicationToApi(data.application)
|
||||
: null,
|
||||
card: null,
|
||||
content: replacedContent,
|
||||
emojis: data.emojis.map((emoji) => emojiToAPI(emoji)),
|
||||
emojis: data.emojis.map((emoji) => emojiToApi(emoji)),
|
||||
favourited: data.liked,
|
||||
favourites_count: data.likeCount,
|
||||
media_attachments: (data.attachments ?? []).map(
|
||||
(a) => new Attachment(a).toAPI() as APIAttachment,
|
||||
(a) => new Attachment(a).toApi() as apiAttachment,
|
||||
),
|
||||
mentions: data.mentions.map((mention) => ({
|
||||
id: mention.id,
|
||||
|
|
@ -725,7 +738,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
// TODO: Add polls
|
||||
poll: null,
|
||||
reblog: data.reblog
|
||||
? await new Note(data.reblog as StatusWithRelations).toAPI(
|
||||
? await new Note(data.reblog as StatusWithRelations).toApi(
|
||||
userFetching,
|
||||
)
|
||||
: null,
|
||||
|
|
@ -736,13 +749,13 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
spoiler_text: data.spoilerText,
|
||||
tags: [],
|
||||
uri: data.uri || this.getUri(),
|
||||
visibility: data.visibility as APIStatus["visibility"],
|
||||
visibility: data.visibility as apiStatus["visibility"],
|
||||
url: data.uri || this.getMastoUri(),
|
||||
bookmarked: false,
|
||||
// @ts-expect-error Glitch-SOC extension
|
||||
quote: data.quotingId
|
||||
? (await Note.fromId(data.quotingId, userFetching?.id).then(
|
||||
(n) => n?.toAPI(userFetching),
|
||||
(n) => n?.toApi(userFetching),
|
||||
)) ?? null
|
||||
: null,
|
||||
quote_id: data.quotingId || undefined,
|
||||
|
|
@ -750,12 +763,14 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
}
|
||||
|
||||
getUri() {
|
||||
return localObjectURI(this.data.id);
|
||||
return localObjectUri(this.data.id);
|
||||
}
|
||||
|
||||
static getUri(id?: string | null) {
|
||||
if (!id) return null;
|
||||
return localObjectURI(id);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return localObjectUri(id);
|
||||
}
|
||||
|
||||
getMastoUri() {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
userInfoRequest,
|
||||
validateAuthResponse,
|
||||
} from "oauth4webapi";
|
||||
import type { Application } from "~/database/entities/Application";
|
||||
import type { Application } from "~/database/entities/application";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { type Applications, OpenIdAccounts } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
|
@ -21,13 +21,13 @@ import { config } from "~/packages/config-manager";
|
|||
export class OAuthManager {
|
||||
public issuer: (typeof config.oidc.providers)[0];
|
||||
|
||||
constructor(public issuer_id: string) {
|
||||
constructor(public issuerId: string) {
|
||||
const found = config.oidc.providers.find(
|
||||
(provider) => provider.id === this.issuer_id,
|
||||
(provider) => provider.id === this.issuerId,
|
||||
);
|
||||
|
||||
if (!found) {
|
||||
throw new Error(`Issuer ${this.issuer_id} not found`);
|
||||
throw new Error(`Issuer ${this.issuerId} not found`);
|
||||
}
|
||||
|
||||
this.issuer = found;
|
||||
|
|
@ -48,7 +48,7 @@ export class OAuthManager {
|
|||
}).then((res) => processDiscoveryResponse(issuerUrl, res));
|
||||
}
|
||||
|
||||
async getParameters(
|
||||
getParameters(
|
||||
authServer: AuthorizationServer,
|
||||
issuer: (typeof config.oidc.providers)[0],
|
||||
currentUrl: URL,
|
||||
|
|
@ -101,7 +101,7 @@ export class OAuthManager {
|
|||
async getUserInfo(
|
||||
authServer: AuthorizationServer,
|
||||
issuer: (typeof config.oidc.providers)[0],
|
||||
access_token: string,
|
||||
accessToken: string,
|
||||
sub: string,
|
||||
) {
|
||||
return await userInfoRequest(
|
||||
|
|
@ -110,7 +110,7 @@ export class OAuthManager {
|
|||
client_id: issuer.client_id,
|
||||
client_secret: issuer.client_secret,
|
||||
},
|
||||
access_token,
|
||||
accessToken,
|
||||
).then(
|
||||
async (res) =>
|
||||
await processUserInfoResponse(
|
||||
|
|
@ -125,7 +125,7 @@ export class OAuthManager {
|
|||
);
|
||||
}
|
||||
|
||||
async processOAuth2Error(
|
||||
processOAuth2Error(
|
||||
application: InferInsertModel<typeof Applications> | null,
|
||||
) {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ export class Role extends BaseInterface<typeof Roles> {
|
|||
}
|
||||
|
||||
public static async fromId(id: string | null): Promise<Role | null> {
|
||||
if (!id) return null;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await Role.fromSql(eq(Roles.id, id));
|
||||
}
|
||||
|
|
@ -45,7 +47,9 @@ export class Role extends BaseInterface<typeof Roles> {
|
|||
orderBy,
|
||||
});
|
||||
|
||||
if (!found) return null;
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
return new Role(found);
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +127,7 @@ export class Role extends BaseInterface<typeof Roles> {
|
|||
return updated.data;
|
||||
}
|
||||
|
||||
async save(): Promise<RoleType> {
|
||||
save(): Promise<RoleType> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +177,7 @@ export class Role extends BaseInterface<typeof Roles> {
|
|||
return this.data.id;
|
||||
}
|
||||
|
||||
public toAPI() {
|
||||
public toApi() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.data.name,
|
||||
|
|
|
|||
|
|
@ -5,20 +5,20 @@ import { Note } from "./note";
|
|||
import { User } from "./user";
|
||||
|
||||
enum TimelineType {
|
||||
NOTE = "Note",
|
||||
USER = "User",
|
||||
Note = "Note",
|
||||
User = "User",
|
||||
}
|
||||
|
||||
export class Timeline {
|
||||
export class Timeline<Type extends Note | User> {
|
||||
constructor(private type: TimelineType) {}
|
||||
|
||||
static async getNoteTimeline(
|
||||
static getNoteTimeline(
|
||||
sql: SQL<unknown> | undefined,
|
||||
limit: number,
|
||||
url: string,
|
||||
userId?: string,
|
||||
) {
|
||||
return new Timeline(TimelineType.NOTE).fetchTimeline<Note>(
|
||||
return new Timeline<Note>(TimelineType.Note).fetchTimeline(
|
||||
sql,
|
||||
limit,
|
||||
url,
|
||||
|
|
@ -26,19 +26,158 @@ export class Timeline {
|
|||
);
|
||||
}
|
||||
|
||||
static async getUserTimeline(
|
||||
static getUserTimeline(
|
||||
sql: SQL<unknown> | undefined,
|
||||
limit: number,
|
||||
url: string,
|
||||
) {
|
||||
return new Timeline(TimelineType.USER).fetchTimeline<User>(
|
||||
return new Timeline<User>(TimelineType.User).fetchTimeline(
|
||||
sql,
|
||||
limit,
|
||||
url,
|
||||
);
|
||||
}
|
||||
|
||||
private async fetchTimeline<T>(
|
||||
private async fetchObjects(
|
||||
sql: SQL<unknown> | undefined,
|
||||
limit: number,
|
||||
userId?: string,
|
||||
): Promise<Type[]> {
|
||||
switch (this.type) {
|
||||
case TimelineType.Note:
|
||||
return (await Note.manyFromSql(
|
||||
sql,
|
||||
undefined,
|
||||
limit,
|
||||
undefined,
|
||||
userId,
|
||||
)) as Type[];
|
||||
case TimelineType.User:
|
||||
return (await User.manyFromSql(
|
||||
sql,
|
||||
undefined,
|
||||
limit,
|
||||
)) as Type[];
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchLinkHeader(
|
||||
objects: Type[],
|
||||
url: string,
|
||||
limit: number,
|
||||
): Promise<string> {
|
||||
const linkHeader = [];
|
||||
const urlWithoutQuery = new URL(
|
||||
new URL(url).pathname,
|
||||
config.http.base_url,
|
||||
).toString();
|
||||
|
||||
if (objects.length > 0) {
|
||||
switch (this.type) {
|
||||
case TimelineType.Note:
|
||||
linkHeader.push(
|
||||
...(await this.fetchNoteLinkHeader(
|
||||
objects as Note[],
|
||||
urlWithoutQuery,
|
||||
limit,
|
||||
)),
|
||||
);
|
||||
break;
|
||||
case TimelineType.User:
|
||||
linkHeader.push(
|
||||
...(await this.fetchUserLinkHeader(
|
||||
objects as User[],
|
||||
urlWithoutQuery,
|
||||
limit,
|
||||
)),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return linkHeader.join(", ");
|
||||
}
|
||||
|
||||
private async fetchNoteLinkHeader(
|
||||
notes: Note[],
|
||||
urlWithoutQuery: string,
|
||||
limit: number,
|
||||
): Promise<string[]> {
|
||||
const linkHeader = [];
|
||||
|
||||
const objectBefore = await Note.fromSql(gt(Notes.id, notes[0].data.id));
|
||||
if (objectBefore) {
|
||||
linkHeader.push(
|
||||
`<${urlWithoutQuery}?limit=${limit ?? 20}&min_id=${notes[0].data.id}>; rel="prev"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (notes.length >= (limit ?? 20)) {
|
||||
const objectAfter = await Note.fromSql(
|
||||
gt(Notes.id, notes[notes.length - 1].data.id),
|
||||
);
|
||||
if (objectAfter) {
|
||||
linkHeader.push(
|
||||
`<${urlWithoutQuery}?limit=${limit ?? 20}&max_id=${notes[notes.length - 1].data.id}>; rel="next"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return linkHeader;
|
||||
}
|
||||
|
||||
private async fetchUserLinkHeader(
|
||||
users: User[],
|
||||
urlWithoutQuery: string,
|
||||
limit: number,
|
||||
): Promise<string[]> {
|
||||
const linkHeader = [];
|
||||
|
||||
const objectBefore = await User.fromSql(gt(Users.id, users[0].id));
|
||||
if (objectBefore) {
|
||||
linkHeader.push(
|
||||
`<${urlWithoutQuery}?limit=${limit ?? 20}&min_id=${users[0].id}>; rel="prev"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (users.length >= (limit ?? 20)) {
|
||||
const objectAfter = await User.fromSql(
|
||||
gt(Users.id, users[users.length - 1].id),
|
||||
);
|
||||
if (objectAfter) {
|
||||
linkHeader.push(
|
||||
`<${urlWithoutQuery}?limit=${limit ?? 20}&max_id=${users[users.length - 1].id}>; rel="next"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return linkHeader;
|
||||
}
|
||||
|
||||
private async fetchTimeline(
|
||||
sql: SQL<unknown> | undefined,
|
||||
limit: number,
|
||||
url: string,
|
||||
userId?: string,
|
||||
): Promise<{ link: string; objects: Type[] }> {
|
||||
const objects = await this.fetchObjects(sql, limit, userId);
|
||||
const link = await this.fetchLinkHeader(objects, url, limit);
|
||||
|
||||
switch (this.type) {
|
||||
case TimelineType.Note:
|
||||
return {
|
||||
link,
|
||||
objects: objects,
|
||||
};
|
||||
case TimelineType.User:
|
||||
return {
|
||||
link,
|
||||
objects: objects,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* private async fetchTimeline<T>(
|
||||
sql: SQL<unknown> | undefined,
|
||||
limit: number,
|
||||
url: string,
|
||||
|
|
@ -48,7 +187,7 @@ export class Timeline {
|
|||
const users: User[] = [];
|
||||
|
||||
switch (this.type) {
|
||||
case TimelineType.NOTE:
|
||||
case TimelineType.Note:
|
||||
notes.push(
|
||||
...(await Note.manyFromSql(
|
||||
sql,
|
||||
|
|
@ -59,7 +198,7 @@ export class Timeline {
|
|||
)),
|
||||
);
|
||||
break;
|
||||
case TimelineType.USER:
|
||||
case TimelineType.User:
|
||||
users.push(...(await User.manyFromSql(sql, undefined, limit)));
|
||||
break;
|
||||
}
|
||||
|
|
@ -72,7 +211,7 @@ export class Timeline {
|
|||
|
||||
if (notes.length > 0) {
|
||||
switch (this.type) {
|
||||
case TimelineType.NOTE: {
|
||||
case TimelineType.Note: {
|
||||
const objectBefore = await Note.fromSql(
|
||||
gt(Notes.id, notes[0].data.id),
|
||||
);
|
||||
|
|
@ -102,7 +241,7 @@ export class Timeline {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case TimelineType.USER: {
|
||||
case TimelineType.User: {
|
||||
const objectBefore = await User.fromSql(
|
||||
gt(Users.id, users[0].id),
|
||||
);
|
||||
|
|
@ -136,16 +275,16 @@ export class Timeline {
|
|||
}
|
||||
|
||||
switch (this.type) {
|
||||
case TimelineType.NOTE:
|
||||
case TimelineType.Note:
|
||||
return {
|
||||
link: linkHeader.join(", "),
|
||||
objects: notes as T[],
|
||||
};
|
||||
case TimelineType.USER:
|
||||
case TimelineType.User:
|
||||
return {
|
||||
link: linkHeader.join(", "),
|
||||
objects: users as T[],
|
||||
};
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { addUserToMeilisearch } from "@/meilisearch";
|
|||
import { proxyUrl } from "@/response";
|
||||
import { EntityValidator } from "@lysand-org/federation";
|
||||
import {
|
||||
type InferInsertModel,
|
||||
type InferSelectModel,
|
||||
type SQL,
|
||||
and,
|
||||
count,
|
||||
|
|
@ -19,20 +21,21 @@ import {
|
|||
} from "drizzle-orm";
|
||||
import { htmlToText } from "html-to-text";
|
||||
import {
|
||||
emojiToAPI,
|
||||
emojiToApi,
|
||||
emojiToLysand,
|
||||
fetchEmoji,
|
||||
} from "~/database/entities/Emoji";
|
||||
import { objectToInboxRequest } from "~/database/entities/Federation";
|
||||
import { addInstanceIfNotExists } from "~/database/entities/Instance";
|
||||
} from "~/database/entities/emoji";
|
||||
import { objectToInboxRequest } from "~/database/entities/federation";
|
||||
import { addInstanceIfNotExists } from "~/database/entities/instance";
|
||||
import {
|
||||
type UserWithRelations,
|
||||
findFirstUser,
|
||||
findManyUsers,
|
||||
} from "~/database/entities/User";
|
||||
} from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import {
|
||||
EmojiToUser,
|
||||
type Instances,
|
||||
NoteToMentions,
|
||||
Notes,
|
||||
type RolePermissions,
|
||||
|
|
@ -40,8 +43,8 @@ import {
|
|||
Users,
|
||||
} from "~/drizzle/schema";
|
||||
import { type Config, config } from "~/packages/config-manager";
|
||||
import type { Account as APIAccount } from "~/types/mastodon/account";
|
||||
import type { Mention as APIMention } from "~/types/mastodon/mention";
|
||||
import type { Account as apiAccount } from "~/types/mastodon/account";
|
||||
import type { Mention as apiMention } from "~/types/mastodon/mention";
|
||||
import { BaseInterface } from "./base";
|
||||
import type { Note } from "./note";
|
||||
import { Role } from "./role";
|
||||
|
|
@ -61,7 +64,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
}
|
||||
|
||||
static async fromId(id: string | null): Promise<User | null> {
|
||||
if (!id) return null;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await User.fromSql(eq(Users.id, id));
|
||||
}
|
||||
|
|
@ -79,7 +84,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
orderBy,
|
||||
});
|
||||
|
||||
if (!found) return null;
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
return new User(found);
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +144,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
// If admin, add admin permissions
|
||||
.concat(this.data.isAdmin ? config.permissions.admin : [])
|
||||
.reduce((acc, permission) => {
|
||||
if (!acc.includes(permission)) acc.push(permission);
|
||||
if (!acc.includes(permission)) {
|
||||
acc.push(permission);
|
||||
}
|
||||
return acc;
|
||||
}, [] as RolePermissions[])
|
||||
);
|
||||
|
|
@ -220,11 +229,11 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
)[0];
|
||||
}
|
||||
|
||||
async save(): Promise<UserWithRelations> {
|
||||
save(): Promise<UserWithRelations> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
async updateFromRemote() {
|
||||
async updateFromRemote(): Promise<User> {
|
||||
if (!this.isRemote()) {
|
||||
throw new Error(
|
||||
"Cannot refetch a local user (they are not remote)",
|
||||
|
|
@ -233,16 +242,12 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
|
||||
const updated = await User.saveFromRemote(this.getUri());
|
||||
|
||||
if (!updated) {
|
||||
throw new Error("User not found after update");
|
||||
}
|
||||
|
||||
this.data = updated.data;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static async saveFromRemote(uri: string): Promise<User | null> {
|
||||
static async saveFromRemote(uri: string): Promise<User> {
|
||||
if (!URL.canParse(uri)) {
|
||||
throw new Error(`Invalid URI to parse ${uri}`);
|
||||
}
|
||||
|
|
@ -274,102 +279,25 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
emojis.push(await fetchEmoji(emoji));
|
||||
}
|
||||
|
||||
// Check if new user already exists
|
||||
const foundUser = await User.fromSql(eq(Users.uri, data.uri));
|
||||
|
||||
// If it exists, simply update it
|
||||
if (foundUser) {
|
||||
await foundUser.update({
|
||||
updatedAt: new Date().toISOString(),
|
||||
endpoints: {
|
||||
dislikes: data.dislikes,
|
||||
featured: data.featured,
|
||||
likes: data.likes,
|
||||
followers: data.followers,
|
||||
following: data.following,
|
||||
inbox: data.inbox,
|
||||
outbox: data.outbox,
|
||||
},
|
||||
avatar: data.avatar
|
||||
? Object.entries(data.avatar)[0][1].content
|
||||
: "",
|
||||
header: data.header
|
||||
? Object.entries(data.header)[0][1].content
|
||||
: "",
|
||||
displayName: data.display_name ?? "",
|
||||
note: getBestContentType(data.bio).content,
|
||||
publicKey: data.public_key.public_key,
|
||||
});
|
||||
|
||||
// Add emojis
|
||||
if (emojis.length > 0) {
|
||||
await db
|
||||
.delete(EmojiToUser)
|
||||
.where(eq(EmojiToUser.userId, foundUser.id));
|
||||
|
||||
await db.insert(EmojiToUser).values(
|
||||
emojis.map((emoji) => ({
|
||||
emojiId: emoji.id,
|
||||
userId: foundUser.id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
return foundUser;
|
||||
}
|
||||
|
||||
const newUser = (
|
||||
await db
|
||||
.insert(Users)
|
||||
.values({
|
||||
username: data.username,
|
||||
uri: data.uri,
|
||||
createdAt: new Date(data.created_at).toISOString(),
|
||||
endpoints: {
|
||||
dislikes: data.dislikes,
|
||||
featured: data.featured,
|
||||
likes: data.likes,
|
||||
followers: data.followers,
|
||||
following: data.following,
|
||||
inbox: data.inbox,
|
||||
outbox: data.outbox,
|
||||
},
|
||||
fields: data.fields ?? [],
|
||||
updatedAt: new Date(data.created_at).toISOString(),
|
||||
instanceId: instance.id,
|
||||
avatar: data.avatar
|
||||
? Object.entries(data.avatar)[0][1].content
|
||||
: "",
|
||||
header: data.header
|
||||
? Object.entries(data.header)[0][1].content
|
||||
: "",
|
||||
displayName: data.display_name ?? "",
|
||||
note: getBestContentType(data.bio).content,
|
||||
publicKey: data.public_key.public_key,
|
||||
source: {
|
||||
language: null,
|
||||
note: "",
|
||||
privacy: "public",
|
||||
sensitive: false,
|
||||
fields: [],
|
||||
},
|
||||
})
|
||||
.returning()
|
||||
)[0];
|
||||
const user = await User.fromLysand(data, instance);
|
||||
|
||||
// Add emojis to user
|
||||
if (emojis.length > 0) {
|
||||
await db.delete(EmojiToUser).where(eq(EmojiToUser.userId, user.id));
|
||||
|
||||
await db.insert(EmojiToUser).values(
|
||||
emojis.map((emoji) => ({
|
||||
emojiId: emoji.id,
|
||||
userId: newUser.id,
|
||||
userId: user.id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const finalUser = await User.fromId(newUser.id);
|
||||
const finalUser = await User.fromId(user.id);
|
||||
|
||||
if (!finalUser) return null;
|
||||
if (!finalUser) {
|
||||
throw new Error("Failed to save user from remote");
|
||||
}
|
||||
|
||||
// Add to Meilisearch
|
||||
await addUserToMeilisearch(finalUser);
|
||||
|
|
@ -377,17 +305,86 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
return finalUser;
|
||||
}
|
||||
|
||||
static async fromLysand(
|
||||
user: typeof EntityValidator.$User,
|
||||
instance: InferSelectModel<typeof Instances>,
|
||||
): Promise<User> {
|
||||
const data = {
|
||||
username: user.username,
|
||||
uri: user.uri,
|
||||
createdAt: new Date(user.created_at).toISOString(),
|
||||
endpoints: {
|
||||
dislikes: user.dislikes,
|
||||
featured: user.featured,
|
||||
likes: user.likes,
|
||||
followers: user.followers,
|
||||
following: user.following,
|
||||
inbox: user.inbox,
|
||||
outbox: user.outbox,
|
||||
},
|
||||
fields: user.fields ?? [],
|
||||
updatedAt: new Date(user.created_at).toISOString(),
|
||||
instanceId: instance.id,
|
||||
avatar: user.avatar
|
||||
? Object.entries(user.avatar)[0][1].content
|
||||
: "",
|
||||
header: user.header
|
||||
? Object.entries(user.header)[0][1].content
|
||||
: "",
|
||||
displayName: user.display_name ?? "",
|
||||
note: getBestContentType(user.bio).content,
|
||||
publicKey: user.public_key.public_key,
|
||||
source: {
|
||||
language: null,
|
||||
note: "",
|
||||
privacy: "public",
|
||||
sensitive: false,
|
||||
fields: [],
|
||||
},
|
||||
};
|
||||
|
||||
// Check if new user already exists
|
||||
|
||||
const foundUser = await User.fromSql(eq(Users.uri, user.uri));
|
||||
|
||||
// If it exists, simply update it
|
||||
if (foundUser) {
|
||||
await foundUser.update(data);
|
||||
|
||||
return foundUser;
|
||||
}
|
||||
|
||||
// Else, create a new user
|
||||
return await User.insert(data);
|
||||
}
|
||||
|
||||
public static async insert(
|
||||
data: InferInsertModel<typeof Users>,
|
||||
): Promise<User> {
|
||||
const inserted = (await db.insert(Users).values(data).returning())[0];
|
||||
|
||||
const user = await User.fromId(inserted.id);
|
||||
|
||||
if (!user) {
|
||||
throw new Error("Failed to insert user");
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
static async resolve(uri: string): Promise<User | null> {
|
||||
// Check if user not already in database
|
||||
const foundUser = await User.fromSql(eq(Users.uri, uri));
|
||||
|
||||
if (foundUser) return foundUser;
|
||||
if (foundUser) {
|
||||
return foundUser;
|
||||
}
|
||||
|
||||
// Check if URI is of a local user
|
||||
if (uri.startsWith(config.http.base_url)) {
|
||||
const uuid = uri.match(idValidator);
|
||||
|
||||
if (!uuid || !uuid[0]) {
|
||||
if (!uuid?.[0]) {
|
||||
throw new Error(
|
||||
`URI ${uri} is of a local user, but it could not be parsed`,
|
||||
);
|
||||
|
|
@ -405,11 +402,12 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
* @returns The raw URL for the user's avatar
|
||||
*/
|
||||
getAvatarUrl(config: Config) {
|
||||
if (!this.data.avatar)
|
||||
if (!this.data.avatar) {
|
||||
return (
|
||||
config.defaults.avatar ||
|
||||
`https://api.dicebear.com/8.x/${config.defaults.placeholder_style}/svg?seed=${this.data.username}`
|
||||
);
|
||||
}
|
||||
return this.data.avatar;
|
||||
}
|
||||
|
||||
|
|
@ -480,7 +478,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
|
||||
const finalUser = await User.fromId(newUser.id);
|
||||
|
||||
if (!finalUser) return null;
|
||||
if (!finalUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add to Meilisearch
|
||||
await addUserToMeilisearch(finalUser);
|
||||
|
|
@ -494,7 +494,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
* @returns The raw URL for the user's header
|
||||
*/
|
||||
getHeaderUrl(config: Config) {
|
||||
if (!this.data.header) return config.defaults.header || "";
|
||||
if (!this.data.header) {
|
||||
return config.defaults.header || "";
|
||||
}
|
||||
return this.data.header;
|
||||
}
|
||||
|
||||
|
|
@ -561,7 +563,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
}
|
||||
}
|
||||
|
||||
toAPI(isOwnAccount = false): APIAccount {
|
||||
toApi(isOwnAccount = false): apiAccount {
|
||||
const user = this.data;
|
||||
return {
|
||||
id: user.id,
|
||||
|
|
@ -578,7 +580,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
followers_count: user.followerCount,
|
||||
following_count: user.followingCount,
|
||||
statuses_count: user.statusCount,
|
||||
emojis: user.emojis.map((emoji) => emojiToAPI(emoji)),
|
||||
emojis: user.emojis.map((emoji) => emojiToApi(emoji)),
|
||||
fields: user.fields.map((field) => ({
|
||||
name: htmlToText(getBestContentType(field.key).content),
|
||||
value: getBestContentType(field.value).content,
|
||||
|
|
@ -625,7 +627,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
]
|
||||
: [],
|
||||
)
|
||||
.map((r) => r.toAPI()),
|
||||
.map((r) => r.toApi()),
|
||||
group: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -699,7 +701,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
};
|
||||
}
|
||||
|
||||
toMention(): APIMention {
|
||||
toMention(): apiMention {
|
||||
return {
|
||||
url: this.getUri(),
|
||||
username: this.data.username,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { join } from "node:path";
|
|||
import { redirect } from "@/response";
|
||||
import type { BunFile } from "bun";
|
||||
import { config } from "config-manager";
|
||||
import { retrieveUserFromToken } from "~/database/entities/User";
|
||||
import { retrieveUserFromToken } from "~/database/entities/user";
|
||||
import type { User } from "~/packages/database-interface/user";
|
||||
import type { LogManager, MultiLogManager } from "~/packages/log-manager";
|
||||
import { languages } from "./glitch-languages";
|
||||
|
||||
const handleManifestRequest = async () => {
|
||||
const handleManifestRequest = () => {
|
||||
const manifest = {
|
||||
id: "/home",
|
||||
name: config.instance.name,
|
||||
|
|
@ -99,7 +99,7 @@ const handleManifestRequest = async () => {
|
|||
|
||||
const handleSignInRequest = async (
|
||||
req: Request,
|
||||
path: string,
|
||||
_path: string,
|
||||
url: URL,
|
||||
user: User | null,
|
||||
accessToken: string,
|
||||
|
|
@ -156,7 +156,7 @@ const handleSignInRequest = async (
|
|||
);
|
||||
};
|
||||
|
||||
const handleSignOutRequest = async (req: Request) => {
|
||||
const handleSignOutRequest = (req: Request) => {
|
||||
if (req.method === "POST") {
|
||||
return redirect("/api/auth/mastodon-logout", 307);
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ const returnFile = async (file: BunFile, content?: string) => {
|
|||
};
|
||||
|
||||
const handleDefaultRequest = async (
|
||||
req: Request,
|
||||
_req: Request,
|
||||
path: string,
|
||||
user: User | null,
|
||||
accessToken: string,
|
||||
|
|
@ -198,10 +198,10 @@ const handleDefaultRequest = async (
|
|||
return null;
|
||||
};
|
||||
|
||||
const brandingTransforms = async (
|
||||
const brandingTransforms = (
|
||||
fileContents: string,
|
||||
accessToken: string,
|
||||
user: User | null,
|
||||
_accessToken: string,
|
||||
_user: User | null,
|
||||
) => {
|
||||
let newFileContents = fileContents;
|
||||
for (const server of config.frontend.glitch.server) {
|
||||
|
|
@ -287,7 +287,7 @@ const htmlTransforms = async (
|
|||
},
|
||||
accounts: user
|
||||
? {
|
||||
[user.id]: user.toAPI(true),
|
||||
[user.id]: user.toApi(true),
|
||||
}
|
||||
: {},
|
||||
media_attachments: {
|
||||
|
|
@ -327,7 +327,7 @@ const htmlTransforms = async (
|
|||
|
||||
export const handleGlitchRequest = async (
|
||||
req: Request,
|
||||
logger: LogManager | MultiLogManager,
|
||||
_logger: LogManager | MultiLogManager,
|
||||
): Promise<Response | null> => {
|
||||
const url = new URL(req.url);
|
||||
let path = url.pathname;
|
||||
|
|
@ -336,7 +336,9 @@ export const handleGlitchRequest = async (
|
|||
const user = await retrieveUserFromToken(accessToken ?? "");
|
||||
|
||||
// Strip leading /web from path
|
||||
if (path.startsWith("/web")) path = path.slice(4);
|
||||
if (path.startsWith("/web")) {
|
||||
path = path.slice(4);
|
||||
}
|
||||
|
||||
if (path === "/manifest") {
|
||||
return handleManifestRequest();
|
||||
|
|
|
|||
|
|
@ -5,19 +5,19 @@ import chalk from "chalk";
|
|||
import { config } from "config-manager";
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = "debug",
|
||||
INFO = "info",
|
||||
WARNING = "warning",
|
||||
ERROR = "error",
|
||||
CRITICAL = "critical",
|
||||
Debug = "debug",
|
||||
Info = "info",
|
||||
Warning = "warning",
|
||||
Error = "error",
|
||||
Critical = "critical",
|
||||
}
|
||||
|
||||
const logOrder = [
|
||||
LogLevel.DEBUG,
|
||||
LogLevel.INFO,
|
||||
LogLevel.WARNING,
|
||||
LogLevel.ERROR,
|
||||
LogLevel.CRITICAL,
|
||||
LogLevel.Debug,
|
||||
LogLevel.Info,
|
||||
LogLevel.Warning,
|
||||
LogLevel.Error,
|
||||
LogLevel.Critical,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -37,15 +37,15 @@ export class LogManager {
|
|||
|
||||
getLevelColor(level: LogLevel) {
|
||||
switch (level) {
|
||||
case LogLevel.DEBUG:
|
||||
case LogLevel.Debug:
|
||||
return chalk.blue;
|
||||
case LogLevel.INFO:
|
||||
case LogLevel.Info:
|
||||
return chalk.green;
|
||||
case LogLevel.WARNING:
|
||||
case LogLevel.Warning:
|
||||
return chalk.yellow;
|
||||
case LogLevel.ERROR:
|
||||
case LogLevel.Error:
|
||||
return chalk.red;
|
||||
case LogLevel.CRITICAL:
|
||||
case LogLevel.Critical:
|
||||
return chalk.bgRed;
|
||||
}
|
||||
}
|
||||
|
|
@ -79,8 +79,9 @@ export class LogManager {
|
|||
if (
|
||||
logOrder.indexOf(level) <
|
||||
logOrder.indexOf(config.logging.log_level as LogLevel)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.enableColors) {
|
||||
await this.write(
|
||||
|
|
@ -102,9 +103,8 @@ export class LogManager {
|
|||
}
|
||||
|
||||
private async write(text: string) {
|
||||
Bun.stdout.name;
|
||||
if (this.output === Bun.stdout) {
|
||||
await console.log(`${text}`);
|
||||
console.info(text);
|
||||
} else {
|
||||
if (!(await exists(this.output.name ?? ""))) {
|
||||
// Create file if it doesn't exist
|
||||
|
|
@ -128,17 +128,112 @@ export class LogManager {
|
|||
* @param error Error to log
|
||||
*/
|
||||
async logError(level: LogLevel, entity: string, error: Error) {
|
||||
error.stack && (await this.log(LogLevel.DEBUG, entity, error.stack));
|
||||
error.stack && (await this.log(LogLevel.Debug, entity, error.stack));
|
||||
await this.log(level, entity, error.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the headers of a request
|
||||
* @param req Request to log
|
||||
*/
|
||||
public logHeaders(req: Request): string {
|
||||
let string = " [Headers]\n";
|
||||
for (const [key, value] of req.headers.entries()) {
|
||||
string += ` ${key}: ${value}\n`;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the body of a request
|
||||
* @param req Request to log
|
||||
*/
|
||||
async logBody(req: Request): Promise<string> {
|
||||
let string = " [Body]\n";
|
||||
const contentType = req.headers.get("Content-Type");
|
||||
|
||||
if (contentType?.includes("application/json")) {
|
||||
string += await this.logJsonBody(req);
|
||||
} else if (
|
||||
contentType &&
|
||||
(contentType.includes("application/x-www-form-urlencoded") ||
|
||||
contentType.includes("multipart/form-data"))
|
||||
) {
|
||||
string += await this.logFormData(req);
|
||||
} else {
|
||||
const text = await req.text();
|
||||
string += ` ${text}\n`;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the JSON body of a request
|
||||
* @param req Request to log
|
||||
*/
|
||||
async logJsonBody(req: Request): Promise<string> {
|
||||
let string = "";
|
||||
try {
|
||||
const json = await req.clone().json();
|
||||
const stringified = JSON.stringify(json, null, 4)
|
||||
.split("\n")
|
||||
.map((line) => ` ${line}`)
|
||||
.join("\n");
|
||||
|
||||
string += `${stringified}\n`;
|
||||
} catch {
|
||||
string += ` [Invalid JSON] (raw: ${await req.clone().text()})\n`;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the form data of a request
|
||||
* @param req Request to log
|
||||
*/
|
||||
async logFormData(req: Request): Promise<string> {
|
||||
let string = "";
|
||||
const formData = await req.clone().formData();
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (value.toString().length < 300) {
|
||||
string += ` ${key}: ${value.toString()}\n`;
|
||||
} else {
|
||||
string += ` ${key}: <${value.toString().length} bytes>\n`;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a request to the output
|
||||
* @param req Request to log
|
||||
* @param ip IP of the request
|
||||
* @param logAllDetails Whether to log all details of the request
|
||||
*/
|
||||
async logRequest(req: Request, ip?: string, logAllDetails = false) {
|
||||
async logRequest(
|
||||
req: Request,
|
||||
ip?: string,
|
||||
logAllDetails = false,
|
||||
): Promise<void> {
|
||||
let string = ip ? `${ip}: ` : "";
|
||||
|
||||
string += `${req.method} ${req.url}`;
|
||||
|
||||
if (logAllDetails) {
|
||||
string += "\n";
|
||||
string += await this.logHeaders(req);
|
||||
string += await this.logBody(req);
|
||||
}
|
||||
await this.log(LogLevel.Info, "Request", string);
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs a request to the output
|
||||
* @param req Request to log
|
||||
* @param ip IP of the request
|
||||
* @param logAllDetails Whether to log all details of the request
|
||||
*/
|
||||
/**async logRequest(req: Request, ip?: string, logAllDetails = false) {
|
||||
let string = ip ? `${ip}: ` : "";
|
||||
|
||||
string += `${req.method} ${req.url}`;
|
||||
|
|
@ -153,9 +248,9 @@ export class LogManager {
|
|||
|
||||
// Pretty print body
|
||||
string += " [Body]\n";
|
||||
const content_type = req.headers.get("Content-Type");
|
||||
const contentType = req.headers.get("Content-Type");
|
||||
|
||||
if (content_type?.includes("application/json")) {
|
||||
if (contentType?.includes("application/json")) {
|
||||
try {
|
||||
const json = await req.clone().json();
|
||||
const stringified = JSON.stringify(json, null, 4)
|
||||
|
|
@ -170,9 +265,9 @@ export class LogManager {
|
|||
.text()})\n`;
|
||||
}
|
||||
} else if (
|
||||
content_type &&
|
||||
(content_type.includes("application/x-www-form-urlencoded") ||
|
||||
content_type.includes("multipart/form-data"))
|
||||
contentType &&
|
||||
(contentType.includes("application/x-www-form-urlencoded") ||
|
||||
contentType.includes("multipart/form-data"))
|
||||
) {
|
||||
const formData = await req.clone().formData();
|
||||
for (const [key, value] of formData.entries()) {
|
||||
|
|
@ -189,8 +284,8 @@ export class LogManager {
|
|||
string += ` ${text}\n`;
|
||||
}
|
||||
}
|
||||
await this.log(LogLevel.INFO, "Request", string);
|
||||
}
|
||||
await this.log(LogLevel.Info, "Request", string);
|
||||
} */
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ describe("LogManager", () => {
|
|||
});
|
||||
*/
|
||||
it("should log message with timestamp", async () => {
|
||||
await logManager.log(LogLevel.INFO, "TestEntity", "Test message");
|
||||
await logManager.log(LogLevel.Info, "TestEntity", "Test message");
|
||||
expect(mockAppend).toHaveBeenCalledWith(
|
||||
mockOutput.name,
|
||||
expect.stringContaining("[INFO] TestEntity: Test message"),
|
||||
|
|
@ -45,7 +45,7 @@ describe("LogManager", () => {
|
|||
|
||||
it("should log message without timestamp", async () => {
|
||||
await logManager.log(
|
||||
LogLevel.INFO,
|
||||
LogLevel.Info,
|
||||
"TestEntity",
|
||||
"Test message",
|
||||
false,
|
||||
|
|
@ -56,9 +56,10 @@ describe("LogManager", () => {
|
|||
);
|
||||
});
|
||||
|
||||
// biome-ignore lint/suspicious/noSkippedTests: I need to fix this :sob:
|
||||
test.skip("should write to stdout", async () => {
|
||||
logManager = new LogManager(Bun.stdout);
|
||||
await logManager.log(LogLevel.INFO, "TestEntity", "Test message");
|
||||
await logManager.log(LogLevel.Info, "TestEntity", "Test message");
|
||||
|
||||
const writeMock = jest.fn();
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ describe("LogManager", () => {
|
|||
|
||||
it("should log error message", async () => {
|
||||
const error = new Error("Test error");
|
||||
await logManager.logError(LogLevel.ERROR, "TestEntity", error);
|
||||
await logManager.logError(LogLevel.Error, "TestEntity", error);
|
||||
expect(mockAppend).toHaveBeenCalledWith(
|
||||
mockOutput.name,
|
||||
expect.stringContaining("[ERROR] TestEntity: Test error"),
|
||||
|
|
@ -191,10 +192,10 @@ describe("MultiLogManager", () => {
|
|||
});
|
||||
|
||||
it("should log message to all logManagers", async () => {
|
||||
await multiLogManager.log(LogLevel.INFO, "TestEntity", "Test message");
|
||||
await multiLogManager.log(LogLevel.Info, "TestEntity", "Test message");
|
||||
expect(mockLog).toHaveBeenCalledTimes(2);
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
LogLevel.INFO,
|
||||
LogLevel.Info,
|
||||
"TestEntity",
|
||||
"Test message",
|
||||
true,
|
||||
|
|
@ -203,10 +204,10 @@ describe("MultiLogManager", () => {
|
|||
|
||||
it("should log error to all logManagers", async () => {
|
||||
const error = new Error("Test error");
|
||||
await multiLogManager.logError(LogLevel.ERROR, "TestEntity", error);
|
||||
await multiLogManager.logError(LogLevel.Error, "TestEntity", error);
|
||||
expect(mockLogError).toHaveBeenCalledTimes(2);
|
||||
expect(mockLogError).toHaveBeenCalledWith(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"TestEntity",
|
||||
error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { Config } from "config-manager";
|
|||
import { MediaConverter } from "./media-converter";
|
||||
|
||||
export enum MediaBackendType {
|
||||
LOCAL = "local",
|
||||
Local = "local",
|
||||
S3 = "s3",
|
||||
}
|
||||
|
||||
|
|
@ -35,12 +35,12 @@ export class MediaBackend {
|
|||
public backend: MediaBackendType,
|
||||
) {}
|
||||
|
||||
static async fromBackendType(
|
||||
public static fromBackendType(
|
||||
backend: MediaBackendType,
|
||||
config: Config,
|
||||
): Promise<MediaBackend> {
|
||||
): MediaBackend {
|
||||
switch (backend) {
|
||||
case MediaBackendType.LOCAL:
|
||||
case MediaBackendType.Local:
|
||||
return new LocalMediaBackend(config);
|
||||
case MediaBackendType.S3:
|
||||
return new S3MediaBackend(config);
|
||||
|
|
@ -64,15 +64,15 @@ export class MediaBackend {
|
|||
* @returns The file as a File object
|
||||
*/
|
||||
public getFileByHash(
|
||||
file: string,
|
||||
databaseHashFetcher: (sha256: string) => Promise<string>,
|
||||
_file: string,
|
||||
_databaseHashFetcher: (sha256: string) => Promise<string>,
|
||||
): Promise<File | null> {
|
||||
return Promise.reject(
|
||||
new Error("Do not call MediaBackend directly: use a subclass"),
|
||||
);
|
||||
}
|
||||
|
||||
public deleteFileByUrl(url: string): Promise<void> {
|
||||
public deleteFileByUrl(_url: string): Promise<void> {
|
||||
return Promise.reject(
|
||||
new Error("Do not call MediaBackend directly: use a subclass"),
|
||||
);
|
||||
|
|
@ -83,7 +83,7 @@ export class MediaBackend {
|
|||
* @param filename File name
|
||||
* @returns The file as a File object
|
||||
*/
|
||||
public getFile(filename: string): Promise<File | null> {
|
||||
public getFile(_filename: string): Promise<File | null> {
|
||||
return Promise.reject(
|
||||
new Error("Do not call MediaBackend directly: use a subclass"),
|
||||
);
|
||||
|
|
@ -94,7 +94,7 @@ export class MediaBackend {
|
|||
* @param file File to add
|
||||
* @returns Metadata about the uploaded file
|
||||
*/
|
||||
public addFile(file: File): Promise<UploadedFileMetadata> {
|
||||
public addFile(_file: File): Promise<UploadedFileMetadata> {
|
||||
return Promise.reject(
|
||||
new Error("Do not call MediaBackend directly: use a subclass"),
|
||||
);
|
||||
|
|
@ -103,7 +103,7 @@ export class MediaBackend {
|
|||
|
||||
export class LocalMediaBackend extends MediaBackend {
|
||||
constructor(config: Config) {
|
||||
super(config, MediaBackendType.LOCAL);
|
||||
super(config, MediaBackendType.Local);
|
||||
}
|
||||
|
||||
public async addFile(file: File) {
|
||||
|
|
@ -164,7 +164,9 @@ export class LocalMediaBackend extends MediaBackend {
|
|||
): Promise<File | null> {
|
||||
const filename = await databaseHashFetcher(hash);
|
||||
|
||||
if (!filename) return null;
|
||||
if (!filename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getFile(filename);
|
||||
}
|
||||
|
|
@ -174,7 +176,9 @@ export class LocalMediaBackend extends MediaBackend {
|
|||
`${this.config.media.local_uploads_folder}/${filename}`,
|
||||
);
|
||||
|
||||
if (!(await file.exists())) return null;
|
||||
if (!(await file.exists())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new File([await file.arrayBuffer()], filename, {
|
||||
type: file.type,
|
||||
|
|
@ -243,7 +247,9 @@ export class S3MediaBackend extends MediaBackend {
|
|||
): Promise<File | null> {
|
||||
const filename = await databaseHashFetcher(hash);
|
||||
|
||||
if (!filename) return null;
|
||||
if (!filename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getFile(filename);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ describe("MediaBackend", () => {
|
|||
describe("fromBackendType", () => {
|
||||
it("should return a LocalMediaBackend instance for LOCAL backend type", async () => {
|
||||
const backend = await MediaBackend.fromBackendType(
|
||||
MediaBackendType.LOCAL,
|
||||
MediaBackendType.Local,
|
||||
mockConfig,
|
||||
);
|
||||
expect(backend).toBeInstanceOf(LocalMediaBackend);
|
||||
|
|
@ -62,8 +62,8 @@ describe("MediaBackend", () => {
|
|||
it("should throw an error for unknown backend type", () => {
|
||||
expect(
|
||||
// @ts-expect-error This is a test
|
||||
MediaBackend.fromBackendType("unknown", mockConfig),
|
||||
).rejects.toThrow("Unknown backend type: unknown");
|
||||
() => MediaBackend.fromBackendType("unknown", mockConfig),
|
||||
).toThrow("Unknown backend type: unknown");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ describe("LocalMediaBackend", () => {
|
|||
|
||||
it("should initialize with correct type", () => {
|
||||
expect(localMediaBackend.getBackendType()).toEqual(
|
||||
MediaBackendType.LOCAL,
|
||||
MediaBackendType.Local,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const meta = applyConfig({
|
|||
});
|
||||
|
||||
export default (app: Hono) =>
|
||||
app.on(meta.allowedMethods, meta.route, async (context) => {
|
||||
app.on(meta.allowedMethods, meta.route, (_context) => {
|
||||
return jsonResponse({
|
||||
http: {
|
||||
bind: config.http.bind,
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ describe(meta.route, () => {
|
|||
expect(response.headers.get("Set-Cookie")).toMatch(/jwt=[^;]+;/);
|
||||
});
|
||||
|
||||
describe("should reject invalid credentials", async () => {
|
||||
describe("should reject invalid credentials", () => {
|
||||
// Redirects to /oauth/authorize on invalid
|
||||
test("invalid email", async () => {
|
||||
const formData = new FormData();
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@ const returnError = (query: object, error: string, description: string) => {
|
|||
|
||||
// Add all data that is not undefined except email and password
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (key !== "email" && key !== "password" && value !== undefined)
|
||||
if (key !== "email" && key !== "password" && value !== undefined) {
|
||||
searchParams.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
searchParams.append("error", error);
|
||||
|
|
@ -107,14 +108,20 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
if (
|
||||
!user ||
|
||||
!(await Bun.password.verify(password, user.data.password || ""))
|
||||
)
|
||||
!(
|
||||
user &&
|
||||
(await Bun.password.verify(
|
||||
password,
|
||||
user.data.password || "",
|
||||
))
|
||||
)
|
||||
) {
|
||||
return returnError(
|
||||
context.req.query(),
|
||||
"invalid_grant",
|
||||
"Invalid identifier or password",
|
||||
);
|
||||
}
|
||||
|
||||
if (user.data.passwordResetToken) {
|
||||
return response(null, 302, {
|
||||
|
|
@ -163,8 +170,9 @@ export default (app: Hono) =>
|
|||
application: application.name,
|
||||
});
|
||||
|
||||
if (application.website)
|
||||
if (application.website) {
|
||||
searchParams.append("website", application.website);
|
||||
}
|
||||
|
||||
// Add all data that is not undefined except email and password
|
||||
for (const [key, value] of Object.entries(context.req.query())) {
|
||||
|
|
@ -172,8 +180,9 @@ export default (app: Hono) =>
|
|||
key !== "email" &&
|
||||
key !== "password" &&
|
||||
value !== undefined
|
||||
)
|
||||
) {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to OAuth authorize with JWT
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { TokenType } from "~/database/entities/Token";
|
||||
import { TokenType } from "~/database/entities/token";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Tokens, Users } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
|
@ -56,10 +56,16 @@ export default (app: Hono) =>
|
|||
const user = await User.fromSql(eq(Users.email, email));
|
||||
|
||||
if (
|
||||
!user ||
|
||||
!(await Bun.password.verify(password, user.data.password || ""))
|
||||
)
|
||||
!(
|
||||
user &&
|
||||
(await Bun.password.verify(
|
||||
password,
|
||||
user.data.password || "",
|
||||
))
|
||||
)
|
||||
) {
|
||||
return redirectToLogin("Invalid email or password");
|
||||
}
|
||||
|
||||
if (user.data.passwordResetToken) {
|
||||
return response(null, 302, {
|
||||
|
|
@ -82,7 +88,7 @@ export default (app: Hono) =>
|
|||
accessToken,
|
||||
code: code,
|
||||
scope: "read write follow push",
|
||||
tokenType: TokenType.BEARER,
|
||||
tokenType: TokenType.Bearer,
|
||||
applicationId: null,
|
||||
userId: user.id,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const meta = applyConfig({
|
|||
* Mastodon-FE logout route
|
||||
*/
|
||||
export default (app: Hono) =>
|
||||
app.on(meta.allowedMethods, meta.route, async () => {
|
||||
app.on(meta.allowedMethods, meta.route, () => {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
Location: "/",
|
||||
|
|
|
|||
|
|
@ -63,8 +63,9 @@ export default (app: Hono) =>
|
|||
)
|
||||
.limit(1);
|
||||
|
||||
if (!foundToken || foundToken.length <= 0)
|
||||
if (!foundToken || foundToken.length <= 0) {
|
||||
return redirectToLogin("Invalid code");
|
||||
}
|
||||
|
||||
// Redirect back to application
|
||||
return Response.redirect(`${redirect_uri}?code=${code}`, 302);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Relationship as APIRelationship } from "~/types/mastodon/relationship";
|
||||
import type { Relationship as apiRelationship } from "~/types/mastodon/relationship";
|
||||
import { meta } from "./block";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
|
|
@ -65,7 +65,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.blocking).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.blocking).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_BLOCKS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnBlocks,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -45,11 +45,15 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
user,
|
||||
|
|
@ -67,6 +71,6 @@ export default (app: Hono) =>
|
|||
})
|
||||
.where(eq(Relationships.id, foundRelationship.id));
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Relationship as APIRelationship } from "~/types/mastodon/relationship";
|
||||
import type { Relationship as apiRelationship } from "~/types/mastodon/relationship";
|
||||
import { meta } from "./follow";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
|
|
@ -73,7 +73,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.following).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.following).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import type { Hono } from "hono";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import {
|
||||
followRequestUser,
|
||||
getRelationshipToOtherUser,
|
||||
} from "~/database/entities/User";
|
||||
} from "~/database/entities/user";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
||||
|
|
@ -25,8 +25,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_FOLLOWS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnFollows,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -59,11 +59,15 @@ export default (app: Hono) =>
|
|||
const { user } = context.req.valid("header");
|
||||
const { reblogs, notify, languages } = context.req.valid("json");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
let relationship = await getRelationshipToOtherUser(
|
||||
user,
|
||||
|
|
@ -81,6 +85,6 @@ export default (app: Hono) =>
|
|||
);
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(relationship));
|
||||
return jsonResponse(relationshipToApi(relationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Account as APIAccount } from "~/types/mastodon/account";
|
||||
import type { Account as apiAccount } from "~/types/mastodon/account";
|
||||
import { meta } from "./followers";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||
|
|
@ -51,7 +51,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIAccount[];
|
||||
const data = (await response.json()) as apiAccount[];
|
||||
|
||||
expect(data).toBeInstanceOf(Array);
|
||||
expect(data.length).toBe(1);
|
||||
|
|
@ -93,7 +93,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response2.status).toBe(200);
|
||||
|
||||
const data = (await response2.json()) as APIAccount[];
|
||||
const data = (await response2.json()) as apiAccount[];
|
||||
|
||||
expect(data).toBeInstanceOf(Array);
|
||||
expect(data.length).toBe(0);
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.VIEW_ACCOUNT_FOLLOWS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ViewAccountFollows,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -55,7 +55,9 @@ export default (app: Hono) =>
|
|||
|
||||
// TODO: Add follower/following privacy settings
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const { objects, link } = await Timeline.getUserTimeline(
|
||||
and(
|
||||
|
|
@ -69,7 +71,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
return jsonResponse(
|
||||
await Promise.all(objects.map((object) => object.toAPI())),
|
||||
await Promise.all(objects.map((object) => object.toApi())),
|
||||
200,
|
||||
{
|
||||
Link: link,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Account as APIAccount } from "~/types/mastodon/account";
|
||||
import type { Account as apiAccount } from "~/types/mastodon/account";
|
||||
import { meta } from "./following";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||
|
|
@ -51,7 +51,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIAccount[];
|
||||
const data = (await response.json()) as apiAccount[];
|
||||
|
||||
expect(data).toBeInstanceOf(Array);
|
||||
expect(data.length).toBe(1);
|
||||
|
|
@ -93,7 +93,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response2.status).toBe(200);
|
||||
|
||||
const data = (await response2.json()) as APIAccount[];
|
||||
const data = (await response2.json()) as apiAccount[];
|
||||
|
||||
expect(data).toBeInstanceOf(Array);
|
||||
expect(data.length).toBe(0);
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.VIEW_ACCOUNT_FOLLOWS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ViewAccountFollows,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -52,7 +52,9 @@ export default (app: Hono) =>
|
|||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
// TODO: Add follower/following privacy settings
|
||||
|
||||
|
|
@ -68,7 +70,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
return jsonResponse(
|
||||
await Promise.all(objects.map((object) => object.toAPI())),
|
||||
await Promise.all(objects.map((object) => object.toApi())),
|
||||
200,
|
||||
{
|
||||
Link: link,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Account as APIAccount } from "~/types/mastodon/account";
|
||||
import type { Account as apiAccount } from "~/types/mastodon/account";
|
||||
import { meta } from "./index";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||
|
|
@ -58,7 +58,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIAccount;
|
||||
const data = (await response.json()) as apiAccount;
|
||||
expect(data).toMatchObject({
|
||||
id: users[0].id,
|
||||
username: users[0].data.username,
|
||||
|
|
@ -93,6 +93,6 @@ describe(meta.route, () => {
|
|||
icon: null,
|
||||
}),
|
||||
]),
|
||||
} satisfies APIAccount);
|
||||
} satisfies apiAccount);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: [],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.VIEW_ACCOUNTS],
|
||||
required: [RolePermissions.ViewAccounts],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -40,8 +40,10 @@ export default (app: Hono) =>
|
|||
|
||||
const foundUser = await User.fromId(id);
|
||||
|
||||
if (!foundUser) return errorResponse("User not found", 404);
|
||||
if (!foundUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
return jsonResponse(foundUser.toAPI(user?.id === foundUser.id));
|
||||
return jsonResponse(foundUser.toApi(user?.id === foundUser.id));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Relationship as APIRelationship } from "~/types/mastodon/relationship";
|
||||
import type { Relationship as apiRelationship } from "~/types/mastodon/relationship";
|
||||
import { meta } from "./mute";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
|
|
@ -73,7 +73,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.muting).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.muting).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_MUTES,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnMutes,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -57,11 +57,15 @@ export default (app: Hono) =>
|
|||
// TODO: Add duration support
|
||||
const { notifications } = context.req.valid("json");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
user,
|
||||
|
|
@ -85,6 +89,6 @@ export default (app: Hono) =>
|
|||
|
||||
// TODO: Implement duration
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_ACCOUNT,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnAccount,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -50,11 +50,15 @@ export default (app: Hono) =>
|
|||
const { user } = context.req.valid("header");
|
||||
const { comment } = context.req.valid("json");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
user,
|
||||
|
|
@ -70,6 +74,6 @@ export default (app: Hono) =>
|
|||
})
|
||||
.where(eq(Relationships.id, foundRelationship.id));
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_ACCOUNT,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnAccount,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -45,11 +45,15 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
user,
|
||||
|
|
@ -67,6 +71,6 @@ export default (app: Hono) =>
|
|||
})
|
||||
.where(eq(Relationships.id, foundRelationship.id));
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { and, eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_FOLLOWS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnFollows,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -45,11 +45,15 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
const { user: self } = context.req.valid("header");
|
||||
|
||||
if (!self) return errorResponse("Unauthorized", 401);
|
||||
if (!self) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
self,
|
||||
|
|
@ -81,6 +85,6 @@ export default (app: Hono) =>
|
|||
}
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Status as APIStatus } from "~/types/mastodon/status";
|
||||
import type { Status as apiStatus } from "~/types/mastodon/status";
|
||||
import { meta } from "./statuses";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||
|
|
@ -50,7 +50,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIStatus[];
|
||||
const data = (await response.json()) as apiStatus[];
|
||||
|
||||
expect(data.length).toBe(20);
|
||||
// Should have reblogs
|
||||
|
|
@ -77,7 +77,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIStatus[];
|
||||
const data = (await response.json()) as apiStatus[];
|
||||
|
||||
expect(data.length).toBe(20);
|
||||
// Should not have reblogs
|
||||
|
|
@ -121,7 +121,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIStatus[];
|
||||
const data = (await response.json()) as apiStatus[];
|
||||
|
||||
expect(data.length).toBe(20);
|
||||
// Should not have replies
|
||||
|
|
@ -145,7 +145,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIStatus[];
|
||||
const data = (await response.json()) as apiStatus[];
|
||||
|
||||
expect(data.length).toBe(0);
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response2.status).toBe(200);
|
||||
|
||||
const data2 = (await response2.json()) as APIStatus[];
|
||||
const data2 = (await response2.json()) as apiStatus[];
|
||||
|
||||
expect(data2.length).toBe(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:statuses"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.VIEW_NOTES, RolePermissions.VIEW_ACCOUNTS],
|
||||
required: [RolePermissions.ViewNotes, RolePermissions.ViewAccounts],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -69,7 +69,9 @@ export default (app: Hono) =>
|
|||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const {
|
||||
max_id,
|
||||
|
|
@ -103,7 +105,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
return jsonResponse(
|
||||
await Promise.all(objects.map((note) => note.toAPI(otherUser))),
|
||||
await Promise.all(objects.map((note) => note.toApi(otherUser))),
|
||||
200,
|
||||
{
|
||||
Link: link,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_BLOCKS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnBlocks,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -45,11 +45,15 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
user,
|
||||
|
|
@ -67,6 +71,6 @@ export default (app: Hono) =>
|
|||
.where(eq(Relationships.id, foundRelationship.id));
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_FOLLOWS,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnFollows,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -45,11 +45,15 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
const { user: self } = context.req.valid("header");
|
||||
|
||||
if (!self) return errorResponse("Unauthorized", 401);
|
||||
if (!self) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
self,
|
||||
|
|
@ -68,6 +72,6 @@ export default (app: Hono) =>
|
|||
.where(eq(Relationships.id, foundRelationship.id));
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Relationship as APIRelationship } from "~/types/mastodon/relationship";
|
||||
import type { Relationship as apiRelationship } from "~/types/mastodon/relationship";
|
||||
import { meta } from "./unmute";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
|
|
@ -82,7 +82,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.muting).toBe(false);
|
||||
});
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ describe(meta.route, () => {
|
|||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const relationship = (await response.json()) as APIRelationship;
|
||||
const relationship = (await response.json()) as apiRelationship;
|
||||
expect(relationship.muting).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_MUTES,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnMutes,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -45,11 +45,15 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
const { user: self } = context.req.valid("header");
|
||||
|
||||
if (!self) return errorResponse("Unauthorized", 401);
|
||||
if (!self) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const user = await User.fromId(id);
|
||||
|
||||
if (!user) return errorResponse("User not found", 404);
|
||||
if (!user) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
self,
|
||||
|
|
@ -69,6 +73,6 @@ export default (app: Hono) =>
|
|||
.where(eq(Relationships.id, foundRelationship.id));
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { relationshipToAPI } from "~/database/entities/Relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/User";
|
||||
import { relationshipToApi } from "~/database/entities/relationship";
|
||||
import { getRelationshipToOtherUser } from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,8 +23,8 @@ export const meta = applyConfig({
|
|||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_ACCOUNT,
|
||||
RolePermissions.VIEW_ACCOUNTS,
|
||||
RolePermissions.ManageOwnAccount,
|
||||
RolePermissions.ViewAccounts,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -45,11 +45,15 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
const { user: self } = context.req.valid("header");
|
||||
|
||||
if (!self) return errorResponse("Unauthorized", 401);
|
||||
if (!self) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const otherUser = await User.fromId(id);
|
||||
|
||||
if (!otherUser) return errorResponse("User not found", 404);
|
||||
if (!otherUser) {
|
||||
return errorResponse("User not found", 404);
|
||||
}
|
||||
|
||||
const foundRelationship = await getRelationshipToOtherUser(
|
||||
self,
|
||||
|
|
@ -67,6 +71,6 @@ export default (app: Hono) =>
|
|||
.where(eq(Relationships.id, foundRelationship.id));
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:follows"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
|
||||
required: [RolePermissions.ManageOwnFollows],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -41,7 +41,9 @@ export default (app: Hono) =>
|
|||
const { user: self } = context.req.valid("header");
|
||||
const { id: ids } = context.req.valid("query");
|
||||
|
||||
if (!self) return errorResponse("Unauthorized", 401);
|
||||
if (!self) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const idFollowerRelationships =
|
||||
await db.query.Relationships.findMany({
|
||||
|
|
@ -91,6 +93,6 @@ export default (app: Hono) =>
|
|||
),
|
||||
);
|
||||
|
||||
return jsonResponse(finalUsers.map((o) => o.toAPI()));
|
||||
return jsonResponse(finalUsers.map((o) => o.toApi()));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -105,12 +105,13 @@ export default (app: Hono) =>
|
|||
}
|
||||
|
||||
// Check if username is valid
|
||||
if (!username?.match(/^[a-z0-9_]+$/))
|
||||
if (!username?.match(/^[a-z0-9_]+$/)) {
|
||||
errors.details.username.push({
|
||||
error: "ERR_INVALID",
|
||||
description:
|
||||
"must only contain lowercase letters, numbers, and underscores",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if username doesnt match filters
|
||||
if (
|
||||
|
|
@ -125,25 +126,28 @@ export default (app: Hono) =>
|
|||
}
|
||||
|
||||
// Check if username is too long
|
||||
if ((username?.length ?? 0) > config.validation.max_username_size)
|
||||
if ((username?.length ?? 0) > config.validation.max_username_size) {
|
||||
errors.details.username.push({
|
||||
error: "ERR_TOO_LONG",
|
||||
description: `is too long (maximum is ${config.validation.max_username_size} characters)`,
|
||||
});
|
||||
}
|
||||
|
||||
// Check if username is too short
|
||||
if ((username?.length ?? 0) < 3)
|
||||
if ((username?.length ?? 0) < 3) {
|
||||
errors.details.username.push({
|
||||
error: "ERR_TOO_SHORT",
|
||||
description: "is too short (minimum is 3 characters)",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if username is reserved
|
||||
if (config.validation.username_blacklist.includes(username ?? ""))
|
||||
if (config.validation.username_blacklist.includes(username ?? "")) {
|
||||
errors.details.username.push({
|
||||
error: "ERR_RESERVED",
|
||||
description: "is reserved",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if username is taken
|
||||
if (await User.fromSql(eq(Users.username, username))) {
|
||||
|
|
@ -158,11 +162,12 @@ export default (app: Hono) =>
|
|||
!email?.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
)
|
||||
)
|
||||
) {
|
||||
errors.details.email.push({
|
||||
error: "ERR_INVALID",
|
||||
description: "must be a valid email address",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if email is blocked
|
||||
if (
|
||||
|
|
@ -171,37 +176,42 @@ export default (app: Hono) =>
|
|||
tempmailDomains.domains.includes(
|
||||
(email ?? "").split("@")[1],
|
||||
))
|
||||
)
|
||||
) {
|
||||
errors.details.email.push({
|
||||
error: "ERR_BLOCKED",
|
||||
description: "is from a blocked email provider",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if email is taken
|
||||
if (await User.fromSql(eq(Users.email, email)))
|
||||
if (await User.fromSql(eq(Users.email, email))) {
|
||||
errors.details.email.push({
|
||||
error: "ERR_TAKEN",
|
||||
description: "is already taken",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if agreement is accepted
|
||||
if (!agreement)
|
||||
if (!agreement) {
|
||||
errors.details.agreement.push({
|
||||
error: "ERR_ACCEPTED",
|
||||
description: "must be accepted",
|
||||
});
|
||||
}
|
||||
|
||||
if (!locale)
|
||||
if (!locale) {
|
||||
errors.details.locale.push({
|
||||
error: "ERR_BLANK",
|
||||
description: `can't be blank`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!ISO6391.validate(locale ?? ""))
|
||||
if (!ISO6391.validate(locale ?? "")) {
|
||||
errors.details.locale.push({
|
||||
error: "ERR_INVALID",
|
||||
description: "must be a valid ISO 639-1 code",
|
||||
});
|
||||
}
|
||||
|
||||
// If any errors are present, return them
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Account as APIAccount } from "~/types/mastodon/account";
|
||||
import type { Account as apiAccount } from "~/types/mastodon/account";
|
||||
import { meta } from "./index";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||
|
|
@ -29,7 +29,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIAccount[];
|
||||
const data = (await response.json()) as apiAccount[];
|
||||
expect(data).toEqual(
|
||||
expect.objectContaining({
|
||||
id: users[0].id,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
oneOrMore,
|
||||
} from "magic-regexp";
|
||||
import { z } from "zod";
|
||||
import { resolveWebFinger } from "~/database/entities/User";
|
||||
import { resolveWebFinger } from "~/database/entities/user";
|
||||
import { RolePermissions, Users } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
import { LogLevel } from "~/packages/log-manager";
|
||||
|
|
@ -33,7 +33,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: [],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.SEARCH],
|
||||
required: [RolePermissions.Search],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ export default (app: Hono) =>
|
|||
domain,
|
||||
).catch((e) => {
|
||||
dualLogger.logError(
|
||||
LogLevel.ERROR,
|
||||
LogLevel.Error,
|
||||
"WebFinger.Resolve",
|
||||
e as Error,
|
||||
);
|
||||
|
|
@ -92,7 +92,7 @@ export default (app: Hono) =>
|
|||
});
|
||||
|
||||
if (foundAccount) {
|
||||
return jsonResponse(foundAccount.toAPI());
|
||||
return jsonResponse(foundAccount.toApi());
|
||||
}
|
||||
|
||||
return errorResponse("Account not found", 404);
|
||||
|
|
@ -106,7 +106,7 @@ export default (app: Hono) =>
|
|||
const account = await User.fromSql(eq(Users.username, username));
|
||||
|
||||
if (account) {
|
||||
return jsonResponse(account.toAPI());
|
||||
return jsonResponse(account.toApi());
|
||||
}
|
||||
|
||||
return errorResponse(
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import type { Hono } from "hono";
|
|||
import { z } from "zod";
|
||||
import {
|
||||
createNewRelationship,
|
||||
relationshipToAPI,
|
||||
} from "~/database/entities/Relationship";
|
||||
relationshipToApi,
|
||||
} from "~/database/entities/relationship";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -23,7 +23,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:follows"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
|
||||
required: [RolePermissions.ManageOwnFollows],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -46,7 +46,9 @@ export default (app: Hono) =>
|
|||
|
||||
const ids = Array.isArray(id) ? id : [id];
|
||||
|
||||
if (!self) return errorResponse("Unauthorized", 401);
|
||||
if (!self) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const relationships = await db.query.Relationships.findMany({
|
||||
where: (relationship, { inArray, and, eq }) =>
|
||||
|
|
@ -62,7 +64,9 @@ export default (app: Hono) =>
|
|||
|
||||
for (const id of missingIds) {
|
||||
const user = await User.fromId(id);
|
||||
if (!user) continue;
|
||||
if (!user) {
|
||||
continue;
|
||||
}
|
||||
const relationship = await createNewRelationship(self, user);
|
||||
|
||||
relationships.push(relationship);
|
||||
|
|
@ -72,6 +76,6 @@ export default (app: Hono) =>
|
|||
(a, b) => ids.indexOf(a.subjectId) - ids.indexOf(b.subjectId),
|
||||
);
|
||||
|
||||
return jsonResponse(relationships.map((r) => relationshipToAPI(r)));
|
||||
return jsonResponse(relationships.map((r) => relationshipToApi(r)));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Account as APIAccount } from "~/types/mastodon/account";
|
||||
import type { Account as apiAccount } from "~/types/mastodon/account";
|
||||
import { meta } from "./index";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||
|
|
@ -29,7 +29,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = (await response.json()) as APIAccount[];
|
||||
const data = (await response.json()) as apiAccount[];
|
||||
expect(data).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from "magic-regexp";
|
||||
import stringComparison from "string-comparison";
|
||||
import { z } from "zod";
|
||||
import { resolveWebFinger } from "~/database/entities/User";
|
||||
import { resolveWebFinger } from "~/database/entities/user";
|
||||
import { RolePermissions, Users } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:accounts"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.SEARCH, RolePermissions.VIEW_ACCOUNTS],
|
||||
required: [RolePermissions.Search, RolePermissions.ViewAccounts],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -81,7 +81,9 @@ export default (app: Hono) =>
|
|||
context.req.valid("query");
|
||||
const { user: self } = context.req.valid("header");
|
||||
|
||||
if (!self && following) return errorResponse("Unauthorized", 401);
|
||||
if (!self && following) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const [username, host] = q.replace(/^@/, "").split("@");
|
||||
|
||||
|
|
@ -120,6 +122,6 @@ export default (app: Hono) =>
|
|||
|
||||
const result = indexOfCorrectSort.map((index) => accounts[index]);
|
||||
|
||||
return jsonResponse(result.map((acct) => acct.toAPI()));
|
||||
return jsonResponse(result.map((acct) => acct.toApi()));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import { MediaBackendType } from "media-manager";
|
|||
import type { MediaBackend } from "media-manager";
|
||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||
import { z } from "zod";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { type EmojiWithInstance, parseEmojis } from "~/database/entities/Emoji";
|
||||
import { contentToHtml } from "~/database/entities/Status";
|
||||
import { getUrl } from "~/database/entities/attachment";
|
||||
import { type EmojiWithInstance, parseEmojis } from "~/database/entities/emoji";
|
||||
import { contentToHtml } from "~/database/entities/status";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { EmojiToUser, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -29,7 +29,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["write:accounts"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_ACCOUNT],
|
||||
required: [RolePermissions.ManageOwnAccount],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -116,7 +116,9 @@ export default (app: Hono) =>
|
|||
fields_attributes,
|
||||
} = context.req.valid("form");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const self = user.data;
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ export default (app: Hono) =>
|
|||
let mediaManager: MediaBackend;
|
||||
|
||||
switch (config.media.backend as MediaBackendType) {
|
||||
case MediaBackendType.LOCAL:
|
||||
case MediaBackendType.Local:
|
||||
mediaManager = new LocalMediaBackend(config);
|
||||
break;
|
||||
case MediaBackendType.S3:
|
||||
|
|
@ -321,8 +323,10 @@ export default (app: Hono) =>
|
|||
});
|
||||
|
||||
const output = await User.fromId(self.id);
|
||||
if (!output) return errorResponse("Couldn't edit user", 500);
|
||||
if (!output) {
|
||||
return errorResponse("Couldn't edit user", 500);
|
||||
}
|
||||
|
||||
return jsonResponse(output.toAPI());
|
||||
return jsonResponse(output.toApi());
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ export default (app: Hono) =>
|
|||
meta.allowedMethods,
|
||||
meta.route,
|
||||
auth(meta.auth, meta.permissions),
|
||||
async (context) => {
|
||||
(context) => {
|
||||
// TODO: Add checks for disabled/unverified accounts
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
return jsonResponse(user.toAPI(true));
|
||||
return jsonResponse(user.toApi(true));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const meta = applyConfig({
|
|||
required: false,
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_APPS],
|
||||
required: [RolePermissions.ManageOwnApps],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { applyConfig, auth } from "@/api";
|
||||
import { errorResponse, jsonResponse } from "@/response";
|
||||
import type { Hono } from "hono";
|
||||
import { getFromToken } from "~/database/entities/Application";
|
||||
import { getFromToken } from "~/database/entities/application";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -15,7 +15,7 @@ export const meta = applyConfig({
|
|||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_APPS],
|
||||
required: [RolePermissions.ManageOwnApps],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -27,12 +27,18 @@ export default (app: Hono) =>
|
|||
async (context) => {
|
||||
const { user, token } = context.req.valid("header");
|
||||
|
||||
if (!token) return errorResponse("Unauthorized", 401);
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!token) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const application = await getFromToken(token);
|
||||
|
||||
if (!application) return errorResponse("Unauthorized", 401);
|
||||
if (!application) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
return jsonResponse({
|
||||
name: application.name,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:blocks"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_BLOCKS],
|
||||
required: [RolePermissions.ManageOwnBlocks],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -44,7 +44,9 @@ export default (app: Hono) =>
|
|||
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const { objects: blocks, link } = await Timeline.getUserTimeline(
|
||||
and(
|
||||
|
|
@ -58,7 +60,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
return jsonResponse(
|
||||
blocks.map((u) => u.toAPI()),
|
||||
blocks.map((u) => u.toApi()),
|
||||
200,
|
||||
{
|
||||
Link: link,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { applyConfig, auth } from "@/api";
|
||||
import { jsonResponse } from "@/response";
|
||||
import type { Hono } from "hono";
|
||||
import { emojiToAPI } from "~/database/entities/Emoji";
|
||||
import { emojiToApi } from "~/database/entities/emoji";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export const meta = applyConfig({
|
|||
required: false,
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.VIEW_EMOJIS],
|
||||
required: [RolePermissions.ViewEmojis],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ export default (app: Hono) =>
|
|||
});
|
||||
|
||||
return jsonResponse(
|
||||
await Promise.all(emojis.map((emoji) => emojiToAPI(emoji))),
|
||||
await Promise.all(emojis.map((emoji) => emojiToApi(emoji))),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import { zValidator } from "@hono/zod-validator";
|
|||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { emojiToAPI } from "~/database/entities/Emoji";
|
||||
import { getUrl } from "~/database/entities/attachment";
|
||||
import { emojiToApi } from "~/database/entities/emoji";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Emojis, RolePermissions } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
|
@ -29,10 +29,7 @@ export const meta = applyConfig({
|
|||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_EMOJIS,
|
||||
RolePermissions.VIEW_EMOJIS,
|
||||
],
|
||||
required: [RolePermissions.ManageOwnEmojis, RolePermissions.ViewEmojis],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -93,16 +90,18 @@ export default (app: Hono) =>
|
|||
},
|
||||
});
|
||||
|
||||
if (!emoji) return errorResponse("Emoji not found", 404);
|
||||
if (!emoji) {
|
||||
return errorResponse("Emoji not found", 404);
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
if (
|
||||
!user.hasPermission(RolePermissions.MANAGE_EMOJIS) &&
|
||||
!user.hasPermission(RolePermissions.ManageEmojis) &&
|
||||
emoji.ownerId !== user.data.id
|
||||
) {
|
||||
return jsonResponse(
|
||||
{
|
||||
error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.MANAGE_EMOJIS}' permission to manage global emojis`,
|
||||
error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.ManageEmojis}' permission to manage global emojis`,
|
||||
},
|
||||
403,
|
||||
);
|
||||
|
|
@ -133,10 +132,12 @@ export default (app: Hono) =>
|
|||
}
|
||||
|
||||
if (
|
||||
!form.shortcode &&
|
||||
!form.element &&
|
||||
!form.alt &&
|
||||
!form.category &&
|
||||
!(
|
||||
form.shortcode ||
|
||||
form.element ||
|
||||
form.alt ||
|
||||
form.category
|
||||
) &&
|
||||
form.global === undefined
|
||||
) {
|
||||
return errorResponse(
|
||||
|
|
@ -146,11 +147,11 @@ export default (app: Hono) =>
|
|||
}
|
||||
|
||||
if (
|
||||
!user.hasPermission(RolePermissions.MANAGE_EMOJIS) &&
|
||||
!user.hasPermission(RolePermissions.ManageEmojis) &&
|
||||
form.global
|
||||
) {
|
||||
return errorResponse(
|
||||
`Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can make an emoji global or not`,
|
||||
`Only users with the '${RolePermissions.ManageEmojis}' permission can make an emoji global or not`,
|
||||
401,
|
||||
);
|
||||
}
|
||||
|
|
@ -207,7 +208,7 @@ export default (app: Hono) =>
|
|||
)[0];
|
||||
|
||||
return jsonResponse(
|
||||
emojiToAPI({
|
||||
emojiToApi({
|
||||
...newEmoji,
|
||||
instance: null,
|
||||
}),
|
||||
|
|
@ -215,7 +216,7 @@ export default (app: Hono) =>
|
|||
}
|
||||
|
||||
case "GET": {
|
||||
return jsonResponse(emojiToAPI(emoji));
|
||||
return jsonResponse(emojiToApi(emoji));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import { errorResponse, jsonResponse } from "@/response";
|
|||
import { zValidator } from "@hono/zod-validator";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { emojiToAPI } from "~/database/entities/Emoji";
|
||||
import { getUrl } from "~/database/entities/attachment";
|
||||
import { emojiToApi } from "~/database/entities/emoji";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Emojis, RolePermissions } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
|
|
@ -28,10 +28,7 @@ export const meta = applyConfig({
|
|||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
required: [
|
||||
RolePermissions.MANAGE_OWN_EMOJIS,
|
||||
RolePermissions.VIEW_EMOJIS,
|
||||
],
|
||||
required: [RolePermissions.ManageOwnEmojis, RolePermissions.ViewEmojis],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -79,9 +76,9 @@ export default (app: Hono) =>
|
|||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
if (!user.hasPermission(RolePermissions.MANAGE_EMOJIS) && global) {
|
||||
if (!user.hasPermission(RolePermissions.ManageEmojis) && global) {
|
||||
return errorResponse(
|
||||
`Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can upload global emojis`,
|
||||
`Only users with the '${RolePermissions.ManageEmojis}' permission can upload global emojis`,
|
||||
401,
|
||||
);
|
||||
}
|
||||
|
|
@ -148,7 +145,7 @@ export default (app: Hono) =>
|
|||
)[0];
|
||||
|
||||
return jsonResponse(
|
||||
emojiToAPI({
|
||||
emojiToApi({
|
||||
...emoji,
|
||||
instance: null,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const meta = applyConfig({
|
|||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_LIKES],
|
||||
required: [RolePermissions.ManageOwnLikes],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -43,7 +43,9 @@ export default (app: Hono) =>
|
|||
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const { objects: favourites, link } =
|
||||
await Timeline.getNoteTimeline(
|
||||
|
|
@ -60,7 +62,7 @@ export default (app: Hono) =>
|
|||
|
||||
return jsonResponse(
|
||||
await Promise.all(
|
||||
favourites.map(async (note) => note.toAPI(user)),
|
||||
favourites.map(async (note) => note.toApi(user)),
|
||||
),
|
||||
200,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import type { Hono } from "hono";
|
|||
import { z } from "zod";
|
||||
import {
|
||||
checkForBidirectionalRelationships,
|
||||
relationshipToAPI,
|
||||
} from "~/database/entities/Relationship";
|
||||
relationshipToApi,
|
||||
} from "~/database/entities/relationship";
|
||||
import {
|
||||
getRelationshipToOtherUser,
|
||||
sendFollowAccept,
|
||||
} from "~/database/entities/User";
|
||||
} from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -27,7 +27,7 @@ export const meta = applyConfig({
|
|||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
|
||||
required: [RolePermissions.ManageOwnFollows],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -46,13 +46,17 @@ export default (app: Hono) =>
|
|||
async (context) => {
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const { account_id } = context.req.valid("param");
|
||||
|
||||
const account = await User.fromId(account_id);
|
||||
|
||||
if (!account) return errorResponse("Account not found", 404);
|
||||
if (!account) {
|
||||
return errorResponse("Account not found", 404);
|
||||
}
|
||||
|
||||
// Check if there is a relationship on both sides
|
||||
await checkForBidirectionalRelationships(user, account);
|
||||
|
|
@ -90,8 +94,9 @@ export default (app: Hono) =>
|
|||
account,
|
||||
);
|
||||
|
||||
if (!foundRelationship)
|
||||
if (!foundRelationship) {
|
||||
return errorResponse("Relationship not found", 404);
|
||||
}
|
||||
|
||||
// Check if accepting remote follow
|
||||
if (account.isRemote()) {
|
||||
|
|
@ -99,6 +104,6 @@ export default (app: Hono) =>
|
|||
await sendFollowAccept(account, user);
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import type { Hono } from "hono";
|
|||
import { z } from "zod";
|
||||
import {
|
||||
checkForBidirectionalRelationships,
|
||||
relationshipToAPI,
|
||||
} from "~/database/entities/Relationship";
|
||||
relationshipToApi,
|
||||
} from "~/database/entities/relationship";
|
||||
import {
|
||||
getRelationshipToOtherUser,
|
||||
sendFollowReject,
|
||||
} from "~/database/entities/User";
|
||||
} from "~/database/entities/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
|
|
@ -27,7 +27,7 @@ export const meta = applyConfig({
|
|||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
|
||||
required: [RolePermissions.ManageOwnFollows],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -46,13 +46,17 @@ export default (app: Hono) =>
|
|||
async (context) => {
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const { account_id } = context.req.valid("param");
|
||||
|
||||
const account = await User.fromId(account_id);
|
||||
|
||||
if (!account) return errorResponse("Account not found", 404);
|
||||
if (!account) {
|
||||
return errorResponse("Account not found", 404);
|
||||
}
|
||||
|
||||
// Check if there is a relationship on both sides
|
||||
await checkForBidirectionalRelationships(user, account);
|
||||
|
|
@ -90,8 +94,9 @@ export default (app: Hono) =>
|
|||
account,
|
||||
);
|
||||
|
||||
if (!foundRelationship)
|
||||
if (!foundRelationship) {
|
||||
return errorResponse("Relationship not found", 404);
|
||||
}
|
||||
|
||||
// Check if rejecting remote follow
|
||||
if (account.isRemote()) {
|
||||
|
|
@ -99,6 +104,6 @@ export default (app: Hono) =>
|
|||
await sendFollowReject(account, user);
|
||||
}
|
||||
|
||||
return jsonResponse(relationshipToAPI(foundRelationship));
|
||||
return jsonResponse(relationshipToApi(foundRelationship));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const meta = applyConfig({
|
|||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_FOLLOWS],
|
||||
required: [RolePermissions.ManageOwnFollows],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -43,7 +43,9 @@ export default (app: Hono) =>
|
|||
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const { objects: followRequests, link } =
|
||||
await Timeline.getUserTimeline(
|
||||
|
|
@ -58,7 +60,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
return jsonResponse(
|
||||
followRequests.map((u) => u.toAPI()),
|
||||
followRequests.map((u) => u.toApi()),
|
||||
200,
|
||||
{
|
||||
Link: link,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ export const meta = applyConfig({
|
|||
});
|
||||
|
||||
export default (app: Hono) =>
|
||||
app.on(meta.allowedMethods, meta.route, async () => {
|
||||
app.on(meta.allowedMethods, meta.route, () => {
|
||||
return jsonResponse(config.frontend.settings);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import manifest from "~/package.json";
|
|||
import { config } from "~/packages/config-manager";
|
||||
import { Note } from "~/packages/database-interface/note";
|
||||
import { User } from "~/packages/database-interface/user";
|
||||
import type { Instance as APIInstance } from "~/types/mastodon/instance";
|
||||
import type { Instance as apiInstance } from "~/types/mastodon/instance";
|
||||
|
||||
export const meta = applyConfig({
|
||||
allowedMethods: ["GET"],
|
||||
|
|
@ -96,8 +96,8 @@ export default (app: Hono) =>
|
|||
id: p.id,
|
||||
})),
|
||||
},
|
||||
contact_account: contactAccount?.toAPI() || undefined,
|
||||
} satisfies APIInstance & {
|
||||
contact_account: contactAccount?.toApi() || undefined,
|
||||
} satisfies apiInstance & {
|
||||
banner: string | null;
|
||||
lysand_version: string;
|
||||
sso: {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default (app: Hono) =>
|
|||
meta.allowedMethods,
|
||||
meta.route,
|
||||
auth(meta.auth, meta.permissions),
|
||||
async (context) => {
|
||||
async (_context) => {
|
||||
return jsonResponse(
|
||||
config.signups.rules.map((rule, index) => ({
|
||||
id: String(index),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { Hono } from "hono";
|
|||
import { z } from "zod";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Markers, RolePermissions } from "~/drizzle/schema";
|
||||
import type { Marker as APIMarker } from "~/types/mastodon/marker";
|
||||
import type { Marker as apiMarker } from "~/types/mastodon/marker";
|
||||
|
||||
export const meta = applyConfig({
|
||||
allowedMethods: ["GET", "POST"],
|
||||
|
|
@ -20,7 +20,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:blocks"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_ACCOUNT],
|
||||
required: [RolePermissions.ManageOwnAccount],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ export default (app: Hono) =>
|
|||
return jsonResponse({});
|
||||
}
|
||||
|
||||
const markers: APIMarker = {
|
||||
const markers: apiMarker = {
|
||||
home: undefined,
|
||||
notifications: undefined,
|
||||
};
|
||||
|
|
@ -132,23 +132,23 @@ export default (app: Hono) =>
|
|||
|
||||
case "POST": {
|
||||
const {
|
||||
"home[last_read_id]": home_id,
|
||||
"notifications[last_read_id]": notifications_id,
|
||||
"home[last_read_id]": homeId,
|
||||
"notifications[last_read_id]": notificationsId,
|
||||
} = context.req.valid("query");
|
||||
|
||||
const markers: APIMarker = {
|
||||
const markers: apiMarker = {
|
||||
home: undefined,
|
||||
notifications: undefined,
|
||||
};
|
||||
|
||||
if (home_id) {
|
||||
if (homeId) {
|
||||
const insertedMarker = (
|
||||
await db
|
||||
.insert(Markers)
|
||||
.values({
|
||||
userId: user.id,
|
||||
timeline: "home",
|
||||
noteId: home_id,
|
||||
noteId: homeId,
|
||||
})
|
||||
.returning()
|
||||
)[0];
|
||||
|
|
@ -166,7 +166,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
markers.home = {
|
||||
last_read_id: home_id,
|
||||
last_read_id: homeId,
|
||||
version: totalCount[0].count,
|
||||
updated_at: new Date(
|
||||
insertedMarker.createdAt,
|
||||
|
|
@ -174,14 +174,14 @@ export default (app: Hono) =>
|
|||
};
|
||||
}
|
||||
|
||||
if (notifications_id) {
|
||||
if (notificationsId) {
|
||||
const insertedMarker = (
|
||||
await db
|
||||
.insert(Markers)
|
||||
.values({
|
||||
userId: user.id,
|
||||
timeline: "notifications",
|
||||
notificationId: notifications_id,
|
||||
notificationId: notificationsId,
|
||||
})
|
||||
.returning()
|
||||
)[0];
|
||||
|
|
@ -199,7 +199,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
markers.notifications = {
|
||||
last_read_id: notifications_id,
|
||||
last_read_id: notificationsId,
|
||||
version: totalCount[0].count,
|
||||
updated_at: new Date(
|
||||
insertedMarker.createdAt,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type { MediaBackend } from "media-manager";
|
|||
import { MediaBackendType } from "media-manager";
|
||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||
import { z } from "zod";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { getUrl } from "~/database/entities/attachment";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { Attachment } from "~/packages/database-interface/attachment";
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["write:media"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_MEDIA],
|
||||
required: [RolePermissions.ManageOwnMedia],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ export default (app: Hono) =>
|
|||
switch (context.req.method) {
|
||||
case "GET": {
|
||||
if (attachment.data.url) {
|
||||
return jsonResponse(attachment.toAPI());
|
||||
return jsonResponse(attachment.toApi());
|
||||
}
|
||||
return response(null, 206);
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ export default (app: Hono) =>
|
|||
let mediaManager: MediaBackend;
|
||||
|
||||
switch (config.media.backend as MediaBackendType) {
|
||||
case MediaBackendType.LOCAL:
|
||||
case MediaBackendType.Local:
|
||||
mediaManager = new LocalMediaBackend(config);
|
||||
break;
|
||||
case MediaBackendType.S3:
|
||||
|
|
@ -105,10 +105,10 @@ export default (app: Hono) =>
|
|||
thumbnailUrl,
|
||||
});
|
||||
|
||||
return jsonResponse(attachment.toAPI());
|
||||
return jsonResponse(attachment.toApi());
|
||||
}
|
||||
|
||||
return jsonResponse(attachment.toAPI());
|
||||
return jsonResponse(attachment.toApi());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import type { MediaBackend } from "media-manager";
|
|||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { getUrl } from "~/database/entities/attachment";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { Attachment } from "~/packages/database-interface/attachment";
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["write:media"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_MEDIA],
|
||||
required: [RolePermissions.ManageOwnMedia],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ export default (app: Hono) =>
|
|||
let mediaManager: MediaBackend;
|
||||
|
||||
switch (config.media.backend as MediaBackendType) {
|
||||
case MediaBackendType.LOCAL:
|
||||
case MediaBackendType.Local:
|
||||
mediaManager = new LocalMediaBackend(config);
|
||||
break;
|
||||
case MediaBackendType.S3:
|
||||
|
|
@ -141,6 +141,6 @@ export default (app: Hono) =>
|
|||
|
||||
// TODO: Add job to process videos and other media
|
||||
|
||||
return jsonResponse(newAttachment.toAPI());
|
||||
return jsonResponse(newAttachment.toApi());
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:mutes"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_MUTES],
|
||||
required: [RolePermissions.ManageOwnMutes],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -43,7 +43,9 @@ export default (app: Hono) =>
|
|||
context.req.valid("query");
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const { objects: mutes, link } = await Timeline.getUserTimeline(
|
||||
and(
|
||||
|
|
@ -57,7 +59,7 @@ export default (app: Hono) =>
|
|||
);
|
||||
|
||||
return jsonResponse(
|
||||
mutes.map((u) => u.toAPI()),
|
||||
mutes.map((u) => u.toApi()),
|
||||
200,
|
||||
{
|
||||
Link: link,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Notification as APINotification } from "~/types/mastodon/notification";
|
||||
import type { Notification as apiNotification } from "~/types/mastodon/notification";
|
||||
import { meta } from "./dismiss";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
let notifications: APINotification[] = [];
|
||||
let notifications: apiNotification[] = [];
|
||||
|
||||
// Create some test notifications: follow, favourite, reblog, mention
|
||||
beforeAll(async () => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["write:notifications"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
|
||||
required: [RolePermissions.ManageOwnNotifications],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -39,7 +39,9 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
|
||||
const { user } = context.req.valid("header");
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(Notifications)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Notification as APINotification } from "~/types/mastodon/notification";
|
||||
import type { Notification as apiNotification } from "~/types/mastodon/notification";
|
||||
import { meta } from "./index";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
let notifications: APINotification[] = [];
|
||||
let notifications: apiNotification[] = [];
|
||||
|
||||
// Create some test notifications: follow, favourite, reblog, mention
|
||||
beforeAll(async () => {
|
||||
|
|
@ -114,7 +114,7 @@ describe(meta.route, () => {
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const notification = (await response.json()) as APINotification;
|
||||
const notification = (await response.json()) as apiNotification;
|
||||
|
||||
expect(notification).toBeDefined();
|
||||
expect(notification.id).toBe(notifications[0].id);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@/response";
|
|||
import { zValidator } from "@hono/zod-validator";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { findManyNotifications } from "~/database/entities/Notification";
|
||||
import { findManyNotifications } from "~/database/entities/notification";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
|
||||
export const meta = applyConfig({
|
||||
|
|
@ -18,7 +18,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["read:notifications"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
|
||||
required: [RolePermissions.ManageOwnNotifications],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -38,7 +38,9 @@ export default (app: Hono) =>
|
|||
const { id } = context.req.valid("param");
|
||||
|
||||
const { user } = context.req.valid("header");
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const notification = (
|
||||
await findManyNotifications(
|
||||
|
|
@ -51,8 +53,9 @@ export default (app: Hono) =>
|
|||
)
|
||||
)[0];
|
||||
|
||||
if (!notification)
|
||||
if (!notification) {
|
||||
return errorResponse("Notification not found", 404);
|
||||
}
|
||||
|
||||
return jsonResponse(notification);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Notification as APINotification } from "~/types/mastodon/notification";
|
||||
import type { Notification as apiNotification } from "~/types/mastodon/notification";
|
||||
import { meta } from "./index";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
let notifications: APINotification[] = [];
|
||||
let notifications: apiNotification[] = [];
|
||||
|
||||
// Create some test notifications: follow, favourite, reblog, mention
|
||||
beforeAll(async () => {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["write:notifications"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
|
||||
required: [RolePermissions.ManageOwnNotifications],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -28,7 +28,9 @@ export default (app: Hono) =>
|
|||
auth(meta.auth, meta.permissions),
|
||||
async (context) => {
|
||||
const { user } = context.req.valid("header");
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(Notifications)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Notification as APINotification } from "~/types/mastodon/notification";
|
||||
import type { Notification as apiNotification } from "~/types/mastodon/notification";
|
||||
import { meta } from "./index";
|
||||
|
||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||
const statuses = await getTestStatuses(40, users[0]);
|
||||
let notifications: APINotification[] = [];
|
||||
let notifications: apiNotification[] = [];
|
||||
|
||||
// Create some test notifications
|
||||
beforeAll(async () => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const meta = applyConfig({
|
|||
oauthPermissions: ["write:notifications"],
|
||||
},
|
||||
permissions: {
|
||||
required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS],
|
||||
required: [RolePermissions.ManageOwnNotifications],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -38,7 +38,9 @@ export default (app: Hono) =>
|
|||
async (context) => {
|
||||
const { user } = context.req.valid("header");
|
||||
|
||||
if (!user) return errorResponse("Unauthorized", 401);
|
||||
if (!user) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const { "ids[]": ids } = context.req.valid("query");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { config } from "config-manager";
|
||||
import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils";
|
||||
import type { Notification as APINotification } from "~/types/mastodon/notification";
|
||||
import type { Notification as apiNotification } from "~/types/mastodon/notification";
|
||||
import { meta } from "./index";
|
||||
|
||||
const getFormData = (object: Record<string, string | number | boolean>) =>
|
||||
|
|
@ -113,7 +113,7 @@ describe(meta.route, () => {
|
|||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type")).toBe("application/json");
|
||||
|
||||
const objects = (await response.json()) as APINotification[];
|
||||
const objects = (await response.json()) as apiNotification[];
|
||||
|
||||
expect(objects.length).toBe(4);
|
||||
for (const [index, notification] of objects.entries()) {
|
||||
|
|
@ -165,7 +165,7 @@ describe(meta.route, () => {
|
|||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type")).toBe("application/json");
|
||||
|
||||
const objects = (await response.json()) as APINotification[];
|
||||
const objects = (await response.json()) as apiNotification[];
|
||||
|
||||
expect(objects.length).toBe(2);
|
||||
// There should be no element with a status with id of timeline[0].id
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue