mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Convert remaining routes to Drizzle
This commit is contained in:
parent
05e45ff5aa
commit
90d522eaa3
331
cli.ts
331
cli.ts
|
|
@ -10,11 +10,19 @@ import Table from "cli-table";
|
||||||
import extract from "extract-zip";
|
import extract from "extract-zip";
|
||||||
import { MediaBackend } from "media-manager";
|
import { MediaBackend } from "media-manager";
|
||||||
import { lookup } from "mime-types";
|
import { lookup } from "mime-types";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import { getUrl } from "~database/entities/Attachment";
|
import { getUrl } from "~database/entities/Attachment";
|
||||||
import { createNewLocalUser } from "~database/entities/User";
|
import {
|
||||||
|
createNewLocalUser,
|
||||||
|
findFirstUser,
|
||||||
|
findManyUsers,
|
||||||
|
type User,
|
||||||
|
} from "~database/entities/User";
|
||||||
import { CliParameterType } from "~packages/cli-parser/cli-builder.type";
|
import { CliParameterType } from "~packages/cli-parser/cli-builder.type";
|
||||||
import { config } from "~packages/config-manager";
|
import { config } from "~packages/config-manager";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { emoji, openIdAccount, status, user } from "~drizzle/schema";
|
||||||
|
import { type SQL, eq, inArray, isNotNull, isNull, like } from "drizzle-orm";
|
||||||
|
import { findFirstStatuses, findManyStatuses } from "~database/entities/Status";
|
||||||
|
|
||||||
const args = process.argv;
|
const args = process.argv;
|
||||||
|
|
||||||
|
|
@ -103,10 +111,9 @@ const cliBuilder = new CliBuilder([
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user already exists
|
// Check if user already exists
|
||||||
const user = await client.user.findFirst({
|
const user = await findFirstUser({
|
||||||
where: {
|
where: (user, { or, eq }) =>
|
||||||
OR: [{ username }, { email }],
|
or(eq(user.username, username), eq(user.email, email)),
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|
@ -136,7 +143,7 @@ const cliBuilder = new CliBuilder([
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.green("✓")} Created user ${chalk.blue(
|
`${chalk.green("✓")} Created user ${chalk.blue(
|
||||||
newUser.username,
|
newUser?.username,
|
||||||
)}${admin ? chalk.green(" (admin)") : ""}`,
|
)}${admin ? chalk.green(" (admin)") : ""}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -189,13 +196,11 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await client.user.findFirst({
|
const foundUser = await findFirstUser({
|
||||||
where: {
|
where: (user, { eq }) => eq(user.username, username),
|
||||||
username: username,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!foundUser) {
|
||||||
console.log(`${chalk.red("✗")} User not found`);
|
console.log(`${chalk.red("✗")} User not found`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +208,7 @@ const cliBuilder = new CliBuilder([
|
||||||
if (!args.noconfirm) {
|
if (!args.noconfirm) {
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
`Are you sure you want to delete user ${chalk.blue(
|
`Are you sure you want to delete user ${chalk.blue(
|
||||||
user.username,
|
foundUser.username,
|
||||||
)}?\n${chalk.red(
|
)}?\n${chalk.red(
|
||||||
chalk.bold(
|
chalk.bold(
|
||||||
"This is a destructive action and cannot be undone!",
|
"This is a destructive action and cannot be undone!",
|
||||||
|
|
@ -220,14 +225,12 @@ const cliBuilder = new CliBuilder([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.user.delete({
|
await db.delete(user).where(eq(user.id, foundUser.id));
|
||||||
where: {
|
|
||||||
id: user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.green("✓")} Deleted user ${chalk.blue(user.username)}`,
|
`${chalk.green("✓")} Deleted user ${chalk.blue(
|
||||||
|
foundUser.username,
|
||||||
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -308,21 +311,25 @@ const cliBuilder = new CliBuilder([
|
||||||
console.log(`${chalk.red("✗")} Invalid format`);
|
console.log(`${chalk.red("✗")} Invalid format`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
const users = filterObjects(
|
|
||||||
await client.user.findMany({
|
// @ts-ignore
|
||||||
where: {
|
let users: (User & {
|
||||||
isAdmin: admins || undefined,
|
instance?: {
|
||||||
},
|
baseUrl: string;
|
||||||
take: args.limit ?? 200,
|
};
|
||||||
include: {
|
})[] = await findManyUsers({
|
||||||
instance:
|
where: (user, { eq }) =>
|
||||||
fields.length === 0
|
admins ? eq(user.isAdmin, true) : undefined,
|
||||||
? true
|
limit: args.limit ?? 200,
|
||||||
: fields.includes("instance"),
|
});
|
||||||
},
|
|
||||||
}),
|
// If instance is not in fields, remove them
|
||||||
fields,
|
if (fields.length > 0 && !fields.includes("instance")) {
|
||||||
);
|
users = users.map((user) => ({
|
||||||
|
...user,
|
||||||
|
instance: undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (args.redact) {
|
if (args.redact) {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
|
|
@ -377,9 +384,10 @@ const cliBuilder = new CliBuilder([
|
||||||
isAdmin: () => chalk.red(user.isAdmin ? "Yes" : "No"),
|
isAdmin: () => chalk.red(user.isAdmin ? "Yes" : "No"),
|
||||||
instance: () =>
|
instance: () =>
|
||||||
chalk.blue(
|
chalk.blue(
|
||||||
user.instance ? user.instance.base_url : "Local",
|
user.instance ? user.instance.baseUrl : "Local",
|
||||||
),
|
),
|
||||||
createdAt: () => chalk.blue(user.createdAt?.toISOString()),
|
createdAt: () =>
|
||||||
|
chalk.blue(new Date(user.createdAt).toISOString()),
|
||||||
id: () => chalk.blue(user.id),
|
id: () => chalk.blue(user.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -497,25 +505,13 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queries: Prisma.UserWhereInput[] = [];
|
const users = await findManyUsers({
|
||||||
|
where: (user, { or, eq }) =>
|
||||||
for (const field of fields) {
|
or(
|
||||||
queries.push({
|
// @ts-expect-error
|
||||||
[field]: {
|
...fields.map((field) => eq(user[field], query)),
|
||||||
contains: query,
|
),
|
||||||
mode: caseSensitive ? "default" : "insensitive",
|
limit: Number(limit),
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const users = await client.user.findMany({
|
|
||||||
where: {
|
|
||||||
OR: queries,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
instance: true,
|
|
||||||
},
|
|
||||||
take: Number(limit),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (redact) {
|
if (redact) {
|
||||||
|
|
@ -560,7 +556,7 @@ const cliBuilder = new CliBuilder([
|
||||||
chalk.blue(user.displayName),
|
chalk.blue(user.displayName),
|
||||||
chalk.red(user.isAdmin ? "Yes" : "No"),
|
chalk.red(user.isAdmin ? "Yes" : "No"),
|
||||||
chalk.blue(
|
chalk.blue(
|
||||||
user.instanceId ? user.instance?.base_url : "Local",
|
user.instanceId ? user.instance?.baseUrl : "Local",
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -635,13 +631,8 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await client.user.findFirst({
|
const user = await findFirstUser({
|
||||||
where: {
|
where: (user, { eq }) => eq(user.username, username),
|
||||||
username: username,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
linkedOpenIdAccounts: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
@ -649,9 +640,15 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const linkedOpenIdAccounts = await db.query.openIdAccount.findMany({
|
||||||
user.linkedOpenIdAccounts.find((a) => a.issuerId === issuerId)
|
where: (account, { eq, and }) =>
|
||||||
) {
|
and(
|
||||||
|
eq(account.userId, user.id),
|
||||||
|
eq(account.issuerId, issuerId),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (linkedOpenIdAccounts.find((a) => a.issuerId === issuerId)) {
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.red("✗")} User ${chalk.blue(
|
`${chalk.red("✗")} User ${chalk.blue(
|
||||||
user.username,
|
user.username,
|
||||||
|
|
@ -661,18 +658,10 @@ const cliBuilder = new CliBuilder([
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect the OpenID account
|
// Connect the OpenID account
|
||||||
await client.user.update({
|
await db.insert(openIdAccount).values({
|
||||||
where: {
|
issuerId: issuerId,
|
||||||
id: user.id,
|
serverId: serverId,
|
||||||
},
|
userId: user.id,
|
||||||
data: {
|
|
||||||
linkedOpenIdAccounts: {
|
|
||||||
create: {
|
|
||||||
issuerId: issuerId,
|
|
||||||
serverId: serverId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
|
@ -723,13 +712,8 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await client.openIdAccount.findFirst({
|
const account = await db.query.openIdAccount.findFirst({
|
||||||
where: {
|
where: (account, { eq }) => eq(account.serverId, id),
|
||||||
serverId: id,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
User: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
|
|
@ -737,17 +721,28 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.openIdAccount.delete({
|
if (!account.userId) {
|
||||||
where: {
|
console.log(
|
||||||
id: account.id,
|
`${chalk.red("✗")} Account ${chalk.blue(
|
||||||
},
|
account.serverId,
|
||||||
|
)} is not connected to any user`,
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.id, account.userId ?? ""),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await db
|
||||||
|
.delete(openIdAccount)
|
||||||
|
.where(eq(openIdAccount.id, account.id));
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.green(
|
`${chalk.green(
|
||||||
"✓",
|
"✓",
|
||||||
)} Disconnected OpenID account from user ${chalk.blue(
|
)} Disconnected OpenID account from user ${chalk.blue(
|
||||||
account.User?.username,
|
user?.username,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -800,10 +795,8 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = await client.status.findFirst({
|
const note = await findFirstStatuses({
|
||||||
where: {
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
id: id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
|
|
@ -831,11 +824,7 @@ const cliBuilder = new CliBuilder([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.status.delete({
|
await db.delete(status).where(eq(status.id, note.id));
|
||||||
where: {
|
|
||||||
id: note.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.green("✓")} Deleted note ${chalk.blue(note.id)}`,
|
`${chalk.green("✓")} Deleted note ${chalk.blue(note.id)}`,
|
||||||
|
|
@ -971,30 +960,30 @@ const cliBuilder = new CliBuilder([
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let instanceIdQuery: Prisma.StatusWhereInput["instanceId"];
|
let instanceQuery: SQL<unknown> | undefined = isNull(
|
||||||
|
status.instanceId,
|
||||||
|
);
|
||||||
|
|
||||||
if (local && remote) {
|
if (local && remote) {
|
||||||
instanceIdQuery = undefined;
|
instanceQuery = undefined;
|
||||||
} else if (local) {
|
} else if (local) {
|
||||||
instanceIdQuery = null;
|
instanceQuery = isNull(status.instanceId);
|
||||||
} else if (remote) {
|
} else if (remote) {
|
||||||
instanceIdQuery = {
|
instanceQuery = isNotNull(status.instanceId);
|
||||||
not: null,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
instanceIdQuery = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = await client.status.findMany({
|
const notes = await findManyStatuses({
|
||||||
where: {
|
where: (status, { or, and }) =>
|
||||||
OR: queries,
|
and(
|
||||||
instanceId: instanceIdQuery,
|
or(
|
||||||
},
|
...fields.map((field) =>
|
||||||
include: {
|
// @ts-expect-error
|
||||||
author: true,
|
like(status[field], `%${query}%`),
|
||||||
instance: true,
|
),
|
||||||
},
|
),
|
||||||
take: Number(limit),
|
instanceQuery,
|
||||||
|
),
|
||||||
|
limit: Number(limit),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (redact) {
|
if (redact) {
|
||||||
|
|
@ -1038,9 +1027,11 @@ const cliBuilder = new CliBuilder([
|
||||||
chalk.green(note.content),
|
chalk.green(note.content),
|
||||||
chalk.blue(note.author.username),
|
chalk.blue(note.author.username),
|
||||||
chalk.red(
|
chalk.red(
|
||||||
note.instanceId ? note.instance?.base_url : "Yes",
|
note.author.instanceId
|
||||||
|
? note.author.instance?.baseUrl
|
||||||
|
: "Yes",
|
||||||
),
|
),
|
||||||
chalk.blue(note.createdAt.toISOString()),
|
chalk.blue(new Date(note.createdAt).toISOString()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1197,11 +1188,12 @@ const cliBuilder = new CliBuilder([
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if emoji already exists
|
// Check if emoji already exists
|
||||||
const existingEmoji = await client.emoji.findFirst({
|
const existingEmoji = await db.query.emoji.findFirst({
|
||||||
where: {
|
where: (emoji, { and, eq, isNull }) =>
|
||||||
shortcode: shortcode,
|
and(
|
||||||
instanceId: null,
|
eq(emoji.shortcode, shortcode),
|
||||||
},
|
isNull(emoji.instanceId),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingEmoji) {
|
if (existingEmoji) {
|
||||||
|
|
@ -1262,19 +1254,21 @@ const cliBuilder = new CliBuilder([
|
||||||
// Add the emoji
|
// Add the emoji
|
||||||
const content_type = lookup(newUrl) || "application/octet-stream";
|
const content_type = lookup(newUrl) || "application/octet-stream";
|
||||||
|
|
||||||
const emoji = await client.emoji.create({
|
const newEmoji = (
|
||||||
data: {
|
await db
|
||||||
shortcode: shortcode,
|
.insert(emoji)
|
||||||
url: newUrl,
|
.values({
|
||||||
visible_in_picker: true,
|
shortcode: shortcode,
|
||||||
content_type: content_type,
|
url: newUrl,
|
||||||
instanceId: null,
|
visibleInPicker: true,
|
||||||
},
|
contentType: content_type,
|
||||||
});
|
})
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.green("✓")} Created emoji ${chalk.blue(
|
`${chalk.green("✓")} Created emoji ${chalk.blue(
|
||||||
emoji.shortcode,
|
newEmoji.shortcode,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1303,7 +1297,7 @@ const cliBuilder = new CliBuilder([
|
||||||
name: "shortcode",
|
name: "shortcode",
|
||||||
type: CliParameterType.STRING,
|
type: CliParameterType.STRING,
|
||||||
description:
|
description:
|
||||||
"Shortcode of the emoji to delete (can add up to two wildcards *)",
|
"Shortcode of the emoji to delete (wildcards supported)",
|
||||||
needsValue: true,
|
needsValue: true,
|
||||||
positioned: true,
|
positioned: true,
|
||||||
},
|
},
|
||||||
|
|
@ -1339,35 +1333,12 @@ const cliBuilder = new CliBuilder([
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate up to one wildcard
|
const emojis = await db.query.emoji.findMany({
|
||||||
if (shortcode.split("*").length > 3) {
|
where: (emoji, { and, isNull, like }) =>
|
||||||
console.log(
|
and(
|
||||||
`${chalk.red(
|
like(emoji.shortcode, shortcode.replace(/\*/g, "%")),
|
||||||
"✗",
|
isNull(emoji.instanceId),
|
||||||
)} Invalid shortcode (can only have up to two wildcards)`,
|
),
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasWildcard = shortcode.includes("*");
|
|
||||||
const hasTwoWildcards = shortcode.split("*").length === 3;
|
|
||||||
|
|
||||||
const emojis = await client.emoji.findMany({
|
|
||||||
where: {
|
|
||||||
shortcode: {
|
|
||||||
startsWith: hasWildcard
|
|
||||||
? shortcode.split("*")[0]
|
|
||||||
: undefined,
|
|
||||||
endsWith: hasWildcard
|
|
||||||
? shortcode.split("*").at(-1)
|
|
||||||
: undefined,
|
|
||||||
contains: hasTwoWildcards
|
|
||||||
? shortcode.split("*")[1]
|
|
||||||
: undefined,
|
|
||||||
equals: hasWildcard ? undefined : shortcode,
|
|
||||||
},
|
|
||||||
instanceId: null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (emojis.length === 0) {
|
if (emojis.length === 0) {
|
||||||
|
|
@ -1406,13 +1377,12 @@ const cliBuilder = new CliBuilder([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.emoji.deleteMany({
|
await db.delete(emoji).where(
|
||||||
where: {
|
inArray(
|
||||||
id: {
|
emoji.id,
|
||||||
in: emojis.map((e) => e.id),
|
emojis.map((e) => e.id),
|
||||||
},
|
),
|
||||||
},
|
);
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.green(
|
`${chalk.green(
|
||||||
|
|
@ -1466,11 +1436,9 @@ const cliBuilder = new CliBuilder([
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = await client.emoji.findMany({
|
const emojis = await db.query.emoji.findMany({
|
||||||
where: {
|
where: (emoji, { isNull }) => isNull(emoji.instanceId),
|
||||||
instanceId: null,
|
limit: Number(limit),
|
||||||
},
|
|
||||||
take: Number(limit),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (format === "json") {
|
if (format === "json") {
|
||||||
|
|
@ -1746,11 +1714,12 @@ const cliBuilder = new CliBuilder([
|
||||||
).toString();
|
).toString();
|
||||||
|
|
||||||
// Check if emoji already exists
|
// Check if emoji already exists
|
||||||
const existingEmoji = await client.emoji.findFirst({
|
const existingEmoji = await db.query.emoji.findFirst({
|
||||||
where: {
|
where: (emoji, { and, eq, isNull }) =>
|
||||||
shortcode: shortcode,
|
and(
|
||||||
instanceId: null,
|
eq(emoji.shortcode, shortcode),
|
||||||
},
|
isNull(emoji.instanceId),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingEmoji) {
|
if (existingEmoji) {
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,11 @@ export const application = pgTable(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const applicationRelations = relations(application, ({ many }) => ({
|
||||||
|
tokens: many(token),
|
||||||
|
loginFlows: many(openIdLoginFlow),
|
||||||
|
}));
|
||||||
|
|
||||||
export const token = pgTable("Token", {
|
export const token = pgTable("Token", {
|
||||||
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
tokenType: text("token_type").notNull(),
|
tokenType: text("token_type").notNull(),
|
||||||
|
|
@ -337,6 +342,16 @@ export const openIdLoginFlow = pgTable("OpenIdLoginFlow", {
|
||||||
issuerId: text("issuerId").notNull(),
|
issuerId: text("issuerId").notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const openIdLoginFlowRelations = relations(
|
||||||
|
openIdLoginFlow,
|
||||||
|
({ one }) => ({
|
||||||
|
application: one(application, {
|
||||||
|
fields: [openIdLoginFlow.applicationId],
|
||||||
|
references: [application.id],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export const flag = pgTable("Flag", {
|
export const flag = pgTable("Flag", {
|
||||||
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
flagType: text("flagType").default("other").notNull(),
|
flagType: text("flagType").default("other").notNull(),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse } from "@response";
|
import { errorResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { findFirstStatuses, isViewableByUser } from "~database/entities/Status";
|
||||||
import { isViewableByUser } from "~database/entities/Status";
|
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -26,9 +24,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const status = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import { deleteLike } from "~database/entities/Like";
|
import { deleteLike } from "~database/entities/Like";
|
||||||
import {
|
import {
|
||||||
findFirstStatuses,
|
findFirstStatuses,
|
||||||
isViewableByUser,
|
isViewableByUser,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import type { APIStatus } from "~types/entities/status";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { statusToAPI } from "~database/entities/Status";
|
import { findFirstStatuses, statusToAPI } from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { db } from "~drizzle/db";
|
||||||
|
import { statusToUser } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["POST"],
|
allowedMethods: ["POST"],
|
||||||
|
|
@ -26,9 +27,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
let status = await client.status.findUnique({
|
const status = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if status exists
|
// Check if status exists
|
||||||
|
|
@ -37,21 +37,9 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
// Check if status is user's
|
// Check if status is user's
|
||||||
if (status.authorId !== user.id) return errorResponse("Unauthorized", 401);
|
if (status.authorId !== user.id) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
await client.user.update({
|
await db
|
||||||
where: { id: user.id },
|
.delete(statusToUser)
|
||||||
data: {
|
.where(and(eq(statusToUser.a, status.id), eq(statusToUser.b, user.id)));
|
||||||
pinnedNotes: {
|
|
||||||
disconnect: {
|
|
||||||
id: status.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
status = await client.status.findUnique({
|
|
||||||
where: { id },
|
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!status) return errorResponse("Record not found", 404);
|
if (!status) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { MeiliIndexType, meilisearch } from "@meilisearch";
|
import { MeiliIndexType, meilisearch } from "@meilisearch";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { and, eq, sql } from "drizzle-orm";
|
||||||
import { statusToAPI } from "~database/entities/Status";
|
import { findManyStatuses, statusToAPI } from "~database/entities/Status";
|
||||||
import {
|
import {
|
||||||
resolveUser,
|
findFirstUser,
|
||||||
|
findManyUsers,
|
||||||
resolveWebFinger,
|
resolveWebFinger,
|
||||||
userToAPI,
|
userToAPI,
|
||||||
} from "~database/entities/User";
|
} from "~database/entities/User";
|
||||||
import {
|
import { db } from "~drizzle/db";
|
||||||
statusAndUserRelations,
|
import { instance, user } from "~drizzle/schema";
|
||||||
userRelations,
|
|
||||||
} from "~database/entities/relations";
|
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -26,9 +25,6 @@ export const meta = applyConfig({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload new media
|
|
||||||
*/
|
|
||||||
export default apiRoute<{
|
export default apiRoute<{
|
||||||
q?: string;
|
q?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
|
@ -40,7 +36,7 @@ export default apiRoute<{
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
}>(async (req, matchedRoute, extraData) => {
|
}>(async (req, matchedRoute, extraData) => {
|
||||||
const { user } = extraData.auth;
|
const { user: self } = extraData.auth;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
q,
|
q,
|
||||||
|
|
@ -60,7 +56,7 @@ export default apiRoute<{
|
||||||
return errorResponse("Meilisearch is not enabled", 501);
|
return errorResponse("Meilisearch is not enabled", 501);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user && (resolve || offset)) {
|
if (!self && (resolve || offset)) {
|
||||||
return errorResponse(
|
return errorResponse(
|
||||||
"Cannot use resolve or offset without being authenticated",
|
"Cannot use resolve or offset without being authenticated",
|
||||||
401,
|
401,
|
||||||
|
|
@ -87,15 +83,26 @@ export default apiRoute<{
|
||||||
|
|
||||||
const [username, domain] = accountMatches[0].split("@");
|
const [username, domain] = accountMatches[0].split("@");
|
||||||
|
|
||||||
const account = await client.user.findFirst({
|
const accountId = (
|
||||||
where: {
|
await db
|
||||||
username,
|
.select({
|
||||||
instance: {
|
id: user.id,
|
||||||
base_url: domain,
|
})
|
||||||
},
|
.from(user)
|
||||||
},
|
.leftJoin(instance, eq(user.instanceId, instance.id))
|
||||||
include: userRelations,
|
.where(
|
||||||
});
|
and(
|
||||||
|
eq(user.username, username),
|
||||||
|
eq(instance.baseUrl, domain),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)[0]?.id;
|
||||||
|
|
||||||
|
const account = accountId
|
||||||
|
? await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.id, accountId),
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
if (account) {
|
if (account) {
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
|
|
@ -146,43 +153,41 @@ export default apiRoute<{
|
||||||
).hits;
|
).hits;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = await client.user.findMany({
|
const accounts = await findManyUsers({
|
||||||
where: {
|
where: (user, { and, eq, inArray }) =>
|
||||||
id: {
|
and(
|
||||||
in: accountResults.map((hit) => hit.id),
|
inArray(
|
||||||
},
|
user.id,
|
||||||
relationshipSubjects: {
|
accountResults.map((hit) => hit.id),
|
||||||
some: {
|
),
|
||||||
subjectId: user?.id,
|
self
|
||||||
following: following ? true : undefined,
|
? sql`EXISTS (SELECT 1 FROM Relationships WHERE Relationships.subjectId = ${
|
||||||
},
|
self?.id
|
||||||
},
|
} AND Relationships.following = ${
|
||||||
},
|
following ? true : false
|
||||||
orderBy: {
|
} AND Relationships.objectId = ${user.id})`
|
||||||
createdAt: "desc",
|
: undefined,
|
||||||
},
|
),
|
||||||
include: userRelations,
|
orderBy: (user, { desc }) => desc(user.createdAt),
|
||||||
});
|
});
|
||||||
|
|
||||||
const statuses = await client.status.findMany({
|
const statuses = await findManyStatuses({
|
||||||
where: {
|
where: (status, { and, eq, inArray }) =>
|
||||||
id: {
|
and(
|
||||||
in: statusResults.map((hit) => hit.id),
|
inArray(
|
||||||
},
|
status.id,
|
||||||
author: {
|
statusResults.map((hit) => hit.id),
|
||||||
relationshipSubjects: {
|
),
|
||||||
some: {
|
account_id ? eq(status.authorId, account_id) : undefined,
|
||||||
subjectId: user?.id,
|
self
|
||||||
following: following ? true : undefined,
|
? sql`EXISTS (SELECT 1 FROM Relationships WHERE Relationships.subjectId = ${
|
||||||
},
|
self?.id
|
||||||
},
|
} AND Relationships.following = ${
|
||||||
},
|
following ? true : false
|
||||||
authorId: account_id ? account_id : undefined,
|
} AND Relationships.objectId = ${status.authorId})`
|
||||||
},
|
: undefined,
|
||||||
orderBy: {
|
),
|
||||||
createdAt: "desc",
|
orderBy: (status, { desc }) => desc(status.createdAt),
|
||||||
},
|
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ import {
|
||||||
generateRandomCodeVerifier,
|
generateRandomCodeVerifier,
|
||||||
processDiscoveryResponse,
|
processDiscoveryResponse,
|
||||||
} from "oauth4webapi";
|
} from "oauth4webapi";
|
||||||
import { client } from "~database/datasource";
|
import { db } from "~drizzle/db";
|
||||||
|
import { openIdLoginFlow } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -60,20 +61,26 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
const codeVerifier = generateRandomCodeVerifier();
|
const codeVerifier = generateRandomCodeVerifier();
|
||||||
|
|
||||||
// Store into database
|
const application = await db.query.application.findFirst({
|
||||||
|
where: (application, { eq }) => eq(application.clientId, clientId),
|
||||||
const newFlow = await client.openIdLoginFlow.create({
|
|
||||||
data: {
|
|
||||||
codeVerifier,
|
|
||||||
application: {
|
|
||||||
connect: {
|
|
||||||
client_id: clientId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
issuerId,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return redirectToLogin("Invalid client_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store into database
|
||||||
|
const newFlow = (
|
||||||
|
await db
|
||||||
|
.insert(openIdLoginFlow)
|
||||||
|
.values({
|
||||||
|
codeVerifier,
|
||||||
|
applicationId: application.id,
|
||||||
|
issuerId,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
|
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
|
||||||
|
|
||||||
return Response.redirect(
|
return Response.redirect(
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ import {
|
||||||
userInfoRequest,
|
userInfoRequest,
|
||||||
validateAuthResponse,
|
validateAuthResponse,
|
||||||
} from "oauth4webapi";
|
} from "oauth4webapi";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import { TokenType } from "~database/entities/Token";
|
import { TokenType } from "~database/entities/Token";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { token } from "~drizzle/schema";
|
||||||
|
import { findFirstUser } from "~database/entities/User";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -46,11 +48,10 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
// Remove state query parameter from URL
|
// Remove state query parameter from URL
|
||||||
currentUrl.searchParams.delete("state");
|
currentUrl.searchParams.delete("state");
|
||||||
const issuerParam = matchedRoute.params.issuer;
|
const issuerParam = matchedRoute.params.issuer;
|
||||||
const flow = await client.openIdLoginFlow.findFirst({
|
|
||||||
where: {
|
const flow = await db.query.openIdLoginFlow.findFirst({
|
||||||
id: matchedRoute.query.flow,
|
where: (flow, { eq }) => eq(flow.id, matchedRoute.query.flow),
|
||||||
},
|
with: {
|
||||||
include: {
|
|
||||||
application: true,
|
application: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -142,15 +143,19 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = await client.user.findFirst({
|
const userId = (
|
||||||
where: {
|
await db.query.openIdAccount.findFirst({
|
||||||
linkedOpenIdAccounts: {
|
where: (account, { eq, and }) =>
|
||||||
some: {
|
and(eq(account.serverId, sub), eq(account.issuerId, issuer.id)),
|
||||||
serverId: sub,
|
})
|
||||||
issuerId: issuer.id,
|
)?.userId;
|
||||||
},
|
|
||||||
},
|
if (!userId) {
|
||||||
},
|
return redirectToLogin("No user found with that account");
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.id, userId),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
@ -161,31 +166,21 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
const code = randomBytes(32).toString("hex");
|
const code = randomBytes(32).toString("hex");
|
||||||
|
|
||||||
await client.application.update({
|
await db.insert(token).values({
|
||||||
where: { id: flow.application.id },
|
accessToken: randomBytes(64).toString("base64url"),
|
||||||
data: {
|
code: code,
|
||||||
tokens: {
|
scope: flow.application.scopes,
|
||||||
create: {
|
tokenType: TokenType.BEARER,
|
||||||
access_token: randomBytes(64).toString("base64url"),
|
userId: user.id,
|
||||||
code: code,
|
applicationId: flow.application.id,
|
||||||
scope: flow.application.scopes,
|
|
||||||
token_type: TokenType.BEARER,
|
|
||||||
user: {
|
|
||||||
connect: {
|
|
||||||
id: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redirect back to application
|
// Redirect back to application
|
||||||
return Response.redirect(
|
return Response.redirect(
|
||||||
`/oauth/redirect?${new URLSearchParams({
|
`/oauth/redirect?${new URLSearchParams({
|
||||||
redirect_uri: flow.application.redirect_uris,
|
redirect_uri: flow.application.redirectUris,
|
||||||
code,
|
code,
|
||||||
client_id: flow.application.client_id,
|
client_id: flow.application.clientId,
|
||||||
application: flow.application.name,
|
application: flow.application.name,
|
||||||
website: flow.application.website ?? "",
|
website: flow.application.website ?? "",
|
||||||
scope: flow.application.scopes,
|
scope: flow.application.scopes,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["POST"],
|
allowedMethods: ["POST"],
|
||||||
|
|
@ -34,30 +34,41 @@ export default apiRoute<{
|
||||||
400,
|
400,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!code || !redirect_uri || !client_id || !client_secret || !scope)
|
||||||
|
return errorResponse(
|
||||||
|
"Missing required parameters code, redirect_uri, client_id, client_secret, scope",
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
|
||||||
// Get associated token
|
// Get associated token
|
||||||
const token = await client.token.findFirst({
|
const application = await db.query.application.findFirst({
|
||||||
where: {
|
where: (application, { eq, and }) =>
|
||||||
code,
|
and(
|
||||||
application: {
|
eq(application.clientId, client_id),
|
||||||
client_id,
|
eq(application.secret, client_secret),
|
||||||
secret: client_secret,
|
eq(application.redirectUris, redirect_uri),
|
||||||
redirect_uris: redirect_uri,
|
eq(application.scopes, scope?.replaceAll("+", " ")),
|
||||||
scopes: scope?.replaceAll("+", " "),
|
),
|
||||||
},
|
});
|
||||||
scope: scope?.replaceAll("+", " "),
|
|
||||||
},
|
if (!application)
|
||||||
include: {
|
return errorResponse(
|
||||||
application: true,
|
"Invalid client credentials (missing applicaiton)",
|
||||||
},
|
401,
|
||||||
|
);
|
||||||
|
|
||||||
|
const token = await db.query.token.findFirst({
|
||||||
|
where: (token, { eq }) =>
|
||||||
|
eq(token.code, code) && eq(token.applicationId, application.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!token)
|
if (!token)
|
||||||
return errorResponse("Invalid access token or client credentials", 401);
|
return errorResponse("Invalid access token or client credentials", 401);
|
||||||
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
access_token: token.access_token,
|
access_token: token.accessToken,
|
||||||
token_type: token.token_type,
|
token_type: token.tokenType,
|
||||||
scope: token.scope,
|
scope: token.scope,
|
||||||
created_at: Number(token.created_at),
|
created_at: new Date(token.createdAt).getTime(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import type * as Lysand from "lysand-types";
|
import { findFirstStatuses, statusToLysand } from "~database/entities/Status";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import { statusToLysand } from "~database/entities/Status";
|
|
||||||
import { userToLysand } from "~database/entities/User";
|
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -21,11 +17,8 @@ export const meta = applyConfig({
|
||||||
export default apiRoute(async (req, matchedRoute, extraData) => {
|
export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
const uuid = matchedRoute.params.uuid;
|
const uuid = matchedRoute.params.uuid;
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const status = await findFirstStatuses({
|
||||||
where: {
|
where: (status, { eq }) => eq(status.id, uuid),
|
||||||
id: uuid,
|
|
||||||
},
|
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, response } from "@response";
|
import { errorResponse, response } from "@response";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
import type * as Lysand from "lysand-types";
|
import type * as Lysand from "lysand-types";
|
||||||
import { client } from "~database/datasource";
|
import { resolveStatus } from "~database/entities/Status";
|
||||||
import { objectToInboxRequest } from "~database/entities/Federation";
|
|
||||||
import { createNewStatus, resolveStatus } from "~database/entities/Status";
|
|
||||||
import {
|
import {
|
||||||
followAcceptToLysand,
|
findFirstUser,
|
||||||
getRelationshipToOtherUser,
|
getRelationshipToOtherUser,
|
||||||
resolveUser,
|
resolveUser,
|
||||||
sendFollowAccept,
|
sendFollowAccept,
|
||||||
} from "~database/entities/User";
|
} from "~database/entities/User";
|
||||||
import { userRelations } from "~database/entities/relations";
|
import { db } from "~drizzle/db";
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import { notification, relationship } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["POST"],
|
allowedMethods: ["POST"],
|
||||||
|
|
@ -28,19 +27,14 @@ export const meta = applyConfig({
|
||||||
export default apiRoute(async (req, matchedRoute, extraData) => {
|
export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
const uuid = matchedRoute.params.uuid;
|
const uuid = matchedRoute.params.uuid;
|
||||||
|
|
||||||
const user = await client.user.findUnique({
|
const user = await findFirstUser({
|
||||||
where: {
|
where: (user, { eq }) => eq(user.id, uuid),
|
||||||
id: uuid,
|
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await extraData.configManager.getConfig();
|
|
||||||
|
|
||||||
// Process incoming request
|
// Process incoming request
|
||||||
const body = extraData.parsedRequest as Lysand.Entity;
|
const body = extraData.parsedRequest as Lysand.Entity;
|
||||||
|
|
||||||
|
|
@ -119,8 +113,6 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(body);
|
|
||||||
|
|
||||||
// Add sent data to database
|
// Add sent data to database
|
||||||
switch (body.type) {
|
switch (body.type) {
|
||||||
case "Note": {
|
case "Note": {
|
||||||
|
|
@ -154,33 +146,31 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
return errorResponse("Author not found", 400);
|
return errorResponse("Author not found", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationship = await getRelationshipToOtherUser(
|
const foundRelationship = await getRelationshipToOtherUser(
|
||||||
account,
|
account,
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if already following
|
// Check if already following
|
||||||
if (relationship.following) {
|
if (foundRelationship.following) {
|
||||||
return response("Already following", 200);
|
return response("Already following", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.relationship.update({
|
await db
|
||||||
where: { id: relationship.id },
|
.update(relationship)
|
||||||
data: {
|
.set({
|
||||||
following: !user.isLocked,
|
following: !user.isLocked,
|
||||||
requested: user.isLocked,
|
requested: user.isLocked,
|
||||||
showingReblogs: true,
|
showingReblogs: true,
|
||||||
notifying: true,
|
notifying: true,
|
||||||
languages: [],
|
languages: [],
|
||||||
},
|
})
|
||||||
});
|
.where(eq(relationship.id, foundRelationship.id));
|
||||||
|
|
||||||
await client.notification.create({
|
await db.insert(notification).values({
|
||||||
data: {
|
accountId: account.id,
|
||||||
accountId: account.id,
|
type: user.isLocked ? "follow_request" : "follow",
|
||||||
type: user.isLocked ? "follow_request" : "follow",
|
notifiedId: user.id,
|
||||||
notifiedId: user.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user.isLocked) {
|
if (!user.isLocked) {
|
||||||
|
|
@ -203,24 +193,24 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
console.log(account);
|
console.log(account);
|
||||||
|
|
||||||
const relationship = await getRelationshipToOtherUser(
|
const foundRelationship = await getRelationshipToOtherUser(
|
||||||
user,
|
user,
|
||||||
account,
|
account,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(relationship);
|
console.log(foundRelationship);
|
||||||
|
|
||||||
if (!relationship.requested) {
|
if (!foundRelationship.requested) {
|
||||||
return response("There is no follow request to accept", 200);
|
return response("There is no follow request to accept", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.relationship.update({
|
await db
|
||||||
where: { id: relationship.id },
|
.update(relationship)
|
||||||
data: {
|
.set({
|
||||||
following: true,
|
following: true,
|
||||||
requested: false,
|
requested: false,
|
||||||
},
|
})
|
||||||
});
|
.where(eq(relationship.id, foundRelationship.id));
|
||||||
|
|
||||||
return response("Follow request accepted", 200);
|
return response("Follow request accepted", 200);
|
||||||
}
|
}
|
||||||
|
|
@ -233,22 +223,22 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
return errorResponse("Author not found", 400);
|
return errorResponse("Author not found", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationship = await getRelationshipToOtherUser(
|
const foundRelationship = await getRelationshipToOtherUser(
|
||||||
user,
|
user,
|
||||||
account,
|
account,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!relationship.requested) {
|
if (!foundRelationship.requested) {
|
||||||
return response("There is no follow request to reject", 200);
|
return response("There is no follow request to reject", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.relationship.update({
|
await db
|
||||||
where: { id: relationship.id },
|
.update(relationship)
|
||||||
data: {
|
.set({
|
||||||
requested: false,
|
requested: false,
|
||||||
following: false,
|
following: false,
|
||||||
},
|
})
|
||||||
});
|
.where(eq(relationship.id, foundRelationship.id));
|
||||||
|
|
||||||
return response("Follow request rejected", 200);
|
return response("Follow request rejected", 200);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { findFirstUser, userToLysand } from "~database/entities/User";
|
||||||
import { userToLysand } from "~database/entities/User";
|
|
||||||
import { userRelations } from "~database/entities/relations";
|
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -19,11 +17,8 @@ export const meta = applyConfig({
|
||||||
export default apiRoute(async (req, matchedRoute) => {
|
export default apiRoute(async (req, matchedRoute) => {
|
||||||
const uuid = matchedRoute.params.uuid;
|
const uuid = matchedRoute.params.uuid;
|
||||||
|
|
||||||
const user = await client.user.findUnique({
|
const user = await findFirstUser({
|
||||||
where: {
|
where: (user, { eq }) => eq(user.id, uuid),
|
||||||
id: uuid,
|
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { jsonResponse } from "@response";
|
import { jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { and, count, eq, inArray } from "drizzle-orm";
|
||||||
import { statusToLysand } from "~database/entities/Status";
|
import { findManyStatuses, statusToLysand } from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { db } from "~drizzle/db";
|
||||||
|
import { status } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -16,35 +17,34 @@ export const meta = applyConfig({
|
||||||
route: "/users/:uuid/outbox",
|
route: "/users/:uuid/outbox",
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* ActivityPub user outbox endpoint
|
|
||||||
*/
|
|
||||||
export default apiRoute(async (req, matchedRoute, extraData) => {
|
export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
const uuid = matchedRoute.params.uuid;
|
const uuid = matchedRoute.params.uuid;
|
||||||
const pageNumber = Number(matchedRoute.query.page) || 1;
|
const pageNumber = Number(matchedRoute.query.page) || 1;
|
||||||
const config = await extraData.configManager.getConfig();
|
const config = await extraData.configManager.getConfig();
|
||||||
const host = new URL(config.http.base_url).hostname;
|
const host = new URL(config.http.base_url).hostname;
|
||||||
|
|
||||||
const statuses = await client.status.findMany({
|
const statuses = await findManyStatuses({
|
||||||
where: {
|
where: (status, { eq, and, inArray }) =>
|
||||||
authorId: uuid,
|
and(
|
||||||
visibility: {
|
eq(status.authorId, uuid),
|
||||||
in: ["public", "unlisted"],
|
inArray(status.visibility, ["public", "unlisted"]),
|
||||||
},
|
),
|
||||||
},
|
offset: 20 * (pageNumber - 1),
|
||||||
take: 20,
|
limit: 20,
|
||||||
skip: 20 * (pageNumber - 1),
|
orderBy: (status, { desc }) => desc(status.createdAt),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalStatuses = await client.status.count({
|
const totalStatuses = await db
|
||||||
where: {
|
.select({
|
||||||
authorId: uuid,
|
count: count(),
|
||||||
visibility: {
|
})
|
||||||
in: ["public", "unlisted"],
|
.from(status)
|
||||||
},
|
.where(
|
||||||
},
|
and(
|
||||||
});
|
eq(status.authorId, uuid),
|
||||||
|
inArray(status.visibility, ["public", "unlisted"]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
first: `${host}/users/${uuid}/outbox?page=1`,
|
first: `${host}/users/${uuid}/outbox?page=1`,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { findFirstUser } from "~database/entities/User";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -45,11 +45,9 @@ export default apiRoute<{
|
||||||
/[0-9A-F]{8}-[0-9A-F]{4}-[7][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/i,
|
/[0-9A-F]{8}-[0-9A-F]{4}-[7][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/i,
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = await client.user.findUnique({
|
const user = await findFirstUser({
|
||||||
where: {
|
where: (user, { eq }) =>
|
||||||
id: isUuid ? requestedUser.split("@")[0] : undefined,
|
eq(isUuid ? user.id : user.username, requestedUser.split("@")[0]),
|
||||||
username: isUuid ? undefined : requestedUser.split("@")[0],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue