Convert remaining routes to Drizzle

This commit is contained in:
Jesse Wierzbinski 2024-04-13 14:07:05 -10:00
parent 05e45ff5aa
commit 90d522eaa3
No known key found for this signature in database
14 changed files with 383 additions and 422 deletions

323
cli.ts
View file

@ -10,11 +10,19 @@ import Table from "cli-table";
import extract from "extract-zip";
import { MediaBackend } from "media-manager";
import { lookup } from "mime-types";
import { client } from "~database/datasource";
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 { 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;
@ -103,10 +111,9 @@ const cliBuilder = new CliBuilder([
}
// Check if user already exists
const user = await client.user.findFirst({
where: {
OR: [{ username }, { email }],
},
const user = await findFirstUser({
where: (user, { or, eq }) =>
or(eq(user.username, username), eq(user.email, email)),
});
if (user) {
@ -136,7 +143,7 @@ const cliBuilder = new CliBuilder([
console.log(
`${chalk.green("✓")} Created user ${chalk.blue(
newUser.username,
newUser?.username,
)}${admin ? chalk.green(" (admin)") : ""}`,
);
@ -189,13 +196,11 @@ const cliBuilder = new CliBuilder([
return 1;
}
const user = await client.user.findFirst({
where: {
username: username,
},
const foundUser = await findFirstUser({
where: (user, { eq }) => eq(user.username, username),
});
if (!user) {
if (!foundUser) {
console.log(`${chalk.red("✗")} User not found`);
return 1;
}
@ -203,7 +208,7 @@ const cliBuilder = new CliBuilder([
if (!args.noconfirm) {
process.stdout.write(
`Are you sure you want to delete user ${chalk.blue(
user.username,
foundUser.username,
)}?\n${chalk.red(
chalk.bold(
"This is a destructive action and cannot be undone!",
@ -220,14 +225,12 @@ const cliBuilder = new CliBuilder([
}
}
await client.user.delete({
where: {
id: user.id,
},
});
await db.delete(user).where(eq(user.id, foundUser.id));
console.log(
`${chalk.green("✓")} Deleted user ${chalk.blue(user.username)}`,
`${chalk.green("✓")} Deleted user ${chalk.blue(
foundUser.username,
)}`,
);
return 0;
@ -308,21 +311,25 @@ const cliBuilder = new CliBuilder([
console.log(`${chalk.red("✗")} Invalid format`);
return 1;
}
const users = filterObjects(
await client.user.findMany({
where: {
isAdmin: admins || undefined,
},
take: args.limit ?? 200,
include: {
instance:
fields.length === 0
? true
: fields.includes("instance"),
},
}),
fields,
);
// @ts-ignore
let users: (User & {
instance?: {
baseUrl: string;
};
})[] = await findManyUsers({
where: (user, { eq }) =>
admins ? eq(user.isAdmin, true) : undefined,
limit: args.limit ?? 200,
});
// If instance is not in fields, remove them
if (fields.length > 0 && !fields.includes("instance")) {
users = users.map((user) => ({
...user,
instance: undefined,
}));
}
if (args.redact) {
for (const user of users) {
@ -377,9 +384,10 @@ const cliBuilder = new CliBuilder([
isAdmin: () => chalk.red(user.isAdmin ? "Yes" : "No"),
instance: () =>
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),
};
@ -497,25 +505,13 @@ const cliBuilder = new CliBuilder([
return 1;
}
const queries: Prisma.UserWhereInput[] = [];
for (const field of fields) {
queries.push({
[field]: {
contains: query,
mode: caseSensitive ? "default" : "insensitive",
},
});
}
const users = await client.user.findMany({
where: {
OR: queries,
},
include: {
instance: true,
},
take: Number(limit),
const users = await findManyUsers({
where: (user, { or, eq }) =>
or(
// @ts-expect-error
...fields.map((field) => eq(user[field], query)),
),
limit: Number(limit),
});
if (redact) {
@ -560,7 +556,7 @@ const cliBuilder = new CliBuilder([
chalk.blue(user.displayName),
chalk.red(user.isAdmin ? "Yes" : "No"),
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;
}
const user = await client.user.findFirst({
where: {
username: username,
},
include: {
linkedOpenIdAccounts: true,
},
const user = await findFirstUser({
where: (user, { eq }) => eq(user.username, username),
});
if (!user) {
@ -649,9 +640,15 @@ const cliBuilder = new CliBuilder([
return 1;
}
if (
user.linkedOpenIdAccounts.find((a) => a.issuerId === issuerId)
) {
const linkedOpenIdAccounts = await db.query.openIdAccount.findMany({
where: (account, { eq, and }) =>
and(
eq(account.userId, user.id),
eq(account.issuerId, issuerId),
),
});
if (linkedOpenIdAccounts.find((a) => a.issuerId === issuerId)) {
console.log(
`${chalk.red("✗")} User ${chalk.blue(
user.username,
@ -661,18 +658,10 @@ const cliBuilder = new CliBuilder([
}
// Connect the OpenID account
await client.user.update({
where: {
id: user.id,
},
data: {
linkedOpenIdAccounts: {
create: {
await db.insert(openIdAccount).values({
issuerId: issuerId,
serverId: serverId,
},
},
},
userId: user.id,
});
console.log(
@ -723,13 +712,8 @@ const cliBuilder = new CliBuilder([
return 1;
}
const account = await client.openIdAccount.findFirst({
where: {
serverId: id,
},
include: {
User: true,
},
const account = await db.query.openIdAccount.findFirst({
where: (account, { eq }) => eq(account.serverId, id),
});
if (!account) {
@ -737,17 +721,28 @@ const cliBuilder = new CliBuilder([
return 1;
}
await client.openIdAccount.delete({
where: {
id: account.id,
},
if (!account.userId) {
console.log(
`${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(
`${chalk.green(
"✓",
)} Disconnected OpenID account from user ${chalk.blue(
account.User?.username,
user?.username,
)}`,
);
@ -800,10 +795,8 @@ const cliBuilder = new CliBuilder([
return 1;
}
const note = await client.status.findFirst({
where: {
id: id,
},
const note = await findFirstStatuses({
where: (status, { eq }) => eq(status.id, id),
});
if (!note) {
@ -831,11 +824,7 @@ const cliBuilder = new CliBuilder([
}
}
await client.status.delete({
where: {
id: note.id,
},
});
await db.delete(status).where(eq(status.id, note.id));
console.log(
`${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) {
instanceIdQuery = undefined;
instanceQuery = undefined;
} else if (local) {
instanceIdQuery = null;
instanceQuery = isNull(status.instanceId);
} else if (remote) {
instanceIdQuery = {
not: null,
};
} else {
instanceIdQuery = undefined;
instanceQuery = isNotNull(status.instanceId);
}
const notes = await client.status.findMany({
where: {
OR: queries,
instanceId: instanceIdQuery,
},
include: {
author: true,
instance: true,
},
take: Number(limit),
const notes = await findManyStatuses({
where: (status, { or, and }) =>
and(
or(
...fields.map((field) =>
// @ts-expect-error
like(status[field], `%${query}%`),
),
),
instanceQuery,
),
limit: Number(limit),
});
if (redact) {
@ -1038,9 +1027,11 @@ const cliBuilder = new CliBuilder([
chalk.green(note.content),
chalk.blue(note.author.username),
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
const existingEmoji = await client.emoji.findFirst({
where: {
shortcode: shortcode,
instanceId: null,
},
const existingEmoji = await db.query.emoji.findFirst({
where: (emoji, { and, eq, isNull }) =>
and(
eq(emoji.shortcode, shortcode),
isNull(emoji.instanceId),
),
});
if (existingEmoji) {
@ -1262,19 +1254,21 @@ const cliBuilder = new CliBuilder([
// Add the emoji
const content_type = lookup(newUrl) || "application/octet-stream";
const emoji = await client.emoji.create({
data: {
const newEmoji = (
await db
.insert(emoji)
.values({
shortcode: shortcode,
url: newUrl,
visible_in_picker: true,
content_type: content_type,
instanceId: null,
},
});
visibleInPicker: true,
contentType: content_type,
})
.returning()
)[0];
console.log(
`${chalk.green("✓")} Created emoji ${chalk.blue(
emoji.shortcode,
newEmoji.shortcode,
)}`,
);
@ -1303,7 +1297,7 @@ const cliBuilder = new CliBuilder([
name: "shortcode",
type: CliParameterType.STRING,
description:
"Shortcode of the emoji to delete (can add up to two wildcards *)",
"Shortcode of the emoji to delete (wildcards supported)",
needsValue: true,
positioned: true,
},
@ -1339,35 +1333,12 @@ const cliBuilder = new CliBuilder([
return 1;
}
// Validate up to one wildcard
if (shortcode.split("*").length > 3) {
console.log(
`${chalk.red(
"✗",
)} 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,
},
const emojis = await db.query.emoji.findMany({
where: (emoji, { and, isNull, like }) =>
and(
like(emoji.shortcode, shortcode.replace(/\*/g, "%")),
isNull(emoji.instanceId),
),
});
if (emojis.length === 0) {
@ -1406,13 +1377,12 @@ const cliBuilder = new CliBuilder([
}
}
await client.emoji.deleteMany({
where: {
id: {
in: emojis.map((e) => e.id),
},
},
});
await db.delete(emoji).where(
inArray(
emoji.id,
emojis.map((e) => e.id),
),
);
console.log(
`${chalk.green(
@ -1466,11 +1436,9 @@ const cliBuilder = new CliBuilder([
return 0;
}
const emojis = await client.emoji.findMany({
where: {
instanceId: null,
},
take: Number(limit),
const emojis = await db.query.emoji.findMany({
where: (emoji, { isNull }) => isNull(emoji.instanceId),
limit: Number(limit),
});
if (format === "json") {
@ -1746,11 +1714,12 @@ const cliBuilder = new CliBuilder([
).toString();
// Check if emoji already exists
const existingEmoji = await client.emoji.findFirst({
where: {
shortcode: shortcode,
instanceId: null,
},
const existingEmoji = await db.query.emoji.findFirst({
where: (emoji, { and, eq, isNull }) =>
and(
eq(emoji.shortcode, shortcode),
isNull(emoji.instanceId),
),
});
if (existingEmoji) {

View file

@ -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", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
tokenType: text("token_type").notNull(),
@ -337,6 +342,16 @@ export const openIdLoginFlow = pgTable("OpenIdLoginFlow", {
issuerId: text("issuerId").notNull(),
});
export const openIdLoginFlowRelations = relations(
openIdLoginFlow,
({ one }) => ({
application: one(application, {
fields: [openIdLoginFlow.applicationId],
references: [application.id],
}),
}),
);
export const flag = pgTable("Flag", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
flagType: text("flagType").default("other").notNull(),

View file

@ -1,8 +1,6 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse } from "@response";
import { client } from "~database/datasource";
import { isViewableByUser } from "~database/entities/Status";
import { statusAndUserRelations } from "~database/entities/relations";
import { findFirstStatuses, isViewableByUser } from "~database/entities/Status";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -26,9 +24,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!user) return errorResponse("Unauthorized", 401);
const status = await client.status.findUnique({
where: { id },
include: statusAndUserRelations,
const status = await findFirstStatuses({
where: (status, { eq }) => eq(status.id, id),
});
// Check if user is authorized to view this status (if it's private)

View file

@ -1,13 +1,11 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { deleteLike } from "~database/entities/Like";
import {
findFirstStatuses,
isViewableByUser,
statusToAPI,
} from "~database/entities/Status";
import { statusAndUserRelations } from "~database/entities/relations";
import type { APIStatus } from "~types/entities/status";
export const meta = applyConfig({

View file

@ -1,8 +1,9 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { statusToAPI } from "~database/entities/Status";
import { statusAndUserRelations } from "~database/entities/relations";
import { and, eq } from "drizzle-orm";
import { findFirstStatuses, statusToAPI } from "~database/entities/Status";
import { db } from "~drizzle/db";
import { statusToUser } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -26,9 +27,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!user) return errorResponse("Unauthorized", 401);
let status = await client.status.findUnique({
where: { id },
include: statusAndUserRelations,
const status = await findFirstStatuses({
where: (status, { eq }) => eq(status.id, id),
});
// Check if status exists
@ -37,21 +37,9 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Check if status is user's
if (status.authorId !== user.id) return errorResponse("Unauthorized", 401);
await client.user.update({
where: { id: user.id },
data: {
pinnedNotes: {
disconnect: {
id: status.id,
},
},
},
});
status = await client.status.findUnique({
where: { id },
include: statusAndUserRelations,
});
await db
.delete(statusToUser)
.where(and(eq(statusToUser.a, status.id), eq(statusToUser.b, user.id)));
if (!status) return errorResponse("Record not found", 404);

View file

@ -1,17 +1,16 @@
import { apiRoute, applyConfig } from "@api";
import { MeiliIndexType, meilisearch } from "@meilisearch";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { statusToAPI } from "~database/entities/Status";
import { and, eq, sql } from "drizzle-orm";
import { findManyStatuses, statusToAPI } from "~database/entities/Status";
import {
resolveUser,
findFirstUser,
findManyUsers,
resolveWebFinger,
userToAPI,
} from "~database/entities/User";
import {
statusAndUserRelations,
userRelations,
} from "~database/entities/relations";
import { db } from "~drizzle/db";
import { instance, user } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -26,9 +25,6 @@ export const meta = applyConfig({
},
});
/**
* Upload new media
*/
export default apiRoute<{
q?: string;
type?: string;
@ -40,7 +36,7 @@ export default apiRoute<{
limit?: number;
offset?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
const { user: self } = extraData.auth;
const {
q,
@ -60,7 +56,7 @@ export default apiRoute<{
return errorResponse("Meilisearch is not enabled", 501);
}
if (!user && (resolve || offset)) {
if (!self && (resolve || offset)) {
return errorResponse(
"Cannot use resolve or offset without being authenticated",
401,
@ -87,15 +83,26 @@ export default apiRoute<{
const [username, domain] = accountMatches[0].split("@");
const account = await client.user.findFirst({
where: {
username,
instance: {
base_url: domain,
},
},
include: userRelations,
});
const accountId = (
await db
.select({
id: user.id,
})
.from(user)
.leftJoin(instance, eq(user.instanceId, instance.id))
.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) {
return jsonResponse({
@ -146,43 +153,41 @@ export default apiRoute<{
).hits;
}
const accounts = await client.user.findMany({
where: {
id: {
in: accountResults.map((hit) => hit.id),
},
relationshipSubjects: {
some: {
subjectId: user?.id,
following: following ? true : undefined,
},
},
},
orderBy: {
createdAt: "desc",
},
include: userRelations,
const accounts = await findManyUsers({
where: (user, { and, eq, inArray }) =>
and(
inArray(
user.id,
accountResults.map((hit) => hit.id),
),
self
? sql`EXISTS (SELECT 1 FROM Relationships WHERE Relationships.subjectId = ${
self?.id
} AND Relationships.following = ${
following ? true : false
} AND Relationships.objectId = ${user.id})`
: undefined,
),
orderBy: (user, { desc }) => desc(user.createdAt),
});
const statuses = await client.status.findMany({
where: {
id: {
in: statusResults.map((hit) => hit.id),
},
author: {
relationshipSubjects: {
some: {
subjectId: user?.id,
following: following ? true : undefined,
},
},
},
authorId: account_id ? account_id : undefined,
},
orderBy: {
createdAt: "desc",
},
include: statusAndUserRelations,
const statuses = await findManyStatuses({
where: (status, { and, eq, inArray }) =>
and(
inArray(
status.id,
statusResults.map((hit) => hit.id),
),
account_id ? eq(status.authorId, account_id) : undefined,
self
? sql`EXISTS (SELECT 1 FROM Relationships WHERE Relationships.subjectId = ${
self?.id
} AND Relationships.following = ${
following ? true : false
} AND Relationships.objectId = ${status.authorId})`
: undefined,
),
orderBy: (status, { desc }) => desc(status.createdAt),
});
return jsonResponse({

View file

@ -6,7 +6,8 @@ import {
generateRandomCodeVerifier,
processDiscoveryResponse,
} from "oauth4webapi";
import { client } from "~database/datasource";
import { db } from "~drizzle/db";
import { openIdLoginFlow } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -60,20 +61,26 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const codeVerifier = generateRandomCodeVerifier();
// Store into database
const newFlow = await client.openIdLoginFlow.create({
data: {
codeVerifier,
application: {
connect: {
client_id: clientId,
},
},
issuerId,
},
const application = await db.query.application.findFirst({
where: (application, { eq }) => eq(application.clientId, clientId),
});
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);
return Response.redirect(

View file

@ -13,8 +13,10 @@ import {
userInfoRequest,
validateAuthResponse,
} from "oauth4webapi";
import { client } from "~database/datasource";
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({
allowedMethods: ["GET"],
@ -46,11 +48,10 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Remove state query parameter from URL
currentUrl.searchParams.delete("state");
const issuerParam = matchedRoute.params.issuer;
const flow = await client.openIdLoginFlow.findFirst({
where: {
id: matchedRoute.query.flow,
},
include: {
const flow = await db.query.openIdLoginFlow.findFirst({
where: (flow, { eq }) => eq(flow.id, matchedRoute.query.flow),
with: {
application: true,
},
});
@ -142,15 +143,19 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
),
);
const user = await client.user.findFirst({
where: {
linkedOpenIdAccounts: {
some: {
serverId: sub,
issuerId: issuer.id,
},
},
},
const userId = (
await db.query.openIdAccount.findFirst({
where: (account, { eq, and }) =>
and(eq(account.serverId, sub), eq(account.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) {
@ -161,31 +166,21 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const code = randomBytes(32).toString("hex");
await client.application.update({
where: { id: flow.application.id },
data: {
tokens: {
create: {
access_token: randomBytes(64).toString("base64url"),
await db.insert(token).values({
accessToken: randomBytes(64).toString("base64url"),
code: code,
scope: flow.application.scopes,
token_type: TokenType.BEARER,
user: {
connect: {
id: user.id,
},
},
},
},
},
tokenType: TokenType.BEARER,
userId: user.id,
applicationId: flow.application.id,
});
// Redirect back to application
return Response.redirect(
`/oauth/redirect?${new URLSearchParams({
redirect_uri: flow.application.redirect_uris,
redirect_uri: flow.application.redirectUris,
code,
client_id: flow.application.client_id,
client_id: flow.application.clientId,
application: flow.application.name,
website: flow.application.website ?? "",
scope: flow.application.scopes,

View file

@ -1,6 +1,6 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { db } from "~drizzle/db";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -34,30 +34,41 @@ export default apiRoute<{
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
const token = await client.token.findFirst({
where: {
code,
application: {
client_id,
secret: client_secret,
redirect_uris: redirect_uri,
scopes: scope?.replaceAll("+", " "),
},
scope: scope?.replaceAll("+", " "),
},
include: {
application: true,
},
const application = await db.query.application.findFirst({
where: (application, { eq, and }) =>
and(
eq(application.clientId, client_id),
eq(application.secret, client_secret),
eq(application.redirectUris, redirect_uri),
eq(application.scopes, scope?.replaceAll("+", " ")),
),
});
if (!application)
return errorResponse(
"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)
return errorResponse("Invalid access token or client credentials", 401);
return jsonResponse({
access_token: token.access_token,
token_type: token.token_type,
access_token: token.accessToken,
token_type: token.tokenType,
scope: token.scope,
created_at: Number(token.created_at),
created_at: new Date(token.createdAt).getTime(),
});
});

View file

@ -1,10 +1,6 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type * as Lysand from "lysand-types";
import { client } from "~database/datasource";
import { statusToLysand } from "~database/entities/Status";
import { userToLysand } from "~database/entities/User";
import { statusAndUserRelations } from "~database/entities/relations";
import { findFirstStatuses, statusToLysand } from "~database/entities/Status";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -21,11 +17,8 @@ export const meta = applyConfig({
export default apiRoute(async (req, matchedRoute, extraData) => {
const uuid = matchedRoute.params.uuid;
const status = await client.status.findUnique({
where: {
id: uuid,
},
include: statusAndUserRelations,
const status = await findFirstStatuses({
where: (status, { eq }) => eq(status.id, uuid),
});
if (!status) {

View file

@ -1,17 +1,16 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, response } from "@response";
import { eq } from "drizzle-orm";
import type * as Lysand from "lysand-types";
import { client } from "~database/datasource";
import { objectToInboxRequest } from "~database/entities/Federation";
import { createNewStatus, resolveStatus } from "~database/entities/Status";
import { resolveStatus } from "~database/entities/Status";
import {
followAcceptToLysand,
findFirstUser,
getRelationshipToOtherUser,
resolveUser,
sendFollowAccept,
} from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
import type { APIStatus } from "~types/entities/status";
import { db } from "~drizzle/db";
import { notification, relationship } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -28,19 +27,14 @@ export const meta = applyConfig({
export default apiRoute(async (req, matchedRoute, extraData) => {
const uuid = matchedRoute.params.uuid;
const user = await client.user.findUnique({
where: {
id: uuid,
},
include: userRelations,
const user = await findFirstUser({
where: (user, { eq }) => eq(user.id, uuid),
});
if (!user) {
return errorResponse("User not found", 404);
}
const config = await extraData.configManager.getConfig();
// Process incoming request
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
switch (body.type) {
case "Note": {
@ -154,33 +146,31 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
return errorResponse("Author not found", 400);
}
const relationship = await getRelationshipToOtherUser(
const foundRelationship = await getRelationshipToOtherUser(
account,
user,
);
// Check if already following
if (relationship.following) {
if (foundRelationship.following) {
return response("Already following", 200);
}
await client.relationship.update({
where: { id: relationship.id },
data: {
await db
.update(relationship)
.set({
following: !user.isLocked,
requested: user.isLocked,
showingReblogs: true,
notifying: true,
languages: [],
},
});
})
.where(eq(relationship.id, foundRelationship.id));
await client.notification.create({
data: {
await db.insert(notification).values({
accountId: account.id,
type: user.isLocked ? "follow_request" : "follow",
notifiedId: user.id,
},
});
if (!user.isLocked) {
@ -203,24 +193,24 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
console.log(account);
const relationship = await getRelationshipToOtherUser(
const foundRelationship = await getRelationshipToOtherUser(
user,
account,
);
console.log(relationship);
console.log(foundRelationship);
if (!relationship.requested) {
if (!foundRelationship.requested) {
return response("There is no follow request to accept", 200);
}
await client.relationship.update({
where: { id: relationship.id },
data: {
await db
.update(relationship)
.set({
following: true,
requested: false,
},
});
})
.where(eq(relationship.id, foundRelationship.id));
return response("Follow request accepted", 200);
}
@ -233,22 +223,22 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
return errorResponse("Author not found", 400);
}
const relationship = await getRelationshipToOtherUser(
const foundRelationship = await getRelationshipToOtherUser(
user,
account,
);
if (!relationship.requested) {
if (!foundRelationship.requested) {
return response("There is no follow request to reject", 200);
}
await client.relationship.update({
where: { id: relationship.id },
data: {
await db
.update(relationship)
.set({
requested: false,
following: false,
},
});
})
.where(eq(relationship.id, foundRelationship.id));
return response("Follow request rejected", 200);
}

View file

@ -1,8 +1,6 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { userToLysand } from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
import { findFirstUser, userToLysand } from "~database/entities/User";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -19,11 +17,8 @@ export const meta = applyConfig({
export default apiRoute(async (req, matchedRoute) => {
const uuid = matchedRoute.params.uuid;
const user = await client.user.findUnique({
where: {
id: uuid,
},
include: userRelations,
const user = await findFirstUser({
where: (user, { eq }) => eq(user.id, uuid),
});
if (!user) {

View file

@ -1,8 +1,9 @@
import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
import { client } from "~database/datasource";
import { statusToLysand } from "~database/entities/Status";
import { statusAndUserRelations } from "~database/entities/relations";
import { and, count, eq, inArray } from "drizzle-orm";
import { findManyStatuses, statusToLysand } from "~database/entities/Status";
import { db } from "~drizzle/db";
import { status } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -16,35 +17,34 @@ export const meta = applyConfig({
route: "/users/:uuid/outbox",
});
/**
* ActivityPub user outbox endpoint
*/
export default apiRoute(async (req, matchedRoute, extraData) => {
const uuid = matchedRoute.params.uuid;
const pageNumber = Number(matchedRoute.query.page) || 1;
const config = await extraData.configManager.getConfig();
const host = new URL(config.http.base_url).hostname;
const statuses = await client.status.findMany({
where: {
authorId: uuid,
visibility: {
in: ["public", "unlisted"],
},
},
take: 20,
skip: 20 * (pageNumber - 1),
include: statusAndUserRelations,
const statuses = await findManyStatuses({
where: (status, { eq, and, inArray }) =>
and(
eq(status.authorId, uuid),
inArray(status.visibility, ["public", "unlisted"]),
),
offset: 20 * (pageNumber - 1),
limit: 20,
orderBy: (status, { desc }) => desc(status.createdAt),
});
const totalStatuses = await client.status.count({
where: {
authorId: uuid,
visibility: {
in: ["public", "unlisted"],
},
},
});
const totalStatuses = await db
.select({
count: count(),
})
.from(status)
.where(
and(
eq(status.authorId, uuid),
inArray(status.visibility, ["public", "unlisted"]),
),
);
return jsonResponse({
first: `${host}/users/${uuid}/outbox?page=1`,

View file

@ -1,6 +1,6 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { findFirstUser } from "~database/entities/User";
export const meta = applyConfig({
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,
);
const user = await client.user.findUnique({
where: {
id: isUuid ? requestedUser.split("@")[0] : undefined,
username: isUuid ? undefined : requestedUser.split("@")[0],
},
const user = await findFirstUser({
where: (user, { eq }) =>
eq(isUuid ? user.id : user.username, requestedUser.split("@")[0]),
});
if (!user) {