refactor(database): 🎨 Update database and schema names to be clearer

This commit is contained in:
Jesse Wierzbinski 2024-04-16 20:36:01 -10:00
parent 9081036c6d
commit 88b3ec7b43
No known key found for this signature in database
92 changed files with 6785 additions and 1018 deletions

50
cli.ts
View file

@ -20,7 +20,7 @@ import {
findManyUsers,
} from "~database/entities/User";
import { client, db } from "~drizzle/db";
import { emoji, openIdAccount, status, user } from "~drizzle/schema";
import { Emojis, Notes, OpenIdAccounts, Users } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
await client.connect();
@ -225,7 +225,7 @@ const cliBuilder = new CliBuilder([
}
}
await db.delete(user).where(eq(user.id, foundUser.id));
await db.delete(Users).where(eq(Users.id, foundUser.id));
console.log(
`${chalk.green("✓")} Deleted user ${chalk.blue(
@ -640,13 +640,15 @@ const cliBuilder = new CliBuilder([
return 1;
}
const linkedOpenIdAccounts = await db.query.openIdAccount.findMany({
where: (account, { eq, and }) =>
and(
eq(account.userId, user.id),
eq(account.issuerId, issuerId),
),
});
const linkedOpenIdAccounts = await db.query.OpenIdAccounts.findMany(
{
where: (account, { eq, and }) =>
and(
eq(account.userId, user.id),
eq(account.issuerId, issuerId),
),
},
);
if (linkedOpenIdAccounts.find((a) => a.issuerId === issuerId)) {
console.log(
@ -658,7 +660,7 @@ const cliBuilder = new CliBuilder([
}
// Connect the OpenID account
await db.insert(openIdAccount).values({
await db.insert(OpenIdAccounts).values({
issuerId: issuerId,
serverId: serverId,
userId: user.id,
@ -712,7 +714,7 @@ const cliBuilder = new CliBuilder([
return 1;
}
const account = await db.query.openIdAccount.findFirst({
const account = await db.query.OpenIdAccounts.findFirst({
where: (account, { eq }) => eq(account.serverId, id),
});
@ -735,8 +737,8 @@ const cliBuilder = new CliBuilder([
});
await db
.delete(openIdAccount)
.where(eq(openIdAccount.id, account.id));
.delete(OpenIdAccounts)
.where(eq(OpenIdAccounts.id, account.id));
console.log(
`${chalk.green(
@ -950,14 +952,14 @@ const cliBuilder = new CliBuilder([
}
let instanceQuery: SQL<unknown> | undefined =
sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${status.authorId} AND "User"."instanceId" IS NULL)`;
sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${Notes.authorId} AND "User"."instanceId" IS NULL)`;
if (local && remote) {
instanceQuery = undefined;
} else if (local) {
instanceQuery = sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${status.authorId} AND "User"."instanceId" IS NULL)`;
instanceQuery = sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${Notes.authorId} AND "User"."instanceId" IS NULL)`;
} else if (remote) {
instanceQuery = sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${status.authorId} AND "User"."instanceId" IS NOT NULL)`;
instanceQuery = sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${Notes.authorId} AND "User"."instanceId" IS NOT NULL)`;
}
const notes = (
@ -966,7 +968,7 @@ const cliBuilder = new CliBuilder([
or(
...fields.map((field) =>
// @ts-expect-error
like(status[field], `%${query}%`),
like(Notes[field], `%${query}%`),
),
),
instanceQuery,
@ -1178,7 +1180,7 @@ const cliBuilder = new CliBuilder([
}
// Check if emoji already exists
const existingEmoji = await db.query.emoji.findFirst({
const existingEmoji = await db.query.Emojis.findFirst({
where: (emoji, { and, eq, isNull }) =>
and(
eq(emoji.shortcode, shortcode),
@ -1246,7 +1248,7 @@ const cliBuilder = new CliBuilder([
const newEmoji = (
await db
.insert(emoji)
.insert(Emojis)
.values({
shortcode: shortcode,
url: newUrl,
@ -1323,7 +1325,7 @@ const cliBuilder = new CliBuilder([
return 1;
}
const emojis = await db.query.emoji.findMany({
const emojis = await db.query.Emojis.findMany({
where: (emoji, { and, isNull, like }) =>
and(
like(emoji.shortcode, shortcode.replace(/\*/g, "%")),
@ -1367,9 +1369,9 @@ const cliBuilder = new CliBuilder([
}
}
await db.delete(emoji).where(
await db.delete(Emojis).where(
inArray(
emoji.id,
Emojis.id,
emojis.map((e) => e.id),
),
);
@ -1426,7 +1428,7 @@ const cliBuilder = new CliBuilder([
return 0;
}
const emojis = await db.query.emoji.findMany({
const emojis = await db.query.Emojis.findMany({
where: (emoji, { isNull }) => isNull(emoji.instanceId),
limit: Number(limit),
});
@ -1704,7 +1706,7 @@ const cliBuilder = new CliBuilder([
).toString();
// Check if emoji already exists
const existingEmoji = await db.query.emoji.findFirst({
const existingEmoji = await db.query.Emojis.findFirst({
where: (emoji, { and, eq, isNull }) =>
and(
eq(emoji.shortcode, shortcode),

View file

@ -1,9 +1,9 @@
import type { InferSelectModel } from "drizzle-orm";
import { db } from "~drizzle/db";
import type { application } from "~drizzle/schema";
import type { Applications } from "~drizzle/schema";
import type { Application as APIApplication } from "~types/mastodon/application";
export type Application = InferSelectModel<typeof application>;
export type Application = InferSelectModel<typeof Applications>;
/**
* Retrieves the application associated with the given access token.
@ -13,7 +13,7 @@ export type Application = InferSelectModel<typeof application>;
export const getFromToken = async (
token: string,
): Promise<Application | null> => {
const result = await db.query.token.findFirst({
const result = await db.query.Tokens.findFirst({
where: (tokens, { eq }) => eq(tokens.accessToken, token),
with: {
application: true,

View file

@ -3,11 +3,11 @@ import type { InferSelectModel } from "drizzle-orm";
import type * as Lysand from "lysand-types";
import { MediaBackendType } from "media-manager";
import { db } from "~drizzle/db";
import { attachment } from "~drizzle/schema";
import { Attachments } from "~drizzle/schema";
import type { AsyncAttachment as APIAsyncAttachment } from "~types/mastodon/async_attachment";
import type { Attachment as APIAttachment } from "~types/mastodon/attachment";
export type Attachment = InferSelectModel<typeof attachment>;
export type Attachment = InferSelectModel<typeof Attachments>;
export const attachmentToAPI = (
attachment: Attachment,
@ -86,12 +86,12 @@ export const attachmentToLysand = (
export const attachmentFromLysand = async (
attachmentToConvert: Lysand.ContentFormat,
): Promise<InferSelectModel<typeof attachment>> => {
): Promise<InferSelectModel<typeof Attachments>> => {
const key = Object.keys(attachmentToConvert)[0];
const value = attachmentToConvert[key];
const result = await db
.insert(attachment)
.insert(Attachments)
.values({
mimeType: key,
url: value.content,

View file

@ -1,12 +1,12 @@
import { type InferSelectModel, and, eq } from "drizzle-orm";
import type * as Lysand from "lysand-types";
import { db } from "~drizzle/db";
import { emoji, instance } from "~drizzle/schema";
import { Emojis, Instances } from "~drizzle/schema";
import type { Emoji as APIEmoji } from "~types/mastodon/emoji";
import { addInstanceIfNotExists } from "./Instance";
export type EmojiWithInstance = InferSelectModel<typeof emoji> & {
instance: InferSelectModel<typeof instance> | null;
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
instance: InferSelectModel<typeof Instances> | null;
};
/**
@ -18,7 +18,7 @@ export const parseEmojis = async (text: string) => {
const regex = /:[a-zA-Z0-9_]+:/g;
const matches = text.match(regex);
if (!matches) return [];
const emojis = await db.query.emoji.findMany({
const emojis = await db.query.Emojis.findMany({
where: (emoji, { eq, or }) =>
or(
...matches
@ -45,27 +45,27 @@ export const fetchEmoji = async (
): Promise<EmojiWithInstance> => {
const existingEmoji = await db
.select()
.from(emoji)
.innerJoin(instance, eq(emoji.instanceId, instance.id))
.from(Emojis)
.innerJoin(Instances, eq(Emojis.instanceId, Instances.id))
.where(
and(
eq(emoji.shortcode, emojiToFetch.name),
host ? eq(instance.baseUrl, host) : undefined,
eq(Emojis.shortcode, emojiToFetch.name),
host ? eq(Instances.baseUrl, host) : undefined,
),
)
.limit(1);
if (existingEmoji[0])
return {
...existingEmoji[0].Emoji,
instance: existingEmoji[0].Instance,
...existingEmoji[0].Emojis,
instance: existingEmoji[0].Instances,
};
const foundInstance = host ? await addInstanceIfNotExists(host) : null;
const result = (
await db
.insert(emoji)
.insert(Emojis)
.values({
shortcode: emojiToFetch.name,
url: Object.entries(emojiToFetch.url)[0][1].content,

View file

@ -1,6 +1,6 @@
import type * as Lysand from "lysand-types";
import { db } from "~drizzle/db";
import { instance } from "~drizzle/schema";
import { Instances } from "~drizzle/schema";
/**
* Represents an instance in the database.
@ -38,7 +38,7 @@ export const addInstanceIfNotExists = async (url: string) => {
return (
await db
.insert(instance)
.insert(Instances)
.values({
baseUrl: host,
name: metadata.name,

View file

@ -2,11 +2,11 @@ import { config } from "config-manager";
import { type InferSelectModel, and, eq } from "drizzle-orm";
import type * as Lysand from "lysand-types";
import { db } from "~drizzle/db";
import { like, notification } from "~drizzle/schema";
import { Likes, Notifications } from "~drizzle/schema";
import type { StatusWithRelations } from "./Status";
import type { UserWithRelations } from "./User";
export type Like = InferSelectModel<typeof like>;
export type Like = InferSelectModel<typeof Likes>;
/**
* Represents a Like entity in the database.
@ -33,18 +33,18 @@ export const createLike = async (
user: UserWithRelations,
status: StatusWithRelations,
) => {
await db.insert(like).values({
await db.insert(Likes).values({
likedId: status.id,
likerId: user.id,
});
if (status.author.instanceId === user.instanceId) {
// Notify the user that their post has been favourited
await db.insert(notification).values({
await db.insert(Notifications).values({
accountId: user.id,
type: "favourite",
notifiedId: status.authorId,
statusId: status.id,
noteId: status.id,
});
} else {
// TODO: Add database jobs for federating this
@ -61,18 +61,18 @@ export const deleteLike = async (
status: StatusWithRelations,
) => {
await db
.delete(like)
.where(and(eq(like.likedId, status.id), eq(like.likerId, user.id)));
.delete(Likes)
.where(and(eq(Likes.likedId, status.id), eq(Likes.likerId, user.id)));
// Notify the user that their post has been favourited
await db
.delete(notification)
.delete(Notifications)
.where(
and(
eq(notification.accountId, user.id),
eq(notification.type, "favourite"),
eq(notification.notifiedId, status.authorId),
eq(notification.statusId, status.id),
eq(Notifications.accountId, user.id),
eq(Notifications.type, "favourite"),
eq(Notifications.notifiedId, status.authorId),
eq(Notifications.noteId, status.id),
),
);

View file

@ -1,6 +1,6 @@
import type { InferSelectModel } from "drizzle-orm";
import { db } from "~drizzle/db";
import type { notification } from "~drizzle/schema";
import type { Notifications } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
import type { Notification as APINotification } from "~types/mastodon/notification";
import type { StatusWithRelations } from "./Status";
@ -12,7 +12,7 @@ import {
userToAPI,
} from "./User";
export type Notification = InferSelectModel<typeof notification>;
export type Notification = InferSelectModel<typeof Notifications>;
export type NotificationWithRelations = Notification & {
status: StatusWithRelations | null;
@ -20,9 +20,9 @@ export type NotificationWithRelations = Notification & {
};
export const findManyNotifications = async (
query: Parameters<typeof db.query.notification.findMany>[0],
query: Parameters<typeof db.query.Notifications.findMany>[0],
): Promise<NotificationWithRelations[]> => {
const output = await db.query.notification.findMany({
const output = await db.query.Notifications.findMany({
...query,
with: {
...query?.with,
@ -30,7 +30,7 @@ export const findManyNotifications = async (
with: {
...userRelations,
},
extras: userExtrasTemplate("notification_account"),
extras: userExtrasTemplate("Notifications_account"),
},
},
extras: {
@ -42,7 +42,7 @@ export const findManyNotifications = async (
output.map(async (notif) => ({
...notif,
account: transformOutputToUserWithRelations(notif.account),
status: (await Note.fromId(notif.statusId))?.getStatus() ?? null,
status: (await Note.fromId(notif.noteId))?.getStatus() ?? null,
})),
);
};

View file

@ -1,10 +1,10 @@
import type { InferSelectModel } from "drizzle-orm";
import type * as Lysand from "lysand-types";
import { db } from "~drizzle/db";
import { lysandObject } from "~drizzle/schema";
import { LysandObjects } from "~drizzle/schema";
import { findFirstUser } from "./User";
export type LysandObject = InferSelectModel<typeof lysandObject>;
export type LysandObject = InferSelectModel<typeof LysandObjects>;
/**
* Represents a Lysand object in the database.
@ -29,7 +29,7 @@ export const createFromObject = async (
where: (user, { eq }) => eq(user.uri, authorUri),
});
return await db.insert(lysandObject).values({
return await db.insert(LysandObjects).values({
authorId: author?.id,
createdAt: new Date(object.created_at).toISOString(),
extensions: object.extensions,

View file

@ -1,10 +1,10 @@
import type { InferSelectModel } from "drizzle-orm";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
import type { Relationship as APIRelationship } from "~types/mastodon/relationship";
import type { User } from "./User";
export type Relationship = InferSelectModel<typeof relationship>;
export type Relationship = InferSelectModel<typeof Relationships>;
/**
* Creates a new relationship between two users.
@ -18,7 +18,7 @@ export const createNewRelationship = async (
): Promise<Relationship> => {
return (
await db
.insert(relationship)
.insert(Relationships)
.values({
ownerId: owner.id,
subjectId: other.id,
@ -46,12 +46,12 @@ export const checkForBidirectionalRelationships = async (
user2: User,
createIfNotExists = true,
): Promise<boolean> => {
const relationship1 = await db.query.relationship.findFirst({
const relationship1 = await db.query.Relationships.findFirst({
where: (rel, { and, eq }) =>
and(eq(rel.ownerId, user1.id), eq(rel.subjectId, user2.id)),
});
const relationship2 = await db.query.relationship.findFirst({
const relationship2 = await db.query.Relationships.findFirst({
where: (rel, { and, eq }) =>
and(eq(rel.ownerId, user2.id), eq(rel.subjectId, user1.id)),
});

View file

@ -27,13 +27,13 @@ import {
import { parse } from "marked";
import { db } from "~drizzle/db";
import {
attachment,
emojiToStatus,
instance,
notification,
status,
statusToMentions,
user,
Attachments,
EmojiToNote,
Instances,
NoteToMentions,
Notes,
Notifications,
Users,
} from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
import { LogLevel } from "~packages/log-manager";
@ -61,17 +61,17 @@ import {
userRelations,
} from "./User";
export type Status = InferSelectModel<typeof status>;
export type Status = InferSelectModel<typeof Notes>;
export type StatusWithRelations = Status & {
author: UserWithRelations;
mentions: UserWithInstance[];
attachments: InferSelectModel<typeof attachment>[];
attachments: InferSelectModel<typeof Attachments>[];
reblog: StatusWithoutRecursiveRelations | null;
emojis: EmojiWithInstance[];
likes: Like[];
inReplyTo: Status | null;
quoting: Status | null;
reply: Status | null;
quote: Status | null;
application: Application | null;
reblogCount: number;
likeCount: number;
@ -80,20 +80,20 @@ export type StatusWithRelations = Status & {
export type StatusWithoutRecursiveRelations = Omit<
StatusWithRelations,
"inReplyTo" | "quoting" | "reblog"
"reply" | "quote" | "reblog"
>;
export const noteExtras = {
reblogCount:
sql`(SELECT COUNT(*) FROM "Status" "status" WHERE "status"."reblogId" = "status".id)`.as(
sql`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."reblogId" = "Notes".id)`.as(
"reblog_count",
),
likeCount:
sql`(SELECT COUNT(*) FROM "Like" "like" WHERE "like"."likedId" = "status".id)`.as(
sql`(SELECT COUNT(*) FROM "Likes" WHERE "Likes"."likedId" = "Notes".id)`.as(
"like_count",
),
replyCount:
sql`(SELECT COUNT(*) FROM "Status" "status" WHERE "status"."inReplyToPostId" = "status".id)`.as(
sql`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."replyId" = "Notes".id)`.as(
"reply_count",
),
};
@ -104,15 +104,15 @@ export const noteExtras = {
* @returns
*/
export const findManyNotes = async (
query: Parameters<typeof db.query.status.findMany>[0],
query: Parameters<typeof db.query.Notes.findMany>[0],
): Promise<StatusWithRelations[]> => {
const output = await db.query.status.findMany({
const output = await db.query.Notes.findMany({
...query,
with: {
...query?.with,
attachments: {
where: (attachment, { eq }) =>
eq(attachment.statusId, sql`"status"."id"`),
eq(attachment.noteId, sql`"Notes"."id"`),
},
emojis: {
with: {
@ -127,7 +127,7 @@ export const findManyNotes = async (
with: {
...userRelations,
},
extras: userExtrasTemplate("status_author"),
extras: userExtrasTemplate("Notes_author"),
},
mentions: {
with: {
@ -157,7 +157,7 @@ export const findManyNotes = async (
user: {
with: userRelations,
extras: userExtrasTemplate(
"status_reblog_mentions_user",
"Notes_reblog_mentions_user",
),
},
},
@ -166,15 +166,15 @@ export const findManyNotes = async (
with: {
...userRelations,
},
extras: userExtrasTemplate("status_reblog_author"),
extras: userExtrasTemplate("Notes_reblog_author"),
},
},
extras: {
...noteExtras,
},
},
inReplyTo: true,
quoting: true,
reply: true,
quote: true,
},
extras: {
...noteExtras,
@ -187,25 +187,17 @@ export const findManyNotes = async (
author: transformOutputToUserWithRelations(post.author),
mentions: post.mentions.map((mention) => ({
...mention.user,
endpoints: mention.user.endpoints as User["endpoints"],
endpoints: mention.user.endpoints,
})),
emojis: (post.emojis ?? []).map(
(emoji) =>
(emoji as unknown as Record<string, object>)
.emoji as EmojiWithInstance,
),
emojis: (post.emojis ?? []).map((emoji) => emoji.emoji),
reblog: post.reblog && {
...post.reblog,
author: transformOutputToUserWithRelations(post.reblog.author),
mentions: post.reblog.mentions.map((mention) => ({
...mention.user,
endpoints: mention.user.endpoints as User["endpoints"],
endpoints: mention.user.endpoints,
})),
emojis: (post.reblog.emojis ?? []).map(
(emoji) =>
(emoji as unknown as Record<string, object>)
.emoji as EmojiWithInstance,
),
emojis: (post.reblog.emojis ?? []).map((emoji) => emoji.emoji),
reblogCount: Number(post.reblog.reblogCount),
likeCount: Number(post.reblog.likeCount),
replyCount: Number(post.reblog.replyCount),
@ -217,15 +209,15 @@ export const findManyNotes = async (
};
export const findFirstNote = async (
query: Parameters<typeof db.query.status.findFirst>[0],
query: Parameters<typeof db.query.Notes.findFirst>[0],
): Promise<StatusWithRelations | null> => {
const output = await db.query.status.findFirst({
const output = await db.query.Notes.findFirst({
...query,
with: {
...query?.with,
attachments: {
where: (attachment, { eq }) =>
eq(attachment.statusId, sql`"status"."id"`),
eq(attachment.noteId, sql`"Notes"."id"`),
},
emojis: {
with: {
@ -240,7 +232,7 @@ export const findFirstNote = async (
with: {
...userRelations,
},
extras: userExtrasTemplate("status_author"),
extras: userExtrasTemplate("Notes_author"),
},
mentions: {
with: {
@ -270,7 +262,7 @@ export const findFirstNote = async (
user: {
with: userRelations,
extras: userExtrasTemplate(
"status_reblog_mentions_user",
"Notes_reblog_mentions_user",
),
},
},
@ -279,15 +271,15 @@ export const findFirstNote = async (
with: {
...userRelations,
},
extras: userExtrasTemplate("status_reblog_author"),
extras: userExtrasTemplate("Notes_reblog_author"),
},
},
extras: {
...noteExtras,
},
},
inReplyTo: true,
quoting: true,
reply: true,
quote: true,
},
extras: {
...noteExtras,
@ -302,25 +294,17 @@ export const findFirstNote = async (
author: transformOutputToUserWithRelations(output.author),
mentions: output.mentions.map((mention) => ({
...mention.user,
endpoints: mention.user.endpoints as User["endpoints"],
endpoints: mention.user.endpoints,
})),
emojis: (output.emojis ?? []).map(
(emoji) =>
(emoji as unknown as Record<string, object>)
.emoji as EmojiWithInstance,
),
emojis: (output.emojis ?? []).map((emoji) => emoji.emoji),
reblog: output.reblog && {
...output.reblog,
author: transformOutputToUserWithRelations(output.reblog.author),
mentions: output.reblog.mentions.map((mention) => ({
...mention.user,
endpoints: mention.user.endpoints as User["endpoints"],
endpoints: mention.user.endpoints,
})),
emojis: (output.reblog.emojis ?? []).map(
(emoji) =>
(emoji as unknown as Record<string, object>)
.emoji as EmojiWithInstance,
),
emojis: (output.reblog.emojis ?? []).map((emoji) => emoji.emoji),
reblogCount: Number(output.reblog.reblogCount),
likeCount: Number(output.reblog.likeCount),
replyCount: Number(output.reblog.replyCount),
@ -340,7 +324,7 @@ export const resolveNote = async (
}
const foundStatus = await Note.fromSql(
eq(status.uri, uri ?? providedNote?.uri ?? ""),
eq(Notes.uri, uri ?? providedNote?.uri ?? ""),
);
if (foundStatus) return foundStatus;
@ -478,20 +462,20 @@ export const parseTextMentions = async (
const foundUsers = await db
.select({
id: user.id,
username: user.username,
baseUrl: instance.baseUrl,
id: Users.id,
username: Users.username,
baseUrl: Instances.baseUrl,
})
.from(user)
.leftJoin(instance, eq(user.instanceId, instance.id))
.from(Users)
.leftJoin(Instances, eq(Users.instanceId, Instances.id))
.where(
or(
...mentionedPeople.map((person) =>
and(
eq(user.username, person?.[1] ?? ""),
eq(Users.username, person?.[1] ?? ""),
isLocal(person?.[2])
? isNull(user.instanceId)
: eq(instance.baseUrl, person?.[2] ?? ""),
? isNull(Users.instanceId)
: eq(Instances.baseUrl, person?.[2] ?? ""),
),
),
),
@ -701,10 +685,10 @@ export const editStatus = async (
// Connect emojis
for (const emoji of data.emojis) {
await db
.insert(emojiToStatus)
.insert(EmojiToNote)
.values({
emojiId: emoji.id,
statusId: updated.id,
noteId: updated.id,
})
.execute();
}
@ -712,9 +696,9 @@ export const editStatus = async (
// Connect mentions
for (const mention of mentions) {
await db
.insert(statusToMentions)
.insert(NoteToMentions)
.values({
statusId: updated.id,
noteId: updated.id,
userId: mention.id,
})
.execute();
@ -723,28 +707,28 @@ export const editStatus = async (
// Send notifications for mentioned local users
for (const mention of mentions ?? []) {
if (mention.instanceId === null) {
await db.insert(notification).values({
await db.insert(Notifications).values({
accountId: statusToEdit.authorId,
notifiedId: mention.id,
type: "mention",
statusId: updated.id,
noteId: updated.id,
});
}
}
// Set attachment parents
await db
.update(attachment)
.update(Attachments)
.set({
statusId: updated.id,
noteId: updated.id,
})
.where(inArray(attachment.id, data.media_attachments ?? []));
.where(inArray(Attachments.id, data.media_attachments ?? []));
return await Note.fromId(updated.id);
};
export const isFavouritedBy = async (status: Status, user: User) => {
return !!(await db.query.like.findFirst({
return !!(await db.query.Likes.findFirst({
where: (like, { and, eq }) =>
and(eq(like.likerId, user.id), eq(like.likedId, status.id)),
}));
@ -779,8 +763,8 @@ export const statusToLysand = (status: StatusWithRelations): Lysand.Note => {
),
is_sensitive: status.sensitive,
mentions: status.mentions.map((mention) => mention.uri || ""),
quotes: getStatusUri(status.quoting) ?? undefined,
replies_to: getStatusUri(status.inReplyTo) ?? undefined,
quotes: getStatusUri(status.quote) ?? undefined,
replies_to: getStatusUri(status.reply) ?? undefined,
subject: status.spoilerText,
visibility: status.visibility as Lysand.Visibility,
extensions: {

View file

@ -1,5 +1,5 @@
import type { InferSelectModel } from "drizzle-orm";
import type { token } from "~drizzle/schema";
import type { Tokens } from "~drizzle/schema";
/**
* The type of token.
@ -8,4 +8,4 @@ export enum TokenType {
BEARER = "Bearer",
}
export type Token = InferSelectModel<typeof token>;
export type Token = InferSelectModel<typeof Tokens>;

View file

@ -7,13 +7,13 @@ import { htmlToText } from "html-to-text";
import type * as Lysand from "lysand-types";
import { db } from "~drizzle/db";
import {
application,
emojiToUser,
instance,
notification,
relationship,
token,
user,
Applications,
EmojiToUser,
Instances,
Notifications,
Relationships,
Tokens,
Users,
} from "~drizzle/schema";
import { LogLevel } from "~packages/log-manager";
import type { Account as APIAccount } from "~types/mastodon/account";
@ -31,14 +31,14 @@ import { addInstanceIfNotExists } from "./Instance";
import { createNewRelationship } from "./Relationship";
import type { Token } from "./Token";
export type User = InferSelectModel<typeof user>;
export type User = InferSelectModel<typeof Users>;
export type UserWithInstance = User & {
instance: InferSelectModel<typeof instance> | null;
instance: InferSelectModel<typeof Instances> | null;
};
export type UserWithRelations = User & {
instance: InferSelectModel<typeof instance> | null;
instance: InferSelectModel<typeof Instances> | null;
emojis: EmojiWithInstance[];
followerCount: number;
followingCount: number;
@ -46,8 +46,8 @@ export type UserWithRelations = User & {
};
export type UserWithRelationsAndRelationships = UserWithRelations & {
relationships: InferSelectModel<typeof relationship>[];
relationshipSubjects: InferSelectModel<typeof relationship>[];
relationships: InferSelectModel<typeof Relationships>[];
relationshipSubjects: InferSelectModel<typeof Relationships>[];
};
export const userRelations: {
@ -76,15 +76,15 @@ export const userRelations: {
export const userExtras = {
followerCount:
sql`(SELECT COUNT(*) FROM "Relationship" "relationships" WHERE ("relationships"."ownerId" = "user".id AND "relationships"."following" = true))`.as(
sql`(SELECT COUNT(*) FROM "Relationships" "relationships" WHERE ("relationships"."ownerId" = "Users".id AND "relationships"."following" = true))`.as(
"follower_count",
),
followingCount:
sql`(SELECT COUNT(*) FROM "Relationship" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "user".id AND "relationshipSubjects"."following" = true))`.as(
sql`(SELECT COUNT(*) FROM "Relationships" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "Users".id AND "relationshipSubjects"."following" = true))`.as(
"following_count",
),
statusCount:
sql`(SELECT COUNT(*) FROM "Status" "statuses" WHERE "statuses"."authorId" = "user".id)`.as(
sql`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."authorId" = "Users".id)`.as(
"status_count",
),
};
@ -92,15 +92,15 @@ export const userExtras = {
export const userExtrasTemplate = (name: string) => ({
// @ts-ignore
followerCount: sql([
`(SELECT COUNT(*) FROM "Relationship" "relationships" WHERE ("relationships"."ownerId" = "${name}".id AND "relationships"."following" = true))`,
`(SELECT COUNT(*) FROM "Relationships" "relationships" WHERE ("relationships"."ownerId" = "${name}".id AND "relationships"."following" = true))`,
]).as("follower_count"),
// @ts-ignore
followingCount: sql([
`(SELECT COUNT(*) FROM "Relationship" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "${name}".id AND "relationshipSubjects"."following" = true))`,
`(SELECT COUNT(*) FROM "Relationships" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "${name}".id AND "relationshipSubjects"."following" = true))`,
]).as("following_count"),
// @ts-ignore
statusCount: sql([
`(SELECT COUNT(*) FROM "Status" "statuses" WHERE "statuses"."authorId" = "${name}".id)`,
`(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."authorId" = "${name}".id)`,
]).as("status_count"),
});
@ -151,12 +151,12 @@ export const followRequestUser = async (
reblogs = false,
notify = false,
languages: string[] = [],
): Promise<InferSelectModel<typeof relationship>> => {
): Promise<InferSelectModel<typeof Relationships>> => {
const isRemote = followee.instanceId !== null;
const updatedRelationship = (
await db
.update(relationship)
.update(Relationships)
.set({
following: isRemote ? false : !followee.isLocked,
requested: isRemote ? true : followee.isLocked,
@ -164,7 +164,7 @@ export const followRequestUser = async (
notifying: notify,
languages: languages,
})
.where(eq(relationship.id, relationshipId))
.where(eq(Relationships.id, relationshipId))
.returning()
)[0];
@ -195,17 +195,17 @@ export const followRequestUser = async (
return (
await db
.update(relationship)
.update(Relationships)
.set({
following: false,
requested: false,
})
.where(eq(relationship.id, relationshipId))
.where(eq(Relationships.id, relationshipId))
.returning()
)[0];
}
} else {
await db.insert(notification).values({
await db.insert(Notifications).values({
accountId: follower.id,
type: followee.isLocked ? "follow_request" : "follow",
notifiedId: followee.id,
@ -277,7 +277,7 @@ export const transformOutputToUserWithRelations = (
emojiId: string;
emoji?: EmojiWithInstance;
}[];
instance: InferSelectModel<typeof instance> | null;
instance: InferSelectModel<typeof Instances> | null;
endpoints: unknown;
},
): UserWithRelations => {
@ -306,9 +306,9 @@ export const transformOutputToUserWithRelations = (
};
export const findManyUsers = async (
query: Parameters<typeof db.query.user.findMany>[0],
query: Parameters<typeof db.query.Users.findMany>[0],
): Promise<UserWithRelations[]> => {
const output = await db.query.user.findMany({
const output = await db.query.Users.findMany({
...query,
with: {
...userRelations,
@ -324,9 +324,9 @@ export const findManyUsers = async (
};
export const findFirstUser = async (
query: Parameters<typeof db.query.user.findFirst>[0],
query: Parameters<typeof db.query.Users.findFirst>[0],
): Promise<UserWithRelations | null> => {
const output = await db.query.user.findFirst({
const output = await db.query.Users.findFirst({
...query,
with: {
...userRelations,
@ -418,7 +418,7 @@ export const resolveUser = async (
const newUser = (
await db
.insert(user)
.insert(Users)
.values({
username: data.username,
uri: data.uri,
@ -456,7 +456,7 @@ export const resolveUser = async (
// Add emojis to user
if (emojis.length > 0) {
await db.insert(emojiToUser).values(
await db.insert(EmojiToUser).values(
emojis.map((emoji) => ({
emojiId: emoji.id,
userId: newUser.id,
@ -494,15 +494,15 @@ export const resolveWebFinger = async (
// Check if user not already in database
const foundUser = await db
.select()
.from(user)
.innerJoin(instance, eq(user.instanceId, instance.id))
.where(and(eq(user.username, identifier), eq(instance.baseUrl, host)))
.from(Users)
.innerJoin(Instances, eq(Users.instanceId, Instances.id))
.where(and(eq(Users.username, identifier), eq(Instances.baseUrl, host)))
.limit(1);
if (foundUser[0])
return (
(await findFirstUser({
where: (user, { eq }) => eq(user.id, foundUser[0].User.id),
where: (user, { eq }) => eq(user.id, foundUser[0].Users.id),
})) || null
);
@ -580,7 +580,7 @@ export const createNewLocalUser = async (data: {
const newUser = (
await db
.insert(user)
.insert(Users)
.values({
username: data.username,
displayName: data.display_name ?? data.username,
@ -662,12 +662,12 @@ export const retrieveUserAndApplicationFromToken = async (
const output = (
await db
.select({
token: token,
application: application,
token: Tokens,
application: Applications,
})
.from(token)
.leftJoin(application, eq(token.applicationId, application.id))
.where(eq(token.accessToken, access_token))
.from(Tokens)
.leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
.where(eq(Tokens.accessToken, access_token))
.limit(1)
)[0];
@ -686,7 +686,7 @@ export const retrieveToken = async (
if (!access_token) return null;
return (
(await db.query.token.findFirst({
(await db.query.Tokens.findFirst({
where: (tokens, { eq }) => eq(tokens.accessToken, access_token),
})) ?? null
);
@ -700,8 +700,8 @@ export const retrieveToken = async (
export const getRelationshipToOtherUser = async (
user: UserWithRelations,
other: User,
): Promise<InferSelectModel<typeof relationship>> => {
const foundRelationship = await db.query.relationship.findFirst({
): Promise<InferSelectModel<typeof Relationships>> => {
const foundRelationship = await db.query.Relationships.findFirst({
where: (relationship, { and, eq }) =>
and(
eq(relationship.ownerId, user.id),

331
drizzle/0009_easy_slyde.sql Normal file
View file

@ -0,0 +1,331 @@
ALTER TABLE "Application" RENAME TO "Applications";--> statement-breakpoint
ALTER TABLE "Attachment" RENAME TO "Attachments";--> statement-breakpoint
ALTER TABLE "Emoji" RENAME TO "Emojis";--> statement-breakpoint
ALTER TABLE "EmojiToStatus" RENAME TO "EmojiToNote";--> statement-breakpoint
ALTER TABLE "Flag" RENAME TO "Flags";--> statement-breakpoint
ALTER TABLE "Instance" RENAME TO "Instances";--> statement-breakpoint
ALTER TABLE "Like" RENAME TO "Likes";--> statement-breakpoint
ALTER TABLE "ModNote" RENAME TO "ModNotes";--> statement-breakpoint
ALTER TABLE "ModTag" RENAME TO "ModTags";--> statement-breakpoint
ALTER TABLE "Notification" RENAME TO "Notifications";--> statement-breakpoint
ALTER TABLE "OpenIdAccount" RENAME TO "OpenIdAccounts";--> statement-breakpoint
ALTER TABLE "OpenIdLoginFlow" RENAME TO "OpenIdLoginFlows";--> statement-breakpoint
ALTER TABLE "Relationship" RENAME TO "Relationships";--> statement-breakpoint
ALTER TABLE "Status" RENAME TO "Notes";--> statement-breakpoint
ALTER TABLE "StatusToMentions" RENAME TO "NoteToMentions";--> statement-breakpoint
ALTER TABLE "Token" RENAME TO "Tokens";--> statement-breakpoint
ALTER TABLE "User" RENAME TO "Users";--> statement-breakpoint
ALTER TABLE "UserToPinnedNotes" RENAME COLUMN "statusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "Attachments" RENAME COLUMN "statusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "EmojiToNote" RENAME COLUMN "statusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "Flags" RENAME COLUMN "flaggeStatusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "Flags" RENAME COLUMN "flaggedUserId" TO "userId";--> statement-breakpoint
ALTER TABLE "ModNotes" RENAME COLUMN "notedStatusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "ModNotes" RENAME COLUMN "notedUserId" TO "userId";--> statement-breakpoint
ALTER TABLE "ModTags" RENAME COLUMN "taggedStatusId" TO "statusId";--> statement-breakpoint
ALTER TABLE "ModTags" RENAME COLUMN "taggedUserId" TO "userId";--> statement-breakpoint
ALTER TABLE "Notifications" RENAME COLUMN "statusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "Notes" RENAME COLUMN "inReplyToPostId" TO "replyId";--> statement-breakpoint
ALTER TABLE "Notes" RENAME COLUMN "quotingPostId" TO "quoteId";--> statement-breakpoint
ALTER TABLE "NoteToMentions" RENAME COLUMN "statusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "EmojiToUser" DROP CONSTRAINT "EmojiToUser_emojiId_Emoji_id_fk";
--> statement-breakpoint
ALTER TABLE "EmojiToUser" DROP CONSTRAINT "EmojiToUser_userId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "UserToPinnedNotes" DROP CONSTRAINT "UserToPinnedNotes_userId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "UserToPinnedNotes" DROP CONSTRAINT "UserToPinnedNotes_statusId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Attachments" DROP CONSTRAINT "Attachment_statusId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "Emojis" DROP CONSTRAINT "Emoji_instanceId_Instance_id_fk";
--> statement-breakpoint
ALTER TABLE "EmojiToNote" DROP CONSTRAINT "EmojiToStatus_emojiId_Emoji_id_fk";
--> statement-breakpoint
ALTER TABLE "EmojiToNote" DROP CONSTRAINT "EmojiToStatus_statusId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "Flags" DROP CONSTRAINT "Flag_flaggeStatusId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "Flags" DROP CONSTRAINT "Flag_flaggedUserId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Likes" DROP CONSTRAINT "Like_likerId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Likes" DROP CONSTRAINT "Like_likedId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "ModNotes" DROP CONSTRAINT "ModNote_notedStatusId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "ModNotes" DROP CONSTRAINT "ModNote_notedUserId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "ModNotes" DROP CONSTRAINT "ModNote_modId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "ModTags" DROP CONSTRAINT "ModTag_taggedStatusId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "ModTags" DROP CONSTRAINT "ModTag_taggedUserId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "ModTags" DROP CONSTRAINT "ModTag_modId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Notifications" DROP CONSTRAINT "Notification_notifiedId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Notifications" DROP CONSTRAINT "Notification_accountId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Notifications" DROP CONSTRAINT "Notification_statusId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "OpenIdAccounts" DROP CONSTRAINT "OpenIdAccount_userId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "OpenIdLoginFlows" DROP CONSTRAINT "OpenIdLoginFlow_applicationId_Application_id_fk";
--> statement-breakpoint
ALTER TABLE "Relationships" DROP CONSTRAINT "Relationship_ownerId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Relationships" DROP CONSTRAINT "Relationship_subjectId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Notes" DROP CONSTRAINT "Status_authorId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Notes" DROP CONSTRAINT "Status_applicationId_Application_id_fk";
--> statement-breakpoint
ALTER TABLE "Notes" DROP CONSTRAINT "Status_reblogId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "Notes" DROP CONSTRAINT "Status_inReplyToPostId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "Notes" DROP CONSTRAINT "Status_quotingPostId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "NoteToMentions" DROP CONSTRAINT "StatusToMentions_statusId_Status_id_fk";
--> statement-breakpoint
ALTER TABLE "NoteToMentions" DROP CONSTRAINT "StatusToMentions_userId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Tokens" DROP CONSTRAINT "Token_userId_User_id_fk";
--> statement-breakpoint
ALTER TABLE "Tokens" DROP CONSTRAINT "Token_applicationId_Application_id_fk";
--> statement-breakpoint
ALTER TABLE "Users" DROP CONSTRAINT "User_instanceId_Instance_id_fk";
--> statement-breakpoint
DROP INDEX IF EXISTS "UserToPinnedNotes_userId_statusId_index";--> statement-breakpoint
DROP INDEX IF EXISTS "UserToPinnedNotes_statusId_index";--> statement-breakpoint
DROP INDEX IF EXISTS "Application_client_id_index";--> statement-breakpoint
DROP INDEX IF EXISTS "EmojiToStatus_emojiId_statusId_index";--> statement-breakpoint
DROP INDEX IF EXISTS "EmojiToStatus_statusId_index";--> statement-breakpoint
DROP INDEX IF EXISTS "Status_uri_index";--> statement-breakpoint
DROP INDEX IF EXISTS "StatusToMentions_statusId_userId_index";--> statement-breakpoint
DROP INDEX IF EXISTS "StatusToMentions_userId_index";--> statement-breakpoint
DROP INDEX IF EXISTS "User_uri_index";--> statement-breakpoint
DROP INDEX IF EXISTS "User_username_index";--> statement-breakpoint
DROP INDEX IF EXISTS "User_email_index";--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "UserToPinnedNotes_userId_noteId_index" ON "UserToPinnedNotes" ("userId","noteId");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "UserToPinnedNotes_noteId_index" ON "UserToPinnedNotes" ("noteId");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "Applications_client_id_index" ON "Applications" ("client_id");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "EmojiToNote_emojiId_noteId_index" ON "EmojiToNote" ("emojiId","noteId");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "EmojiToNote_noteId_index" ON "EmojiToNote" ("noteId");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "Notes_uri_index" ON "Notes" ("uri");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "NoteToMentions_noteId_userId_index" ON "NoteToMentions" ("noteId","userId");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "NoteToMentions_userId_index" ON "NoteToMentions" ("userId");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "Users_uri_index" ON "Users" ("uri");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "Users_username_index" ON "Users" ("username");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "Users_email_index" ON "Users" ("email");--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "EmojiToUser" ADD CONSTRAINT "EmojiToUser_emojiId_Emojis_id_fk" FOREIGN KEY ("emojiId") REFERENCES "Emojis"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "EmojiToUser" ADD CONSTRAINT "EmojiToUser_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "UserToPinnedNotes" ADD CONSTRAINT "UserToPinnedNotes_userId_Notes_id_fk" FOREIGN KEY ("userId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "UserToPinnedNotes" ADD CONSTRAINT "UserToPinnedNotes_noteId_Users_id_fk" FOREIGN KEY ("noteId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Attachments" ADD CONSTRAINT "Attachments_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Emojis" ADD CONSTRAINT "Emojis_instanceId_Instances_id_fk" FOREIGN KEY ("instanceId") REFERENCES "Instances"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "EmojiToNote" ADD CONSTRAINT "EmojiToNote_emojiId_Emojis_id_fk" FOREIGN KEY ("emojiId") REFERENCES "Emojis"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "EmojiToNote" ADD CONSTRAINT "EmojiToNote_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Flags" ADD CONSTRAINT "Flags_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Flags" ADD CONSTRAINT "Flags_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Likes" ADD CONSTRAINT "Likes_likerId_Users_id_fk" FOREIGN KEY ("likerId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Likes" ADD CONSTRAINT "Likes_likedId_Notes_id_fk" FOREIGN KEY ("likedId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "ModNotes" ADD CONSTRAINT "ModNotes_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "ModNotes" ADD CONSTRAINT "ModNotes_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "ModNotes" ADD CONSTRAINT "ModNotes_modId_Users_id_fk" FOREIGN KEY ("modId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "ModTags" ADD CONSTRAINT "ModTags_statusId_Notes_id_fk" FOREIGN KEY ("statusId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "ModTags" ADD CONSTRAINT "ModTags_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "ModTags" ADD CONSTRAINT "ModTags_modId_Users_id_fk" FOREIGN KEY ("modId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notifications" ADD CONSTRAINT "Notifications_notifiedId_Users_id_fk" FOREIGN KEY ("notifiedId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notifications" ADD CONSTRAINT "Notifications_accountId_Users_id_fk" FOREIGN KEY ("accountId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notifications" ADD CONSTRAINT "Notifications_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "OpenIdAccounts" ADD CONSTRAINT "OpenIdAccounts_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE set null ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "OpenIdLoginFlows" ADD CONSTRAINT "OpenIdLoginFlows_applicationId_Applications_id_fk" FOREIGN KEY ("applicationId") REFERENCES "Applications"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Relationships" ADD CONSTRAINT "Relationships_ownerId_Users_id_fk" FOREIGN KEY ("ownerId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Relationships" ADD CONSTRAINT "Relationships_subjectId_Users_id_fk" FOREIGN KEY ("subjectId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notes" ADD CONSTRAINT "Notes_authorId_Users_id_fk" FOREIGN KEY ("authorId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notes" ADD CONSTRAINT "Notes_applicationId_Applications_id_fk" FOREIGN KEY ("applicationId") REFERENCES "Applications"("id") ON DELETE set null ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notes" ADD CONSTRAINT "Notes_reblogId_Notes_id_fk" FOREIGN KEY ("reblogId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notes" ADD CONSTRAINT "Notes_replyId_Notes_id_fk" FOREIGN KEY ("replyId") REFERENCES "Notes"("id") ON DELETE set null ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Notes" ADD CONSTRAINT "Notes_quoteId_Notes_id_fk" FOREIGN KEY ("quoteId") REFERENCES "Notes"("id") ON DELETE set null ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "NoteToMentions" ADD CONSTRAINT "NoteToMentions_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "NoteToMentions" ADD CONSTRAINT "NoteToMentions_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Tokens" ADD CONSTRAINT "Tokens_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Tokens" ADD CONSTRAINT "Tokens_applicationId_Applications_id_fk" FOREIGN KEY ("applicationId") REFERENCES "Applications"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "Users" ADD CONSTRAINT "Users_instanceId_Instances_id_fk" FOREIGN KEY ("instanceId") REFERENCES "Instances"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -0,0 +1,8 @@
ALTER TABLE "ModTags" RENAME COLUMN "statusId" TO "noteId";--> statement-breakpoint
ALTER TABLE "ModTags" DROP CONSTRAINT "ModTags_statusId_Notes_id_fk";
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "ModTags" ADD CONSTRAINT "ModTags_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View file

@ -0,0 +1,15 @@
ALTER TABLE "UserToPinnedNotes" DROP CONSTRAINT "UserToPinnedNotes_userId_Notes_id_fk";
--> statement-breakpoint
ALTER TABLE "UserToPinnedNotes" DROP CONSTRAINT "UserToPinnedNotes_noteId_Users_id_fk";
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "UserToPinnedNotes" ADD CONSTRAINT "UserToPinnedNotes_userId_Users_id_fk" FOREIGN KEY ("userId") REFERENCES "Users"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "UserToPinnedNotes" ADD CONSTRAINT "UserToPinnedNotes_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "Notes"("id") ON DELETE cascade ON UPDATE cascade;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,69 +1,90 @@
{
"version": "5",
"dialect": "pg",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1712805159664,
"tag": "0000_illegal_living_lightning",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1713055774123,
"tag": "0001_salty_night_thrasher",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1713056370431,
"tag": "0002_stiff_ares",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1713056528340,
"tag": "0003_spicy_arachne",
"breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1713056712218,
"tag": "0004_burly_lockjaw",
"breakpoints": true
},
{
"idx": 5,
"version": "5",
"when": 1713056917973,
"tag": "0005_sleepy_puma",
"breakpoints": true
},
{
"idx": 6,
"version": "5",
"when": 1713057159867,
"tag": "0006_messy_network",
"breakpoints": true
},
{
"idx": 7,
"version": "5",
"when": 1713227918208,
"tag": "0007_naive_sleeper",
"breakpoints": true
},
{
"idx": 8,
"version": "5",
"when": 1713246700119,
"tag": "0008_flawless_brother_voodoo",
"breakpoints": true
}
]
"version": "5",
"dialect": "pg",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1712805159664,
"tag": "0000_illegal_living_lightning",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1713055774123,
"tag": "0001_salty_night_thrasher",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1713056370431,
"tag": "0002_stiff_ares",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1713056528340,
"tag": "0003_spicy_arachne",
"breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1713056712218,
"tag": "0004_burly_lockjaw",
"breakpoints": true
},
{
"idx": 5,
"version": "5",
"when": 1713056917973,
"tag": "0005_sleepy_puma",
"breakpoints": true
},
{
"idx": 6,
"version": "5",
"when": 1713057159867,
"tag": "0006_messy_network",
"breakpoints": true
},
{
"idx": 7,
"version": "5",
"when": 1713227918208,
"tag": "0007_naive_sleeper",
"breakpoints": true
},
{
"idx": 8,
"version": "5",
"when": 1713246700119,
"tag": "0008_flawless_brother_voodoo",
"breakpoints": true
},
{
"idx": 9,
"version": "5",
"when": 1713327832438,
"tag": "0009_easy_slyde",
"breakpoints": true
},
{
"idx": 10,
"version": "5",
"when": 1713327880929,
"tag": "0010_daffy_frightful_four",
"breakpoints": true
},
{
"idx": 11,
"version": "5",
"when": 1713333611707,
"tag": "0011_special_the_fury",
"breakpoints": true
}
]
}

View file

@ -11,31 +11,32 @@ import {
uniqueIndex,
uuid,
} from "drizzle-orm/pg-core";
import type { Source as APISource } from "~types/mastodon/source";
export const emoji = pgTable("Emoji", {
export const Emojis = pgTable("Emojis", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
shortcode: text("shortcode").notNull(),
url: text("url").notNull(),
visibleInPicker: boolean("visible_in_picker").notNull(),
alt: text("alt"),
contentType: text("content_type").notNull(),
instanceId: uuid("instanceId").references(() => instance.id, {
instanceId: uuid("instanceId").references(() => Instances.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
});
export const like = pgTable("Like", {
export const Likes = pgTable("Likes", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
likerId: uuid("likerId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
likedId: uuid("likedId")
.notNull()
.references(() => status.id, {
.references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
@ -44,7 +45,7 @@ export const like = pgTable("Like", {
.notNull(),
});
export const lysandObject = pgTable(
export const LysandObjects = pgTable(
"LysandObject",
{
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
@ -72,17 +73,17 @@ export const lysandObject = pgTable(
},
);
export const relationship = pgTable("Relationship", {
export const Relationships = pgTable("Relationships", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
ownerId: uuid("ownerId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
subjectId: uuid("subjectId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
@ -110,8 +111,8 @@ export const relationship = pgTable("Relationship", {
.notNull(),
});
export const application = pgTable(
"Application",
export const Applications = pgTable(
"Applications",
{
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
name: text("name").notNull(),
@ -129,12 +130,12 @@ export const application = pgTable(
},
);
export const applicationRelations = relations(application, ({ many }) => ({
tokens: many(token),
loginFlows: many(openIdLoginFlow),
export const ApplicationsRelations = relations(Applications, ({ many }) => ({
tokens: many(Tokens),
loginFlows: many(OpenIdLoginFlows),
}));
export const token = pgTable("Token", {
export const Tokens = pgTable("Tokens", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
tokenType: text("token_type").notNull(),
scope: text("scope").notNull(),
@ -143,17 +144,17 @@ export const token = pgTable("Token", {
createdAt: timestamp("created_at", { precision: 3, mode: "string" })
.defaultNow()
.notNull(),
userId: uuid("userId").references(() => user.id, {
userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
applicationId: uuid("applicationId").references(() => application.id, {
applicationId: uuid("applicationId").references(() => Applications.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
});
export const attachment = pgTable("Attachment", {
export const Attachments = pgTable("Attachments", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
url: text("url").notNull(),
remoteUrl: text("remote_url"),
@ -167,13 +168,13 @@ export const attachment = pgTable("Attachment", {
width: integer("width"),
height: integer("height"),
size: integer("size"),
statusId: uuid("statusId").references(() => status.id, {
noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
});
export const notification = pgTable("Notification", {
export const Notifications = pgTable("Notifications", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
type: text("type").notNull(),
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
@ -181,31 +182,31 @@ export const notification = pgTable("Notification", {
.notNull(),
notifiedId: uuid("notifiedId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
accountId: uuid("accountId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
statusId: uuid("statusId").references(() => status.id, {
noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
dismissed: boolean("dismissed").default(false).notNull(),
});
export const status = pgTable(
"Status",
export const Notes = pgTable(
"Notes",
{
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
uri: text("uri"),
authorId: uuid("authorId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
@ -222,11 +223,11 @@ export const status = pgTable(
content: text("content").default("").notNull(),
contentType: text("content_type").default("text/plain").notNull(),
visibility: text("visibility").notNull(),
inReplyToPostId: uuid("inReplyToPostId"),
quotingPostId: uuid("quotingPostId"),
replyId: uuid("replyId"),
quotingId: uuid("quoteId"),
sensitive: boolean("sensitive").notNull(),
spoilerText: text("spoiler_text").default("").notNull(),
applicationId: uuid("applicationId").references(() => application.id, {
applicationId: uuid("applicationId").references(() => Applications.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
@ -235,20 +236,20 @@ export const status = pgTable(
(table) => {
return {
uriKey: uniqueIndex().on(table.uri),
statusReblogIdFkey: foreignKey({
noteReblogIdFkey: foreignKey({
columns: [table.reblogId],
foreignColumns: [table.id],
})
.onUpdate("cascade")
.onDelete("cascade"),
statusInReplyToPostIdFkey: foreignKey({
columns: [table.inReplyToPostId],
noteReplyIdFkey: foreignKey({
columns: [table.replyId],
foreignColumns: [table.id],
})
.onUpdate("cascade")
.onDelete("set null"),
statusQuotingPostIdFkey: foreignKey({
columns: [table.quotingPostId],
noteQuotingIdFkey: foreignKey({
columns: [table.quotingId],
foreignColumns: [table.id],
})
.onUpdate("cascade")
@ -257,7 +258,7 @@ export const status = pgTable(
},
);
export const instance = pgTable("Instance", {
export const Instances = pgTable("Instances", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
baseUrl: text("base_url").notNull(),
name: text("name").notNull(),
@ -268,9 +269,9 @@ export const instance = pgTable("Instance", {
.notNull(),
});
export const openIdAccount = pgTable("OpenIdAccount", {
export const OpenIdAccounts = pgTable("OpenIdAccounts", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
userId: uuid("userId").references(() => user.id, {
userId: uuid("userId").references(() => Users.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
@ -278,8 +279,8 @@ export const openIdAccount = pgTable("OpenIdAccount", {
issuerId: text("issuer_id").notNull(),
});
export const user = pgTable(
"User",
export const Users = pgTable(
"Users",
{
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
uri: text("uri"),
@ -298,7 +299,7 @@ export const user = pgTable(
inbox: string;
outbox: string;
}> | null>(),
source: jsonb("source").notNull(),
source: jsonb("source").notNull().$type<APISource>(),
avatar: text("avatar").notNull(),
header: text("header").notNull(),
createdAt: timestamp("created_at", { precision: 3, mode: "string" })
@ -316,7 +317,7 @@ export const user = pgTable(
sanctions: text("sanctions").array(),
publicKey: text("public_key").notNull(),
privateKey: text("private_key"),
instanceId: uuid("instanceId").references(() => instance.id, {
instanceId: uuid("instanceId").references(() => Instances.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
@ -333,55 +334,55 @@ export const user = pgTable(
},
);
export const openIdLoginFlow = pgTable("OpenIdLoginFlow", {
export const OpenIdLoginFlows = pgTable("OpenIdLoginFlows", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
codeVerifier: text("code_verifier").notNull(),
applicationId: uuid("applicationId").references(() => application.id, {
applicationId: uuid("applicationId").references(() => Applications.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
issuerId: text("issuer_id").notNull(),
});
export const openIdLoginFlowRelations = relations(
openIdLoginFlow,
export const OpenIdLoginFlowsRelations = relations(
OpenIdLoginFlows,
({ one }) => ({
application: one(application, {
fields: [openIdLoginFlow.applicationId],
references: [application.id],
application: one(Applications, {
fields: [OpenIdLoginFlows.applicationId],
references: [Applications.id],
}),
}),
);
export const flag = pgTable("Flag", {
export const Flags = pgTable("Flags", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
flagType: text("flag_type").default("other").notNull(),
createdAt: timestamp("created_at", { precision: 3, mode: "string" })
.defaultNow()
.notNull(),
flaggeStatusId: uuid("flaggeStatusId").references(() => status.id, {
noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
flaggedUserId: uuid("flaggedUserId").references(() => user.id, {
userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
});
export const modNote = pgTable("ModNote", {
export const ModNotes = pgTable("ModNotes", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
notedStatusId: uuid("notedStatusId").references(() => status.id, {
nodeId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
notedUserId: uuid("notedUserId").references(() => user.id, {
userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
modId: uuid("modId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
@ -391,19 +392,19 @@ export const modNote = pgTable("ModNote", {
.notNull(),
});
export const modTag = pgTable("ModTag", {
export const ModTags = pgTable("ModTags", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
taggedStatusId: uuid("taggedStatusId").references(() => status.id, {
noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
taggedUserId: uuid("taggedUserId").references(() => user.id, {
userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
modId: uuid("modId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
@ -413,18 +414,18 @@ export const modTag = pgTable("ModTag", {
.notNull(),
});
export const emojiToUser = pgTable(
export const EmojiToUser = pgTable(
"EmojiToUser",
{
emojiId: uuid("emojiId")
.notNull()
.references(() => emoji.id, {
.references(() => Emojis.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
userId: uuid("userId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
@ -437,267 +438,267 @@ export const emojiToUser = pgTable(
},
);
export const emojiToUserRelations = relations(emojiToUser, ({ one }) => ({
emoji: one(emoji, {
fields: [emojiToUser.emojiId],
references: [emoji.id],
export const EmojiToUserRelations = relations(EmojiToUser, ({ one }) => ({
emoji: one(Emojis, {
fields: [EmojiToUser.emojiId],
references: [Emojis.id],
}),
user: one(user, {
fields: [emojiToUser.userId],
references: [user.id],
user: one(Users, {
fields: [EmojiToUser.userId],
references: [Users.id],
}),
}));
export const emojiToStatus = pgTable(
"EmojiToStatus",
export const EmojiToNote = pgTable(
"EmojiToNote",
{
emojiId: uuid("emojiId")
.notNull()
.references(() => emoji.id, {
.references(() => Emojis.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
statusId: uuid("statusId")
noteId: uuid("noteId")
.notNull()
.references(() => status.id, {
.references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
},
(table) => {
return {
abUnique: uniqueIndex().on(table.emojiId, table.statusId),
bIdx: index().on(table.statusId),
abUnique: uniqueIndex().on(table.emojiId, table.noteId),
bIdx: index().on(table.noteId),
};
},
);
export const statusToMentions = pgTable(
"StatusToMentions",
export const NoteToMentions = pgTable(
"NoteToMentions",
{
statusId: uuid("statusId")
noteId: uuid("noteId")
.notNull()
.references(() => status.id, {
.references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
userId: uuid("userId")
.notNull()
.references(() => user.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
},
(table) => {
return {
abUnique: uniqueIndex().on(table.statusId, table.userId),
abUnique: uniqueIndex().on(table.noteId, table.userId),
bIdx: index().on(table.userId),
};
},
);
export const userPinnedNotes = pgTable(
export const UserToPinnedNotes = pgTable(
"UserToPinnedNotes",
{
userId: uuid("userId")
.notNull()
.references(() => status.id, {
.references(() => Users.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
statusId: uuid("statusId")
noteId: uuid("noteId")
.notNull()
.references(() => user.id, {
.references(() => Notes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
},
(table) => {
return {
abUnique: uniqueIndex().on(table.userId, table.statusId),
bIdx: index().on(table.statusId),
abUnique: uniqueIndex().on(table.userId, table.noteId),
bIdx: index().on(table.noteId),
};
},
);
export const attachmentRelations = relations(attachment, ({ one }) => ({
status: one(status, {
fields: [attachment.statusId],
references: [status.id],
export const AttachmentsRelations = relations(Attachments, ({ one }) => ({
notes: one(Notes, {
fields: [Attachments.noteId],
references: [Notes.id],
}),
}));
export const userRelations = relations(user, ({ many, one }) => ({
emojis: many(emojiToUser),
pinnedNotes: many(userPinnedNotes),
statuses: many(status, {
relationName: "StatusToAuthor",
export const UsersRelations = relations(Users, ({ many, one }) => ({
emojis: many(EmojiToUser),
pinnedNotes: many(UserToPinnedNotes),
notes: many(Notes, {
relationName: "NoteToAuthor",
}),
likes: many(like),
relationships: many(relationship, {
likes: many(Likes),
relationships: many(Relationships, {
relationName: "RelationshipToOwner",
}),
relationshipSubjects: many(relationship, {
relationshipSubjects: many(Relationships, {
relationName: "RelationshipToSubject",
}),
notificationsMade: many(notification, {
notificationsMade: many(Notifications, {
relationName: "NotificationToAccount",
}),
notificationsReceived: many(notification, {
notificationsReceived: many(Notifications, {
relationName: "NotificationToNotified",
}),
openIdAccounts: many(openIdAccount),
flags: many(flag),
modNotes: many(modNote),
modTags: many(modTag),
tokens: many(token),
instance: one(instance, {
fields: [user.instanceId],
references: [instance.id],
openIdAccounts: many(OpenIdAccounts),
flags: many(Flags),
modNotes: many(ModNotes),
modTags: many(ModTags),
tokens: many(Tokens),
instance: one(Instances, {
fields: [Users.instanceId],
references: [Instances.id],
}),
mentionedIn: many(statusToMentions),
mentionedIn: many(NoteToMentions),
}));
export const relationshipRelations = relations(relationship, ({ one }) => ({
owner: one(user, {
fields: [relationship.ownerId],
references: [user.id],
export const RelationshipsRelations = relations(Relationships, ({ one }) => ({
owner: one(Users, {
fields: [Relationships.ownerId],
references: [Users.id],
relationName: "RelationshipToOwner",
}),
subject: one(user, {
fields: [relationship.subjectId],
references: [user.id],
subject: one(Users, {
fields: [Relationships.subjectId],
references: [Users.id],
relationName: "RelationshipToSubject",
}),
}));
export const tokenRelations = relations(token, ({ one }) => ({
user: one(user, {
fields: [token.userId],
references: [user.id],
export const TokensRelations = relations(Tokens, ({ one }) => ({
user: one(Users, {
fields: [Tokens.userId],
references: [Users.id],
}),
application: one(application, {
fields: [token.applicationId],
references: [application.id],
application: one(Applications, {
fields: [Tokens.applicationId],
references: [Applications.id],
}),
}));
export const statusToUserRelations = relations(statusToMentions, ({ one }) => ({
status: one(status, {
fields: [statusToMentions.statusId],
references: [status.id],
export const NotesToUsersRelations = relations(NoteToMentions, ({ one }) => ({
note: one(Notes, {
fields: [NoteToMentions.noteId],
references: [Notes.id],
}),
user: one(user, {
fields: [statusToMentions.userId],
references: [user.id],
user: one(Users, {
fields: [NoteToMentions.userId],
references: [Users.id],
}),
}));
export const userPinnedNotesRelations = relations(
userPinnedNotes,
export const UserToPinnedNotesRelations = relations(
UserToPinnedNotes,
({ one }) => ({
status: one(status, {
fields: [userPinnedNotes.statusId],
references: [status.id],
note: one(Notes, {
fields: [UserToPinnedNotes.noteId],
references: [Notes.id],
}),
user: one(user, {
fields: [userPinnedNotes.userId],
references: [user.id],
user: one(Users, {
fields: [UserToPinnedNotes.userId],
references: [Users.id],
}),
}),
);
export const statusRelations = relations(status, ({ many, one }) => ({
emojis: many(emojiToStatus),
author: one(user, {
fields: [status.authorId],
references: [user.id],
relationName: "StatusToAuthor",
export const NotesRelations = relations(Notes, ({ many, one }) => ({
emojis: many(EmojiToNote),
author: one(Users, {
fields: [Notes.authorId],
references: [Users.id],
relationName: "NoteToAuthor",
}),
attachments: many(attachment),
mentions: many(statusToMentions),
reblog: one(status, {
fields: [status.reblogId],
references: [status.id],
relationName: "StatusToReblog",
attachments: many(Attachments),
mentions: many(NoteToMentions),
reblog: one(Notes, {
fields: [Notes.reblogId],
references: [Notes.id],
relationName: "NoteToReblogs",
}),
usersThatHavePinned: many(userPinnedNotes),
inReplyTo: one(status, {
fields: [status.inReplyToPostId],
references: [status.id],
relationName: "StatusToReplying",
usersThatHavePinned: many(UserToPinnedNotes),
reply: one(Notes, {
fields: [Notes.replyId],
references: [Notes.id],
relationName: "NoteToReplies",
}),
quoting: one(status, {
fields: [status.quotingPostId],
references: [status.id],
relationName: "StatusToQuoting",
quote: one(Notes, {
fields: [Notes.quotingId],
references: [Notes.id],
relationName: "NoteToQuotes",
}),
application: one(application, {
fields: [status.applicationId],
references: [application.id],
application: one(Applications, {
fields: [Notes.applicationId],
references: [Applications.id],
}),
quotes: many(status, {
relationName: "StatusToQuoting",
quotes: many(Notes, {
relationName: "NoteToQuotes",
}),
replies: many(status, {
relationName: "StatusToReplying",
replies: many(Notes, {
relationName: "NoteToReplies",
}),
likes: many(like),
reblogs: many(status, {
relationName: "StatusToReblog",
likes: many(Likes),
reblogs: many(Notes, {
relationName: "NoteToReblogs",
}),
notifications: many(notification),
notifications: many(Notifications),
}));
export const notificationRelations = relations(notification, ({ one }) => ({
account: one(user, {
fields: [notification.accountId],
references: [user.id],
export const NotificationsRelations = relations(Notifications, ({ one }) => ({
account: one(Users, {
fields: [Notifications.accountId],
references: [Users.id],
relationName: "NotificationToAccount",
}),
notified: one(user, {
fields: [notification.notifiedId],
references: [user.id],
notified: one(Users, {
fields: [Notifications.notifiedId],
references: [Users.id],
relationName: "NotificationToNotified",
}),
status: one(status, {
fields: [notification.statusId],
references: [status.id],
note: one(Notes, {
fields: [Notifications.noteId],
references: [Notes.id],
}),
}));
export const likeRelations = relations(like, ({ one }) => ({
liker: one(user, {
fields: [like.likerId],
references: [user.id],
export const LikesRelations = relations(Likes, ({ one }) => ({
liker: one(Users, {
fields: [Likes.likerId],
references: [Users.id],
}),
liked: one(status, {
fields: [like.likedId],
references: [status.id],
liked: one(Notes, {
fields: [Likes.likedId],
references: [Notes.id],
}),
}));
export const emojiRelations = relations(emoji, ({ one, many }) => ({
instance: one(instance, {
fields: [emoji.instanceId],
references: [instance.id],
export const EmojisRelations = relations(Emojis, ({ one, many }) => ({
instance: one(Instances, {
fields: [Emojis.instanceId],
references: [Instances.id],
}),
users: many(emojiToUser),
statuses: many(emojiToStatus),
users: many(EmojiToUser),
notes: many(EmojiToNote),
}));
export const instanceRelations = relations(instance, ({ many }) => ({
users: many(user),
emojis: many(emoji),
export const InstancesRelations = relations(Instances, ({ many }) => ({
users: many(Users),
emojis: many(Emojis),
}));
export const emojiToStatusRelations = relations(emojiToStatus, ({ one }) => ({
emoji: one(emoji, {
fields: [emojiToStatus.emojiId],
references: [emoji.id],
export const EmojisToNotesRelations = relations(EmojiToNote, ({ one }) => ({
emoji: one(Emojis, {
fields: [EmojiToNote.emojiId],
references: [Emojis.id],
}),
status: one(status, {
fields: [emojiToStatus.statusId],
references: [status.id],
note: one(Notes, {
fields: [EmojiToNote.noteId],
references: [Notes.id],
}),
}));

View file

@ -4,7 +4,7 @@ import { config } from "config-manager";
import { count } from "drizzle-orm";
import { LogLevel, LogManager, type MultiLogManager } from "log-manager";
import { db, setupDatabase } from "~drizzle/db";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { createServer } from "~server";
const timeAtStart = performance.now();
@ -35,7 +35,7 @@ try {
.select({
count: count(),
})
.from(status)
.from(Notes)
)[0].count;
} catch (e) {
const error = e as Error;

View file

@ -43,13 +43,14 @@ import {
} from "~database/entities/User";
import { db } from "~drizzle/db";
import {
attachment,
emojiToStatus,
notification,
status,
statusToMentions,
user,
userRelations,
Attachments,
EmojiToNote,
NoteToMentions,
Notes,
Notifications,
UserToPinnedNotes,
Users,
UsersRelations,
} from "~drizzle/schema";
import { config } from "~packages/config-manager";
import type { Attachment as APIAttachment } from "~types/mastodon/attachment";
@ -64,16 +65,16 @@ export class Note {
static async fromId(id: string | null): Promise<Note | null> {
if (!id) return null;
return await Note.fromSql(eq(status.id, id));
return await Note.fromSql(eq(Notes.id, id));
}
static async fromIds(ids: string[]): Promise<Note[]> {
return await Note.manyFromSql(inArray(status.id, ids));
return await Note.manyFromSql(inArray(Notes.id, ids));
}
static async fromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(status.id),
orderBy: SQL<unknown> | undefined = desc(Notes.id),
) {
const found = await findFirstNote({
where: sql,
@ -86,7 +87,7 @@ export class Note {
static async manyFromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(status.id),
orderBy: SQL<unknown> | undefined = desc(Notes.id),
limit?: number,
offset?: number,
) {
@ -121,11 +122,10 @@ export class Note {
const usersThatCanSeePost = await findManyUsers({
where: (user, { isNotNull }) => isNotNull(user.instanceId),
with: {
...userRelations,
relationships: {
where: (relationship, { eq, and }) =>
and(
eq(relationship.subjectId, user.id),
eq(relationship.subjectId, Users.id),
eq(relationship.following, true),
),
},
@ -163,24 +163,37 @@ export class Note {
}
async getReplyChildren() {
return await Note.manyFromSql(
eq(status.inReplyToPostId, this.status.id),
);
return await Note.manyFromSql(eq(Notes.replyId, this.status.id));
}
async pin(pinner: User) {
return (
await db
.insert(UserToPinnedNotes)
.values({
noteId: this.status.id,
userId: pinner.id,
})
.returning()
)[0];
}
async unpin(unpinner: User) {
return await db
.delete(statusToMentions)
.where(
and(
eq(statusToMentions.statusId, this.status.id),
eq(statusToMentions.userId, unpinner.id),
),
);
return (
await db
.delete(UserToPinnedNotes)
.where(
and(
eq(NoteToMentions.noteId, this.status.id),
eq(NoteToMentions.userId, unpinner.id),
),
)
.returning()
)[0];
}
static async insert(values: InferInsertModel<typeof status>) {
return (await db.insert(status).values(values).returning())[0];
static async insert(values: InferInsertModel<typeof Notes>) {
return (await db.insert(Notes).values(values).returning())[0];
}
static async fromData(
@ -225,18 +238,18 @@ export class Note {
sensitive: is_sensitive,
spoilerText: spoiler_text,
uri: uri || null,
inReplyToPostId: replyId ?? null,
quotingPostId: quoteId ?? null,
replyId: replyId ?? null,
quotingId: quoteId ?? null,
applicationId: application?.id ?? null,
});
// Connect emojis
for (const emoji of foundEmojis) {
await db
.insert(emojiToStatus)
.insert(EmojiToNote)
.values({
emojiId: emoji.id,
statusId: newNote.id,
noteId: newNote.id,
})
.execute();
}
@ -244,9 +257,9 @@ export class Note {
// Connect mentions
for (const mention of mentions ?? []) {
await db
.insert(statusToMentions)
.insert(NoteToMentions)
.values({
statusId: newNote.id,
noteId: newNote.id,
userId: mention.id,
})
.execute();
@ -255,21 +268,21 @@ export class Note {
// Set attachment parents
if (media_attachments && media_attachments.length > 0) {
await db
.update(attachment)
.update(Attachments)
.set({
statusId: newNote.id,
noteId: newNote.id,
})
.where(inArray(attachment.id, media_attachments));
.where(inArray(Attachments.id, media_attachments));
}
// Send notifications for mentioned local users
for (const mention of mentions ?? []) {
if (mention.instanceId === null) {
await db.insert(notification).values({
await db.insert(Notifications).values({
accountId: author.id,
notifiedId: mention.id,
type: "mention",
statusId: newNote.id,
noteId: newNote.id,
});
}
}
@ -319,29 +332,29 @@ export class Note {
// Connect emojis
await db
.delete(emojiToStatus)
.where(eq(emojiToStatus.statusId, this.status.id));
.delete(EmojiToNote)
.where(eq(EmojiToNote.noteId, this.status.id));
for (const emoji of foundEmojis) {
await db
.insert(emojiToStatus)
.insert(EmojiToNote)
.values({
emojiId: emoji.id,
statusId: this.status.id,
noteId: this.status.id,
})
.execute();
}
// Connect mentions
await db
.delete(statusToMentions)
.where(eq(statusToMentions.statusId, this.status.id));
.delete(NoteToMentions)
.where(eq(NoteToMentions.noteId, this.status.id));
for (const mention of mentions ?? []) {
await db
.insert(statusToMentions)
.insert(NoteToMentions)
.values({
statusId: this.status.id,
noteId: this.status.id,
userId: mention.id,
})
.execute();
@ -350,11 +363,11 @@ export class Note {
// Set attachment parents
if (media_attachments && media_attachments.length > 0) {
await db
.update(attachment)
.update(Attachments)
.set({
statusId: this.status.id,
noteId: this.status.id,
})
.where(inArray(attachment.id, media_attachments));
.where(inArray(Attachments.id, media_attachments));
}
return await Note.fromId(newNote.id);
@ -363,8 +376,8 @@ export class Note {
async delete() {
return (
await db
.delete(status)
.where(eq(status.id, this.status.id))
.delete(Notes)
.where(eq(Notes.id, this.status.id))
.returning()
)[0];
}
@ -372,18 +385,15 @@ export class Note {
async update(newStatus: Partial<Status>) {
return (
await db
.update(status)
.update(Notes)
.set(newStatus)
.where(eq(status.id, this.status.id))
.where(eq(Notes.id, this.status.id))
.returning()
)[0];
}
static async deleteMany(ids: string[]) {
return await db
.delete(status)
.where(inArray(status.id, ids))
.returning();
return await db.delete(Notes).where(inArray(Notes.id, ids)).returning();
}
/**
@ -397,11 +407,11 @@ export class Note {
if (this.getStatus().visibility === "unlisted") return true;
if (this.getStatus().visibility === "private") {
return user
? await db.query.relationship.findFirst({
? await db.query.Relationships.findFirst({
where: (relationship, { and, eq }) =>
and(
eq(relationship.ownerId, user?.id),
eq(relationship.subjectId, status.authorId),
eq(relationship.subjectId, Notes.authorId),
eq(relationship.following, true),
),
})
@ -416,10 +426,10 @@ export class Note {
async toAPI(userFetching?: UserWithRelations | null): Promise<APIStatus> {
const data = this.getStatus();
const wasPinnedByUser = userFetching
? !!(await db.query.userPinnedNotes.findFirst({
? !!(await db.query.UserToPinnedNotes.findFirst({
where: (relation, { and, eq }) =>
and(
eq(relation.statusId, data.id),
eq(relation.noteId, data.id),
eq(relation.userId, userFetching?.id),
),
}))
@ -428,14 +438,14 @@ export class Note {
const wasRebloggedByUser = userFetching
? !!(await Note.fromSql(
and(
eq(status.authorId, userFetching?.id),
eq(status.reblogId, data.id),
eq(Notes.authorId, userFetching?.id),
eq(Notes.reblogId, data.id),
),
))
: false;
const wasMutedByUser = userFetching
? !!(await db.query.relationship.findFirst({
? !!(await db.query.Relationships.findFirst({
where: (relationship, { and, eq }) =>
and(
eq(relationship.ownerId, userFetching.id),
@ -468,8 +478,8 @@ export class Note {
return {
id: data.id,
in_reply_to_id: data.inReplyToPostId || null,
in_reply_to_account_id: data.inReplyTo?.authorId || null,
in_reply_to_id: data.replyId || null,
in_reply_to_account_id: data.reply?.authorId || null,
account: userToAPI(data.author),
created_at: new Date(data.createdAt).toISOString(),
application: data.application
@ -511,9 +521,9 @@ export class Note {
visibility: data.visibility as APIStatus["visibility"],
url: data.uri || this.getMastoURI(),
bookmarked: false,
quote: !!data.quotingPostId,
quote: !!data.quotingId,
// @ts-expect-error Pleroma extension
quote_id: data.quotingPostId || undefined,
quote_id: data.quotingId || undefined,
};
}
@ -546,8 +556,8 @@ export class Note {
),
is_sensitive: status.sensitive,
mentions: status.mentions.map((mention) => mention.uri || ""),
quotes: getStatusUri(status.quoting) ?? undefined,
replies_to: getStatusUri(status.inReplyTo) ?? undefined,
quotes: getStatusUri(status.quote) ?? undefined,
replies_to: getStatusUri(status.reply) ?? undefined,
subject: status.spoilerText,
visibility: status.visibility as Lysand.Visibility,
extensions: {
@ -567,10 +577,8 @@ export class Note {
let currentStatus: Note = this;
while (currentStatus.getStatus().inReplyToPostId) {
const parent = await Note.fromId(
currentStatus.getStatus().inReplyToPostId,
);
while (currentStatus.getStatus().replyId) {
const parent = await Note.fromId(currentStatus.getStatus().replyId);
if (!parent) {
break;

View file

@ -1,5 +1,5 @@
import { type SQL, gt } from "drizzle-orm";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { config } from "~packages/config-manager";
import { Note } from "./note";
@ -43,7 +43,7 @@ export class Timeline {
switch (this.type) {
case TimelineType.NOTE: {
const objectBefore = await Note.fromSql(
gt(status.id, objects[0].getStatus().id),
gt(Notes.id, objects[0].getStatus().id),
);
if (objectBefore) {
@ -57,7 +57,7 @@ export class Timeline {
if (objects.length >= (limit ?? 20)) {
const objectAfter = await Note.fromSql(
gt(
status.id,
Notes.id,
objects[objects.length - 1].getStatus().id,
),
);

View file

@ -77,7 +77,7 @@ export const processRoute = async (
return null;
});
if (!route) {
if (!route?.meta) {
return errorResponse("Route not found", 404);
}

View file

@ -4,7 +4,7 @@ import { z } from "zod";
import { TokenType } from "~database/entities/Token";
import { findFirstUser } from "~database/entities/User";
import { db } from "~drizzle/db";
import { token } from "~drizzle/schema";
import { Tokens } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -62,7 +62,7 @@ export default apiRoute<typeof meta, typeof schema>(
)
return redirectToLogin("Invalid username or password");
const application = await db.query.application.findFirst({
const application = await db.query.Applications.findFirst({
where: (app, { eq }) => eq(app.clientId, client_id),
});
@ -70,7 +70,7 @@ export default apiRoute<typeof meta, typeof schema>(
const code = randomBytes(32).toString("hex");
await db.insert(token).values({
await db.insert(Tokens).values({
accessToken: randomBytes(64).toString("base64url"),
code: code,
scope: scopes.join(" "),

View file

@ -4,7 +4,7 @@ import { z } from "zod";
import { TokenType } from "~database/entities/Token";
import { findFirstUser } from "~database/entities/User";
import { db } from "~drizzle/db";
import { token } from "~drizzle/schema";
import { Tokens } from "~drizzle/schema";
import { config } from "~packages/config-manager";
export const meta = applyConfig({
@ -54,7 +54,7 @@ export default apiRoute<typeof meta, typeof schema>(
const code = randomBytes(32).toString("hex");
const accessToken = randomBytes(64).toString("base64url");
await db.insert(token).values({
await db.insert(Tokens).values({
accessToken,
code: code,
scope: "read write follow push",

View file

@ -1,10 +1,4 @@
import { randomBytes } from "node:crypto";
import { apiRoute, applyConfig } from "@api";
import { z } from "zod";
import { TokenType } from "~database/entities/Token";
import { findFirstUser } from "~database/entities/User";
import { db } from "~drizzle/db";
import { token } from "~drizzle/schema";
import { config } from "~packages/config-manager";
export const meta = applyConfig({

View file

@ -1,7 +1,7 @@
import { apiRoute, applyConfig } from "@api";
import { and, eq } from "drizzle-orm";
import { db } from "~drizzle/db";
import { application, token } from "~drizzle/schema";
import { Applications, Tokens } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -18,10 +18,7 @@ export const meta = applyConfig({
/**
* OAuth Code flow
*/
export default apiRoute<{
email: string;
password: string;
}>(async (req, matchedRoute) => {
export default apiRoute(async (req, matchedRoute) => {
const redirect_uri = decodeURIComponent(matchedRoute.query.redirect_uri);
const client_id = matchedRoute.query.client_id;
const code = matchedRoute.query.code;
@ -37,9 +34,9 @@ export default apiRoute<{
const foundToken = await db
.select()
.from(token)
.leftJoin(application, eq(token.applicationId, application.id))
.where(and(eq(token.code, code), eq(application.clientId, client_id)))
.from(Tokens)
.leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
.where(and(eq(Tokens.code, code), eq(Applications.clientId, client_id)))
.limit(1);
if (!foundToken || foundToken.length <= 0)

View file

@ -0,0 +1,98 @@
import { afterAll, describe, expect, test } from "bun:test";
import { config } from "config-manager";
import {
deleteOldTestUsers,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { meta } from "./block";
import type { Relationship as APIRelationship } from "~types/mastodon/relationship";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(2);
afterAll(async () => {
await deleteUsers();
});
// /api/v1/accounts/:id/block
describe(meta.route, () => {
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
},
),
);
expect(response.status).toBe(401);
});
test("should return 404 if user not found", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(
":id",
"00000000-0000-0000-0000-000000000000",
),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(404);
});
test("should block user", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.blocking).toBe(true);
});
test("should return 200 if user already blocked", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.blocking).toBe(true);
});
});

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -48,11 +48,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
}
await db
.update(relationship)
.update(Relationships)
.set({
blocking: true,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
return jsonResponse(relationshipToAPI(foundRelationship));
});

View file

@ -0,0 +1,98 @@
import { afterAll, describe, expect, test } from "bun:test";
import { config } from "config-manager";
import {
deleteOldTestUsers,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { meta } from "./follow";
import type { Relationship as APIRelationship } from "~types/mastodon/relationship";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(2);
afterAll(async () => {
await deleteUsers();
});
// /api/v1/accounts/:id/follow
describe(meta.route, () => {
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
},
),
);
expect(response.status).toBe(401);
});
test("should return 404 if user not found", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(
":id",
"00000000-0000-0000-0000-000000000000",
),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(404);
});
test("should follow user", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.following).toBe(true);
});
test("should return 200 if user already followed", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.following).toBe(true);
});
});

View file

@ -57,7 +57,7 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(follower.id, max_id) : undefined,
since_id ? gte(follower.id, since_id) : undefined,
min_id ? gt(follower.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${otherUser.id} AND "Relationship"."ownerId" = ${follower.id} AND "Relationship"."following" = true)`,
sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${otherUser.id} AND "Relationships"."ownerId" = ${follower.id} AND "Relationships"."following" = true)`,
),
// @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id),

View file

@ -57,7 +57,7 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(following.id, max_id) : undefined,
since_id ? gte(following.id, since_id) : undefined,
min_id ? gt(following.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${following.id} AND "Relationship"."ownerId" = ${otherUser.id} AND "Relationship"."following" = true)`,
sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${following.id} AND "Relationships"."ownerId" = ${otherUser.id} AND "Relationships"."following" = true)`,
),
// @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id),

View file

@ -0,0 +1,98 @@
import { afterAll, describe, expect, test } from "bun:test";
import { config } from "config-manager";
import {
deleteOldTestUsers,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { meta } from "./mute";
import type { Relationship as APIRelationship } from "~types/mastodon/relationship";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(2);
afterAll(async () => {
await deleteUsers();
});
// /api/v1/accounts/:id/mute
describe(meta.route, () => {
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
},
),
);
expect(response.status).toBe(401);
});
test("should return 404 if user not found", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(
":id",
"00000000-0000-0000-0000-000000000000",
),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(404);
});
test("should mute user", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.muting).toBe(true);
});
test("should return 200 if user already muted", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.muting).toBe(true);
});
});

View file

@ -8,7 +8,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -66,12 +66,12 @@ export default apiRoute<typeof meta, typeof schema>(
}
await db
.update(relationship)
.update(Relationships)
.set({
muting: true,
mutingNotifications: notifications ?? true,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
// TODO: Implement duration

View file

@ -8,7 +8,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -58,11 +58,11 @@ export default apiRoute<typeof meta, typeof schema>(
foundRelationship.note = comment ?? "";
await db
.update(relationship)
.update(Relationships)
.set({
note: foundRelationship.note,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
return jsonResponse(relationshipToAPI(foundRelationship));
},

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -48,11 +48,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
foundRelationship.endorsed = true;
await db
.update(relationship)
.update(Relationships)
.set({
endorsed: true,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
}
return jsonResponse(relationshipToAPI(foundRelationship));

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -48,23 +48,23 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
foundRelationship.followedBy = false;
await db
.update(relationship)
.update(Relationships)
.set({
followedBy: false,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
if (otherUser.instanceId === null) {
// Also remove from followers list
await db
.update(relationship)
.update(Relationships)
.set({
following: false,
})
.where(
and(
eq(relationship.ownerId, otherUser.id),
eq(relationship.subjectId, self.id),
eq(Relationships.ownerId, otherUser.id),
eq(Relationships.subjectId, self.id),
),
);
}

View file

@ -0,0 +1,200 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { config } from "config-manager";
import {
deleteOldTestUsers,
getTestStatuses,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import type { Account as APIAccount } from "~types/mastodon/account";
import { meta } from "./statuses";
import type { Status as APIStatus } from "~types/mastodon/status";
import { db } from "~drizzle/db";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(5);
const timeline = (await getTestStatuses(40, users[1])).toReversed();
const timeline2 = (await getTestStatuses(40, users[2])).toReversed();
afterAll(async () => {
await deleteUsers();
});
beforeAll(async () => {
const response = await sendTestRequest(
new Request(
new URL(
`/api/v1/statuses/${timeline2[0].id}/reblog`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
});
// /api/v1/accounts/:id/statuses
describe(meta.route, () => {
test("should return 200 with statuses", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const data = (await response.json()) as APIStatus[];
expect(data.length).toBe(20);
// Should have reblogs
expect(data[0].reblog).toBeDefined();
});
test("should exclude reblogs", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(
":id",
users[1].id,
)}?exclude_reblogs=true`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const data = (await response.json()) as APIStatus[];
expect(data.length).toBe(20);
// Should not have reblogs
expect(data[0].reblog).toBeNull();
});
test("should exclude replies", async () => {
// Create reply
const replyResponse = await sendTestRequest(
new Request(new URL("/api/v1/statuses", config.http.base_url), {
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
status: "Reply",
in_reply_to_id: timeline[0].id,
federate: false,
}),
}),
);
expect(replyResponse.status).toBe(200);
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(
":id",
users[1].id,
)}?exclude_replies=true`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const data = (await response.json()) as APIStatus[];
expect(data.length).toBe(20);
// Should not have replies
expect(data[0].in_reply_to_id).toBeNull();
});
test("should only include pins", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(":id", users[1].id)}?pinned=true`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const data = (await response.json()) as APIStatus[];
expect(data.length).toBe(0);
// Create pin
const pinResponse = await sendTestRequest(
new Request(
new URL(
`/api/v1/statuses/${timeline[3].id}/pin`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
},
},
),
);
expect(pinResponse.status).toBe(200);
const response2 = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(":id", users[1].id)}?pinned=true`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response2.status).toBe(200);
const data2 = (await response2.json()) as APIStatus[];
expect(data2.length).toBe(1);
});
});

View file

@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@response";
import { and, eq, gt, gte, isNull, lt, sql } from "drizzle-orm";
import { z } from "zod";
import { findFirstUser } from "~database/entities/User";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { Timeline } from "~packages/database-interface/timeline";
export const meta = applyConfig({
@ -41,7 +41,6 @@ export default apiRoute<typeof meta, typeof schema>(
return errorResponse("Invalid ID, must be of type UUIDv7", 404);
}
// TODO: Add pinned
const {
max_id,
min_id,
@ -49,6 +48,7 @@ export default apiRoute<typeof meta, typeof schema>(
limit,
exclude_reblogs,
only_media,
exclude_replies,
pinned,
} = extraData.parsedRequest;
@ -58,41 +58,20 @@ export default apiRoute<typeof meta, typeof schema>(
if (!user) return errorResponse("User not found", 404);
if (pinned) {
const { objects, link } = await Timeline.getNoteTimeline(
and(
max_id ? lt(status.id, max_id) : undefined,
since_id ? gte(status.id, since_id) : undefined,
min_id ? gt(status.id, min_id) : undefined,
eq(status.authorId, id),
sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."statusId" = ${status.id} AND "UserToPinnedNotes"."userId" = ${user.id})`,
only_media
? sql`EXISTS (SELECT 1 FROM "Attachment" WHERE "Attachment"."statusId" = ${status.id})`
: undefined,
),
limit,
req.url,
);
return jsonResponse(
await Promise.all(objects.map((note) => note.toAPI(user))),
200,
{
Link: link,
},
);
}
const { objects, link } = await Timeline.getNoteTimeline(
and(
max_id ? lt(status.id, max_id) : undefined,
since_id ? gte(status.id, since_id) : undefined,
min_id ? gt(status.id, min_id) : undefined,
eq(status.authorId, id),
max_id ? lt(Notes.id, max_id) : undefined,
since_id ? gte(Notes.id, since_id) : undefined,
min_id ? gt(Notes.id, min_id) : undefined,
eq(Notes.authorId, id),
only_media
? sql`EXISTS (SELECT 1 FROM "Attachment" WHERE "Attachment"."statusId" = ${status.id})`
? sql`EXISTS (SELECT 1 FROM "Attachments" WHERE "Attachments"."noteId" = ${Notes.id})`
: undefined,
exclude_reblogs ? isNull(status.reblogId) : undefined,
pinned
? sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = ${Notes.id} AND "UserToPinnedNotes"."userId" = ${user.id})`
: undefined,
exclude_reblogs ? isNull(Notes.reblogId) : undefined,
exclude_replies ? isNull(Notes.replyId) : undefined,
),
limit,
req.url,

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -44,11 +44,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
foundRelationship.blocking = false;
await db
.update(relationship)
.update(Relationships)
.set({
blocking: false,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
}
return jsonResponse(relationshipToAPI(foundRelationship));

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -48,12 +48,12 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
foundRelationship.following = false;
await db
.update(relationship)
.update(Relationships)
.set({
following: false,
requested: false,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
}
return jsonResponse(relationshipToAPI(foundRelationship));

View file

@ -0,0 +1,115 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { config } from "config-manager";
import {
deleteOldTestUsers,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { meta } from "./unmute";
import type { Relationship as APIRelationship } from "~types/mastodon/relationship";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(2);
afterAll(async () => {
await deleteUsers();
});
beforeAll(async () => {
await sendTestRequest(
new Request(
new URL(
`/api/v1/accounts/${users[0].id}/mute`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
},
},
),
);
});
// /api/v1/accounts/:id/unmute
describe(meta.route, () => {
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
},
),
);
expect(response.status).toBe(401);
});
test("should return 404 if user not found", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(
":id",
"00000000-0000-0000-0000-000000000000",
),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(404);
});
test("should unmute user", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.muting).toBe(false);
});
test("should return 200 if user already unmuted", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.muting).toBe(false);
});
});

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -49,12 +49,12 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
foundRelationship.mutingNotifications = false;
await db
.update(relationship)
.update(Relationships)
.set({
muting: false,
mutingNotifications: false,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
}
return jsonResponse(relationshipToAPI(foundRelationship));

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -48,11 +48,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
foundRelationship.endorsed = false;
await db
.update(relationship)
.update(Relationships)
.set({
endorsed: false,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
}
return jsonResponse(relationshipToAPI(foundRelationship));

View file

@ -32,7 +32,7 @@ export default apiRoute<typeof meta, typeof schema>(
const { id: ids } = extraData.parsedRequest;
const idFollowerRelationships = await db.query.relationship.findMany({
const idFollowerRelationships = await db.query.Relationships.findMany({
columns: {
ownerId: true,
},
@ -48,7 +48,7 @@ export default apiRoute<typeof meta, typeof schema>(
}
// Find users that you follow in idFollowerRelationships
const relevantRelationships = await db.query.relationship.findMany({
const relevantRelationships = await db.query.Relationships.findMany({
columns: {
subjectId: true,
},

View file

@ -36,7 +36,7 @@ export default apiRoute<typeof meta, typeof schema>(
const { id: ids } = extraData.parsedRequest;
const relationships = await db.query.relationship.findMany({
const relationships = await db.query.Relationships.findMany({
where: (relationship, { inArray, and, eq }) =>
and(
inArray(relationship.subjectId, ids),

View file

@ -19,7 +19,7 @@ import {
resolveWebFinger,
userToAPI,
} from "~database/entities/User";
import { user } from "~drizzle/schema";
import { Users } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -94,7 +94,7 @@ export default apiRoute<typeof meta, typeof schema>(
like(account.displayName, `%${q}%`),
like(account.username, `%${q}%`),
following
? sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${user.id} AND "Relationship"."ownerId" = ${account.id} AND "Relationship"."following" = true)`
? sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${Users.id} AND "Relationships"."ownerId" = ${account.id} AND "Relationships"."following" = true)`
: undefined,
),
offset,

View file

@ -13,7 +13,7 @@ import { getUrl } from "~database/entities/Attachment";
import { parseEmojis } from "~database/entities/Emoji";
import { findFirstUser, userToAPI } from "~database/entities/User";
import { db } from "~drizzle/db";
import { emojiToUser, user } from "~drizzle/schema";
import { EmojiToUser, Users } from "~drizzle/schema";
import type { Source as APISource } from "~types/mastodon/source";
export const meta = applyConfig({
@ -194,7 +194,7 @@ export default apiRoute<typeof meta, typeof schema>(
);
await db
.update(user)
.update(Users)
.set({
displayName: self.displayName,
note: self.note,
@ -205,22 +205,22 @@ export default apiRoute<typeof meta, typeof schema>(
isDiscoverable: self.isDiscoverable,
source: self.source || undefined,
})
.where(eq(user.id, self.id));
.where(eq(Users.id, self.id));
// Connect emojis, if any
for (const emoji of self.emojis) {
await db
.delete(emojiToUser)
.delete(EmojiToUser)
.where(
and(
eq(emojiToUser.emojiId, emoji.id),
eq(emojiToUser.userId, self.id),
eq(EmojiToUser.emojiId, emoji.id),
eq(EmojiToUser.userId, self.id),
),
)
.execute();
await db
.insert(emojiToUser)
.insert(EmojiToUser)
.values({
emojiId: emoji.id,
userId: self.id,

View file

@ -3,7 +3,7 @@ import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { z } from "zod";
import { db } from "~drizzle/db";
import { application } from "~drizzle/schema";
import { Applications } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -34,7 +34,7 @@ export default apiRoute<typeof meta, typeof schema>(
const app = (
await db
.insert(application)
.insert(Applications)
.values({
name: client_name || "",
redirectUris: redirect_uris || "",

View file

@ -46,7 +46,7 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(subject.id, max_id) : undefined,
since_id ? gte(subject.id, since_id) : undefined,
min_id ? gt(subject.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${subject.id} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."blocking" = true)`,
sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${subject.id} AND "Relationships"."ownerId" = ${user.id} AND "Relationships"."blocking" = true)`,
),
limit,
// @ts-expect-error Yes I KNOW the types are wrong

View file

@ -16,7 +16,7 @@ export const meta = applyConfig({
});
export default apiRoute(async () => {
const emojis = await db.query.emoji.findMany({
const emojis = await db.query.Emojis.findMany({
where: (emoji, { isNull }) => isNull(emoji.instanceId),
with: {
instance: true,

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { and, gt, gte, lt, sql } from "drizzle-orm";
import { z } from "zod";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { Timeline } from "~packages/database-interface/timeline";
export const meta = applyConfig({
@ -34,10 +34,10 @@ export default apiRoute<typeof meta, typeof schema>(
const { objects, link } = await Timeline.getNoteTimeline(
and(
max_id ? lt(status.id, max_id) : undefined,
since_id ? gte(status.id, since_id) : undefined,
min_id ? gt(status.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Like" WHERE "Like"."likedId" = ${status.id} AND "Like"."likerId" = ${user.id})`,
max_id ? lt(Notes.id, max_id) : undefined,
since_id ? gte(Notes.id, since_id) : undefined,
min_id ? gt(Notes.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = ${Notes.id} AND "Likes"."likerId" = ${user.id})`,
),
limit,
req.url,

View file

@ -11,7 +11,7 @@ import {
sendFollowAccept,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -43,28 +43,28 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Authorize follow request
await db
.update(relationship)
.update(Relationships)
.set({
requested: false,
following: true,
})
.where(
and(
eq(relationship.subjectId, user.id),
eq(relationship.ownerId, account.id),
eq(Relationships.subjectId, user.id),
eq(Relationships.ownerId, account.id),
),
);
// Update followedBy for other user
await db
.update(relationship)
.update(Relationships)
.set({
followedBy: true,
})
.where(
and(
eq(relationship.subjectId, account.id),
eq(relationship.ownerId, user.id),
eq(Relationships.subjectId, account.id),
eq(Relationships.ownerId, user.id),
),
);

View file

@ -11,7 +11,7 @@ import {
sendFollowReject,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema";
import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -43,28 +43,28 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Reject follow request
await db
.update(relationship)
.update(Relationships)
.set({
requested: false,
following: false,
})
.where(
and(
eq(relationship.subjectId, user.id),
eq(relationship.ownerId, account.id),
eq(Relationships.subjectId, user.id),
eq(Relationships.ownerId, account.id),
),
);
// Update followedBy for other user
await db
.update(relationship)
.update(Relationships)
.set({
followedBy: false,
})
.where(
and(
eq(relationship.subjectId, account.id),
eq(relationship.ownerId, user.id),
eq(Relationships.subjectId, account.id),
eq(Relationships.ownerId, user.id),
),
);

View file

@ -44,7 +44,7 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(subject.id, max_id) : undefined,
since_id ? gte(subject.id, since_id) : undefined,
min_id ? gt(subject.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${user.id} AND "Relationship"."ownerId" = ${subject.id} AND "Relationship"."requested" = true)`,
sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${user.id} AND "Relationships"."ownerId" = ${subject.id} AND "Relationships"."requested" = true)`,
),
limit,
// @ts-expect-error Yes I KNOW the types are wrong

View file

@ -3,7 +3,7 @@ import { jsonResponse } from "@response";
import { and, count, countDistinct, eq, gte, isNull, sql } from "drizzle-orm";
import { findFirstUser, userToAPI } from "~database/entities/User";
import { db } from "~drizzle/db";
import { instance, status, user } from "~drizzle/schema";
import { Instances, Notes, Users } from "~drizzle/schema";
import manifest from "~package.json";
import type { Instance as APIInstance } from "~types/mastodon/instance";
@ -30,9 +30,9 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({
count: count(),
})
.from(status)
.from(Notes)
.where(
sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${status.authorId} AND "User"."instanceId" IS NULL)`,
sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${Notes.authorId} AND "User"."instanceId" IS NULL)`,
)
)[0].count;
@ -41,8 +41,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({
count: count(),
})
.from(user)
.where(isNull(user.instanceId))
.from(Users)
.where(isNull(Users.instanceId))
)[0].count;
const contactAccount = await findFirstUser({
@ -54,15 +54,15 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const monthlyActiveUsers = (
await db
.select({
count: countDistinct(user),
count: countDistinct(Users),
})
.from(user)
.leftJoin(status, eq(user.id, status.authorId))
.from(Users)
.leftJoin(Notes, eq(Users.id, Notes.authorId))
.where(
and(
isNull(user.instanceId),
isNull(Users.instanceId),
gte(
status.createdAt,
Notes.createdAt,
new Date(
Date.now() - 30 * 24 * 60 * 60 * 1000,
).toISOString(),
@ -76,7 +76,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({
count: count(),
})
.from(instance)
.from(Instances)
)[0].count;
// TODO: fill in more values

View file

@ -8,7 +8,7 @@ import { LocalMediaBackend, S3MediaBackend } from "media-manager";
import { z } from "zod";
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
import { db } from "~drizzle/db";
import { attachment } from "~drizzle/schema";
import { Attachments } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET", "PUT"],
@ -48,7 +48,7 @@ export default apiRoute<typeof meta, typeof schema>(
return errorResponse("Invalid ID, must be of type UUIDv7", 404);
}
const foundAttachment = await db.query.attachment.findFirst({
const foundAttachment = await db.query.Attachments.findFirst({
where: (attachment, { eq }) => eq(attachment.id, id),
});
@ -98,12 +98,12 @@ export default apiRoute<typeof meta, typeof schema>(
) {
const newAttachment = (
await db
.update(attachment)
.update(Attachments)
.set({
description: descriptionText,
thumbnailUrl,
})
.where(eq(attachment.id, id))
.where(eq(Attachments.id, id))
.returning()
)[0];

View file

@ -9,7 +9,7 @@ import sharp from "sharp";
import { z } from "zod";
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
import { db } from "~drizzle/db";
import { attachment } from "~drizzle/schema";
import { Attachments } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -128,7 +128,7 @@ export default apiRoute<typeof meta, typeof schema>(
const newAttachment = (
await db
.insert(attachment)
.insert(Attachments)
.values({
url,
thumbnailUrl,

View file

@ -0,0 +1,102 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { config } from "config-manager";
import {
deleteOldTestUsers,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { meta } from "./index";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(3);
afterAll(async () => {
await deleteUsers();
});
beforeAll(async () => {
const response = await sendTestRequest(
new Request(
new URL(
`/api/v1/accounts/${users[1].id}/mute`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
});
// /api/v1/mutes
describe(meta.route, () => {
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[1].id),
config.http.base_url,
),
{
method: "GET",
},
),
);
expect(response.status).toBe(401);
});
test("should return mutes", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
}),
);
expect(response.status).toBe(200);
const body = await response.json();
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: users[1].id,
}),
]),
);
});
test("should return mutes after unmute", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`/api/v1/accounts/${users[1].id}/unmute`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const response2 = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "GET",
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
}),
);
expect(response2.status).toBe(200);
const body = await response2.json();
expect(body).toEqual([]);
});
});

View file

@ -45,7 +45,7 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(subject.id, max_id) : undefined,
since_id ? gte(subject.id, since_id) : undefined,
min_id ? gt(subject.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${subject.id} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."muting" = true)`,
sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${subject.id} AND "Relationships"."ownerId" = ${user.id} AND "Relationships"."muting" = true)`,
),
limit,
// @ts-expect-error Yes I KNOW the types are wrong

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { eq } from "drizzle-orm";
import { db } from "~drizzle/db";
import { notification } from "~drizzle/schema";
import { Notifications } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -27,11 +27,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!user) return errorResponse("Unauthorized", 401);
await db
.update(notification)
.update(Notifications)
.set({
dismissed: true,
})
.where(eq(notification.id, id));
.where(eq(Notifications.id, id));
return jsonResponse({});
});

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { eq } from "drizzle-orm";
import { db } from "~drizzle/db";
import { notification } from "~drizzle/schema";
import { Notifications } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -22,11 +22,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!user) return errorResponse("Unauthorized", 401);
await db
.update(notification)
.update(Notifications)
.set({
dismissed: true,
})
.where(eq(notification.notifiedId, user.id));
.where(eq(Notifications.notifiedId, user.id));
return jsonResponse({});
});

View file

@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@response";
import { inArray } from "drizzle-orm";
import { z } from "zod";
import { db } from "~drizzle/db";
import { notification } from "~drizzle/schema";
import { Notifications } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["DELETE"],
@ -30,11 +30,11 @@ export default apiRoute<typeof meta, typeof schema>(
const { ids } = extraData.parsedRequest;
await db
.update(notification)
.update(Notifications)
.set({
dismissed: true,
})
.where(inArray(notification.id, ids));
.where(inArray(Notifications.id, ids));
return jsonResponse({});
},

View file

@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@response";
import { eq } from "drizzle-orm";
import { userToAPI } from "~database/entities/User";
import { db } from "~drizzle/db";
import { user } from "~drizzle/schema";
import { Users } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["DELETE"],
@ -25,7 +25,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!self) return errorResponse("Unauthorized", 401);
await db.update(user).set({ avatar: "" }).where(eq(user.id, self.id));
await db.update(Users).set({ avatar: "" }).where(eq(Users.id, self.id));
return jsonResponse(
userToAPI({

View file

@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@response";
import { eq } from "drizzle-orm";
import { userToAPI } from "~database/entities/User";
import { db } from "~drizzle/db";
import { user } from "~drizzle/schema";
import { Users } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["DELETE"],
@ -26,7 +26,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!self) return errorResponse("Unauthorized", 401);
// Delete user header
await db.update(user).set({ header: "" }).where(eq(user.id, self.id));
await db.update(Users).set({ header: "" }).where(eq(Users.id, self.id));
return jsonResponse(
userToAPI({

View file

@ -34,14 +34,14 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!foundStatus) return errorResponse("Record not found", 404);
const relations = user
? await db.query.relationship.findMany({
? await db.query.Relationships.findMany({
where: (relationship, { eq }) =>
eq(relationship.ownerId, user.id),
})
: null;
const relationSubjects = user
? await db.query.relationship.findMany({
? await db.query.Relationships.findMany({
where: (relationship, { eq }) =>
eq(relationship.subjectId, user.id),
})

View file

@ -36,7 +36,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!status?.isViewableByUser(user))
return errorResponse("Record not found", 404);
const existingLike = await db.query.like.findFirst({
const existingLike = await db.query.Likes.findFirst({
where: (like, { and, eq }) =>
and(
eq(like.likedId, status.getStatus().id),

View file

@ -57,9 +57,9 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(liker.id, max_id) : undefined,
since_id ? gte(liker.id, since_id) : undefined,
min_id ? gt(liker.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Like" WHERE "Like"."likedId" = ${
sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = ${
status.getStatus().id
} AND "Like"."likerId" = ${liker.id})`,
} AND "Likes"."likerId" = ${liker.id})`,
),
// @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id),

View file

@ -121,7 +121,7 @@ export default apiRoute<typeof meta, typeof schema>(
// Check if media attachments are all valid
if (media_ids && media_ids.length > 0) {
const foundAttachments = await db.query.attachment.findMany({
const foundAttachments = await db.query.Attachments.findMany({
where: (attachment, { inArray }) =>
inArray(attachment.id, media_ids),
});

View file

@ -1,7 +1,7 @@
import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { db } from "~drizzle/db";
import { statusToMentions } from "~drizzle/schema";
import { NoteToMentions, UserToPinnedNotes } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({
@ -40,10 +40,10 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Check if post is already pinned
if (
await db.query.userPinnedNotes.findFirst({
await db.query.UserToPinnedNotes.findFirst({
where: (userPinnedNote, { and, eq }) =>
and(
eq(userPinnedNote.statusId, foundStatus.getStatus().id),
eq(userPinnedNote.noteId, foundStatus.getStatus().id),
eq(userPinnedNote.userId, user.id),
),
})
@ -51,10 +51,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
return errorResponse("Already pinned", 422);
}
await db.insert(statusToMentions).values({
statusId: foundStatus.getStatus().id,
userId: user.id,
});
await foundStatus.pin(user);
return jsonResponse(await foundStatus.toAPI(user));
});

View file

@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@response";
import { and, eq } from "drizzle-orm";
import { z } from "zod";
import { db } from "~drizzle/db";
import { notification, status } from "~drizzle/schema";
import { Notes, Notifications } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({
@ -44,13 +44,12 @@ export default apiRoute<typeof meta, typeof schema>(
if (!foundStatus?.isViewableByUser(user))
return errorResponse("Record not found", 404);
const existingReblog = await db.query.status.findFirst({
where: (status, { and, eq }) =>
and(
eq(status.authorId, user.id),
eq(status.reblogId, status.id),
),
});
const existingReblog = await Note.fromSql(
and(
eq(Notes.authorId, user.id),
eq(Notes.reblogId, foundStatus.getStatus().id),
),
);
if (existingReblog) {
return errorResponse("Already reblogged", 422);
@ -77,11 +76,11 @@ export default apiRoute<typeof meta, typeof schema>(
// Create notification for reblog if reblogged user is on the same instance
if (foundStatus.getAuthor().instanceId === user.instanceId) {
await db.insert(notification).values({
await db.insert(Notifications).values({
accountId: user.id,
notifiedId: foundStatus.getAuthor().id,
type: "reblog",
statusId: foundStatus.getStatus().reblogId,
noteId: foundStatus.getStatus().reblogId,
});
}

View file

@ -57,9 +57,9 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(reblogger.id, max_id) : undefined,
since_id ? gte(reblogger.id, since_id) : undefined,
min_id ? gt(reblogger.id, min_id) : undefined,
sql`EXISTS (SELECT 1 FROM "Status" WHERE "Status"."reblogId" = ${
sql`EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."reblogId" = ${
status.getStatus().id
} AND "Status"."authorId" = ${reblogger.id})`,
} AND "Notes"."authorId" = ${reblogger.id})`,
),
// @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id),

View file

@ -1,7 +1,7 @@
import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { and, eq } from "drizzle-orm";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
import type { Status as APIStatus } from "~types/mastodon/status";
@ -38,8 +38,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const existingReblog = await Note.fromSql(
and(
eq(status.authorId, user.id),
eq(status.reblogId, foundStatus.getStatus().id),
eq(Notes.authorId, user.id),
eq(Notes.reblogId, foundStatus.getStatus().id),
),
);

View file

@ -70,7 +70,6 @@ export default apiRoute<typeof meta, typeof schema>(
const {
status,
media_ids,
"poll[expires_in]": expires_in,
"poll[options]": options,
in_reply_to_id,
quote_id,
@ -119,12 +118,6 @@ export default apiRoute<typeof meta, typeof schema>(
sanitizedStatus = await sanitizeHtml(status ?? "");
}
// Get reply account and status if exists
const replyStatus: StatusWithRelations | null =
(await Note.fromId(in_reply_to_id ?? null))?.getStatus() ?? null;
const quote: StatusWithRelations | null =
(await Note.fromId(quote_id ?? null))?.getStatus() ?? null;
// Check if status body doesnt match filters
if (
config.filters.note_content.some((filter) => status?.match(filter))
@ -134,18 +127,31 @@ export default apiRoute<typeof meta, typeof schema>(
// Check if media attachments are all valid
if (media_ids && media_ids.length > 0) {
const foundAttachments = await db.query.attachment
.findMany({
where: (attachment, { inArray }) =>
inArray(attachment.id, media_ids),
})
.catch(() => []);
const foundAttachments = await db.query.Attachments.findMany({
where: (attachment, { inArray }) =>
inArray(attachment.id, media_ids),
}).catch(() => []);
if (foundAttachments.length !== (media_ids ?? []).length) {
return errorResponse("Invalid media IDs", 422);
}
}
// Check that in_reply_to_id and quote_id are real posts if provided
if (in_reply_to_id) {
const foundReply = await Note.fromId(in_reply_to_id);
if (!foundReply) {
return errorResponse("Invalid in_reply_to_id (not found)", 422);
}
}
if (quote_id) {
const foundQuote = await Note.fromId(quote_id);
if (!foundQuote) {
return errorResponse("Invalid quote_id (not found)", 422);
}
}
const mentions = await parseTextMentions(sanitizedStatus);
const newNote = await Note.fromData(

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { and, eq, gt, gte, lt, or, sql } from "drizzle-orm";
import { z } from "zod";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { Timeline } from "~packages/database-interface/timeline";
export const meta = applyConfig({
@ -38,18 +38,16 @@ export default apiRoute<typeof meta, typeof schema>(
const { objects, link } = await Timeline.getNoteTimeline(
and(
and(
max_id ? lt(status.id, max_id) : undefined,
since_id ? gte(status.id, since_id) : undefined,
min_id ? gt(status.id, min_id) : undefined,
max_id ? lt(Notes.id, max_id) : undefined,
since_id ? gte(Notes.id, since_id) : undefined,
min_id ? gt(Notes.id, min_id) : undefined,
),
or(
eq(status.authorId, user.id),
// All statuses where the user is mentioned, using table _StatusToUser which has a: status.id and b: user.id
// WHERE format (... = ...)
sql`EXISTS (SELECT 1 FROM "StatusToMentions" WHERE "StatusToMentions"."statusId" = ${status.id} AND "StatusToMentions"."userId" = ${user.id})`,
eq(Notes.authorId, user.id),
sql`EXISTS (SELECT 1 FROM "NoteToMentions" WHERE "NoteToMentions"."noteId" = ${Notes.id} AND "NoteToMentions"."userId" = ${user.id})`,
// All statuses from users that the user is following
// WHERE format (... = ...)
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${status.authorId} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."following" = true)`,
sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${Notes.authorId} AND "Relationships"."ownerId" = ${user.id} AND "Relationships"."following" = true)`,
),
),
limit,

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { and, gt, gte, lt, sql } from "drizzle-orm";
import { z } from "zod";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { Timeline } from "~packages/database-interface/timeline";
export const meta = applyConfig({
@ -39,18 +39,18 @@ export default apiRoute<typeof meta, typeof schema>(
const { objects, link } = await Timeline.getNoteTimeline(
and(
max_id ? lt(status.id, max_id) : undefined,
since_id ? gte(status.id, since_id) : undefined,
min_id ? gt(status.id, min_id) : undefined,
max_id ? lt(Notes.id, max_id) : undefined,
since_id ? gte(Notes.id, since_id) : undefined,
min_id ? gt(Notes.id, min_id) : undefined,
// use authorId to grab user, then use user.instanceId to filter local/remote statuses
remote
? sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${status.authorId} AND "User"."instanceId" IS NOT NULL)`
? sql`EXISTS (SELECT 1 FROM "Users" WHERE "Users"."id" = ${Notes.authorId} AND "Users"."instanceId" IS NOT NULL)`
: undefined,
local
? sql`EXISTS (SELECT 1 FROM "User" WHERE "User"."id" = ${status.authorId} AND "User"."instanceId" IS NULL)`
? sql`EXISTS (SELECT 1 FROM "Users" WHERE "Users"."id" = ${Notes.authorId} AND "Users"."instanceId" IS NULL)`
: undefined,
only_media
? sql`EXISTS (SELECT 1 FROM "Attachment" WHERE "Attachment"."statusId" = ${status.id})`
? sql`EXISTS (SELECT 1 FROM "Attachments" WHERE "Attachments"."noteId" = ${Notes.id})`
: undefined,
),
limit,

View file

@ -3,7 +3,7 @@ import { jsonResponse } from "@response";
import { and, countDistinct, eq, gte, isNull } from "drizzle-orm";
import { findFirstUser, userToAPI } from "~database/entities/User";
import { db } from "~drizzle/db";
import { status, user } from "~drizzle/schema";
import { Notes, Users } from "~drizzle/schema";
import manifest from "~package.json";
export const meta = applyConfig({
@ -32,15 +32,15 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const monthlyActiveUsers = (
await db
.select({
count: countDistinct(user),
count: countDistinct(Users),
})
.from(user)
.leftJoin(status, eq(user.id, status.authorId))
.from(Users)
.leftJoin(Notes, eq(Users.id, Notes.authorId))
.where(
and(
isNull(user.instanceId),
isNull(Users.instanceId),
gte(
status.createdAt,
Notes.createdAt,
new Date(
Date.now() - 30 * 24 * 60 * 60 * 1000,
).toISOString(),

View file

@ -9,7 +9,7 @@ import sharp from "sharp";
import { z } from "zod";
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
import { db } from "~drizzle/db";
import { attachment } from "~drizzle/schema";
import { Attachments } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["POST"],
@ -124,7 +124,7 @@ export default apiRoute<typeof meta, typeof schema>(
const newAttachment = (
await db
.insert(attachment)
.insert(Attachments)
.values({
url,
thumbnailUrl,

View file

@ -11,7 +11,7 @@ import {
userToAPI,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { instance, status, user } from "~drizzle/schema";
import { Instances, Notes, Users } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
import { LogLevel } from "~packages/log-manager";
@ -88,14 +88,14 @@ export default apiRoute<typeof meta, typeof schema>(
const accountId = (
await db
.select({
id: user.id,
id: Users.id,
})
.from(user)
.leftJoin(instance, eq(user.instanceId, instance.id))
.from(Users)
.leftJoin(Instances, eq(Users.instanceId, Instances.id))
.where(
and(
eq(user.username, username),
eq(instance.baseUrl, domain),
eq(Users.username, username),
eq(Instances.baseUrl, domain),
),
)
)[0]?.id;
@ -181,16 +181,16 @@ export default apiRoute<typeof meta, typeof schema>(
const statuses = await Note.manyFromSql(
and(
inArray(
status.id,
Notes.id,
statusResults.map((hit) => hit.id),
),
account_id ? eq(status.authorId, account_id) : undefined,
account_id ? eq(Notes.authorId, account_id) : undefined,
self
? sql`EXISTS (SELECT 1 FROM Relationships WHERE Relationships.subjectId = ${
self?.id
} AND Relationships.following = ${
following ? true : false
} AND Relationships.ownerId = ${status.authorId})`
} AND Relationships.ownerId = ${Notes.authorId})`
: undefined,
),
);

View file

@ -7,7 +7,7 @@ import {
processDiscoveryResponse,
} from "oauth4webapi";
import { db } from "~drizzle/db";
import { openIdLoginFlow } from "~drizzle/schema";
import { OpenIdLoginFlows } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -61,7 +61,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const codeVerifier = generateRandomCodeVerifier();
const application = await db.query.application.findFirst({
const application = await db.query.Applications.findFirst({
where: (application, { eq }) => eq(application.clientId, clientId),
});
@ -72,7 +72,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Store into database
const newFlow = (
await db
.insert(openIdLoginFlow)
.insert(OpenIdLoginFlows)
.values({
codeVerifier,
applicationId: application.id,

View file

@ -16,7 +16,7 @@ import {
import { TokenType } from "~database/entities/Token";
import { findFirstUser } from "~database/entities/User";
import { db } from "~drizzle/db";
import { token } from "~drizzle/schema";
import { Tokens } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -166,7 +166,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const code = randomBytes(32).toString("hex");
await db.insert(token).values({
await db.insert(Tokens).values({
accessToken: randomBytes(64).toString("base64url"),
code: code,
scope: flow.application.scopes,

View file

@ -45,7 +45,7 @@ export default apiRoute<typeof meta, typeof schema>(
);
// Get associated token
const application = await db.query.application.findFirst({
const application = await db.query.Applications.findFirst({
where: (application, { eq, and }) =>
and(
eq(application.clientId, client_id),
@ -61,7 +61,7 @@ export default apiRoute<typeof meta, typeof schema>(
401,
);
const token = await db.query.token.findFirst({
const token = await db.query.Tokens.findFirst({
where: (token, { eq }) =>
eq(token.code, code) && eq(token.applicationId, application.id),
});

View file

@ -4,7 +4,7 @@ import { and, eq, inArray, sql } from "drizzle-orm";
import type * as Lysand from "lysand-types";
import { type Like, likeToLysand } from "~database/entities/Like";
import { db } from "~drizzle/db";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({
@ -27,15 +27,15 @@ export default apiRoute(async (req, matchedRoute) => {
foundObject = await Note.fromSql(
and(
eq(status.id, uuid),
inArray(status.visibility, ["public", "unlisted"]),
eq(Notes.id, uuid),
inArray(Notes.visibility, ["public", "unlisted"]),
),
);
apiObject = foundObject ? foundObject.toLysand() : null;
if (!foundObject) {
foundObject =
(await db.query.like.findFirst({
(await db.query.Likes.findFirst({
where: (like, { eq, and }) =>
and(
eq(like.id, uuid),

View file

@ -11,7 +11,7 @@ import {
sendFollowAccept,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { notification, relationship } from "~drizzle/schema";
import { Notifications, Relationships } from "~drizzle/schema";
import { LogLevel } from "~packages/log-manager";
export const meta = applyConfig({
@ -161,7 +161,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
}
await db
.update(relationship)
.update(Relationships)
.set({
following: !user.isLocked,
requested: user.isLocked,
@ -169,9 +169,9 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
notifying: true,
languages: [],
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
await db.insert(notification).values({
await db.insert(Notifications).values({
accountId: account.id,
type: user.isLocked ? "follow_request" : "follow",
notifiedId: user.id,
@ -209,12 +209,12 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
}
await db
.update(relationship)
.update(Relationships)
.set({
following: true,
requested: false,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
return response("Follow request accepted", 200);
}
@ -237,12 +237,12 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
}
await db
.update(relationship)
.update(Relationships)
.set({
requested: false,
following: false,
})
.where(eq(relationship.id, foundRelationship.id));
.where(eq(Relationships.id, foundRelationship.id));
return response("Follow request rejected", 200);
}

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
import { and, count, eq, inArray } from "drizzle-orm";
import { db } from "~drizzle/db";
import { status } from "~drizzle/schema";
import { Notes } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({
@ -25,8 +25,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const notes = await Note.manyFromSql(
and(
eq(status.authorId, uuid),
inArray(status.visibility, ["public", "unlisted"]),
eq(Notes.authorId, uuid),
inArray(Notes.visibility, ["public", "unlisted"]),
),
undefined,
20,
@ -37,11 +37,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({
count: count(),
})
.from(status)
.from(Notes)
.where(
and(
eq(status.authorId, uuid),
inArray(status.visibility, ["public", "unlisted"]),
eq(Notes.authorId, uuid),
inArray(Notes.visibility, ["public", "unlisted"]),
),
);

View file

@ -2,7 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { config } from "config-manager";
import { eq } from "drizzle-orm";
import { db } from "~drizzle/db";
import { emoji } from "~drizzle/schema";
import { Emojis } from "~drizzle/schema";
import type { Emoji as APIEmoji } from "~types/mastodon/emoji";
import type { Instance as APIInstance } from "~types/mastodon/instance";
import { getTestUsers, sendTestRequest, wrapRelativeUrl } from "./utils";
@ -18,7 +18,7 @@ describe("API Tests", () => {
describe("GET /api/v1/custom_emojis", () => {
beforeAll(async () => {
await db.insert(emoji).values({
await db.insert(Emojis).values({
shortcode: "test",
url: "https://example.com/test.png",
contentType: "image/png",
@ -55,7 +55,7 @@ describe("API Tests", () => {
});
afterAll(async () => {
await db.delete(emoji).where(eq(emoji.shortcode, "test"));
await db.delete(Emojis).where(eq(Emojis.shortcode, "test"));
});
});
});

View file

@ -124,68 +124,6 @@ describe("API Tests", () => {
});
});
describe("POST /api/v1/accounts/:id/follow", () => {
test("should follow the specified user and return an APIRelationship object", async () => {
const response = await sendTestRequest(
new Request(
wrapRelativeUrl(
`/api/v1/accounts/${user2.id}/follow`,
base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json",
);
const relationship = (await response.json()) as APIRelationship;
expect(relationship.id).toBe(user2.id);
expect(relationship.following).toBe(true);
});
});
describe("POST /api/v1/accounts/:id/unfollow", () => {
test("should unfollow the specified user and return an APIRelationship object", async () => {
const response = await sendTestRequest(
new Request(
wrapRelativeUrl(
`/api/v1/accounts/${user2.id}/unfollow`,
base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json",
);
const account = (await response.json()) as APIRelationship;
expect(account.id).toBe(user2.id);
expect(account.following).toBe(false);
});
});
describe("POST /api/v1/accounts/:id/remove_from_followers", () => {
test("should remove the specified user from the authenticated user's followers and return an APIRelationship object", async () => {
const response = await sendTestRequest(
@ -302,123 +240,6 @@ describe("API Tests", () => {
});
});
describe("POST /api/v1/accounts/:id/mute with notifications parameter", () => {
test("should mute the specified user and return an APIRelationship object with notifications set to false", async () => {
const response = await sendTestRequest(
new Request(
wrapRelativeUrl(
`/api/v1/accounts/${user2.id}/mute`,
base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ notifications: true }),
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json",
);
const account = (await response.json()) as APIRelationship;
expect(account.id).toBe(user2.id);
expect(account.muting).toBe(true);
expect(account.muting_notifications).toBe(true);
});
test("should mute the specified user and return an APIRelationship object with notifications set to true", async () => {
const response = await sendTestRequest(
new Request(
wrapRelativeUrl(
`/api/v1/accounts/${user2.id}/mute`,
base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ notifications: false }),
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json",
);
const account = (await response.json()) as APIRelationship;
expect(account.id).toBe(user2.id);
expect(account.muting).toBe(true);
expect(account.muting_notifications).toBe(true);
});
});
describe("GET /api/v1/mutes", () => {
test("should return an array of APIAccount objects for the user's muted accounts", async () => {
const response = await sendTestRequest(
new Request(wrapRelativeUrl("/api/v1/mutes", base_url), {
method: "GET",
headers: {
Authorization: `Bearer ${token.accessToken}`,
},
}),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json",
);
const body = (await response.json()) as APIAccount[];
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBe(1);
expect(body[0].id).toBe(user2.id);
});
});
describe("POST /api/v1/accounts/:id/unmute", () => {
test("should unmute the specified user and return an APIRelationship object", async () => {
const response = await sendTestRequest(
new Request(
wrapRelativeUrl(
`/api/v1/accounts/${user2.id}/unmute`,
base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${token.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json",
);
const account = (await response.json()) as APIRelationship;
expect(account.id).toBe(user2.id);
expect(account.muting).toBe(false);
});
});
describe("POST /api/v1/accounts/:id/pin", () => {
test("should pin the specified user and return an APIRelationship object", async () => {
const response = await sendTestRequest(

View file

@ -344,35 +344,6 @@ describe("API Tests", () => {
});
});
describe("GET /api/v1/statuses/:id/favourited_by", () => {
test("should return an array of User objects who favourited the specified status", async () => {
const response = await sendTestRequest(
new Request(
wrapRelativeUrl(
`${base_url}/api/v1/statuses/${status?.id}/favourited_by`,
base_url,
),
{
method: "GET",
headers: {
Authorization: `Bearer ${token.accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json",
);
const users = (await response.json()) as APIAccount[];
expect(users.length).toBe(1);
expect(users[0].id).toBe(user.id);
});
});
describe("POST /api/v1/statuses/:id/unfavourite", () => {
test("should unfavourite the specified status object", async () => {
// Unfavourite the status

View file

@ -7,7 +7,7 @@ import {
createNewLocalUser,
} from "~database/entities/User";
import { db } from "~drizzle/db";
import { status, token, user } from "~drizzle/schema";
import { Notes, Tokens, Users } from "~drizzle/schema";
import { server } from "~index";
import { Note } from "~packages/database-interface/note";
/**
@ -26,7 +26,7 @@ export function wrapRelativeUrl(url: string, base_url: string) {
export const deleteOldTestUsers = async () => {
// Deletes all users that match the test username (test-<32 random characters>)
await db.delete(user).where(like(user.username, "test-%"));
await db.delete(Users).where(like(Users.username, "test-%"));
};
export const getTestUsers = async (count: number) => {
@ -51,7 +51,7 @@ export const getTestUsers = async (count: number) => {
}
const tokens = await db
.insert(token)
.insert(Tokens)
.values(
users.map((u) => ({
accessToken: randomBytes(32).toString("hex"),
@ -69,9 +69,9 @@ export const getTestUsers = async (count: number) => {
tokens,
passwords,
deleteUsers: async () => {
await db.delete(user).where(
await db.delete(Users).where(
inArray(
user.id,
Users.id,
users.map((u) => u.id),
),
);
@ -107,10 +107,10 @@ export const getTestStatuses = async (
return (
await Note.manyFromSql(
inArray(
status.id,
Notes.id,
statuses.map((s) => s.id),
),
asc(status.id),
asc(Notes.id),
)
).map((n) => n.getStatus());
};

View file

@ -6,7 +6,7 @@ import { Meilisearch } from "meilisearch";
import type { Status } from "~database/entities/Status";
import type { User } from "~database/entities/User";
import { db } from "~drizzle/db";
import { status, user } from "~drizzle/schema";
import { Notes, Users } from "~drizzle/schema";
export const meilisearch = new Meilisearch({
host: `${config.meilisearch.host}:${config.meilisearch.port}`,
@ -83,7 +83,7 @@ export const getNthDatabaseAccountBatch = (
n: number,
batchSize = 1000,
): Promise<Record<string, string | Date>[]> => {
return db.query.user.findMany({
return db.query.Users.findMany({
offset: n * batchSize,
limit: batchSize,
columns: {
@ -101,7 +101,7 @@ export const getNthDatabaseStatusBatch = (
n: number,
batchSize = 1000,
): Promise<Record<string, string | Date>[]> => {
return db.query.status.findMany({
return db.query.Notes.findMany({
offset: n * batchSize,
limit: batchSize,
columns: {
@ -123,7 +123,7 @@ export const rebuildSearchIndexes = async (
.select({
count: count(),
})
.from(user)
.from(Users)
)[0].count;
for (let i = 0; i < accountCount / batchSize; i++) {
@ -156,7 +156,7 @@ export const rebuildSearchIndexes = async (
.select({
count: count(),
})
.from(status)
.from(Notes)
)[0].count;
for (let i = 0; i < statusCount / batchSize; i++) {

View file

@ -15,7 +15,7 @@ export async function fetchTimeline<T extends User | Status | Notification>(
args:
| Parameters<typeof findManyNotes>[0]
| Parameters<typeof findManyUsers>[0]
| Parameters<typeof db.query.notification.findMany>[0],
| Parameters<typeof db.query.Notifications.findMany>[0],
req: Request,
) {
// BEFORE: Before in a top-to-bottom order, so the most recent posts