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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import type { InferSelectModel } from "drizzle-orm"; import type { InferSelectModel } from "drizzle-orm";
import { db } from "~drizzle/db"; 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 { Note } from "~packages/database-interface/note";
import type { Notification as APINotification } from "~types/mastodon/notification"; import type { Notification as APINotification } from "~types/mastodon/notification";
import type { StatusWithRelations } from "./Status"; import type { StatusWithRelations } from "./Status";
@ -12,7 +12,7 @@ import {
userToAPI, userToAPI,
} from "./User"; } from "./User";
export type Notification = InferSelectModel<typeof notification>; export type Notification = InferSelectModel<typeof Notifications>;
export type NotificationWithRelations = Notification & { export type NotificationWithRelations = Notification & {
status: StatusWithRelations | null; status: StatusWithRelations | null;
@ -20,9 +20,9 @@ export type NotificationWithRelations = Notification & {
}; };
export const findManyNotifications = async ( export const findManyNotifications = async (
query: Parameters<typeof db.query.notification.findMany>[0], query: Parameters<typeof db.query.Notifications.findMany>[0],
): Promise<NotificationWithRelations[]> => { ): Promise<NotificationWithRelations[]> => {
const output = await db.query.notification.findMany({ const output = await db.query.Notifications.findMany({
...query, ...query,
with: { with: {
...query?.with, ...query?.with,
@ -30,7 +30,7 @@ export const findManyNotifications = async (
with: { with: {
...userRelations, ...userRelations,
}, },
extras: userExtrasTemplate("notification_account"), extras: userExtrasTemplate("Notifications_account"),
}, },
}, },
extras: { extras: {
@ -42,7 +42,7 @@ export const findManyNotifications = async (
output.map(async (notif) => ({ output.map(async (notif) => ({
...notif, ...notif,
account: transformOutputToUserWithRelations(notif.account), 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 { InferSelectModel } from "drizzle-orm";
import type * as Lysand from "lysand-types"; import type * as Lysand from "lysand-types";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { lysandObject } from "~drizzle/schema"; import { LysandObjects } from "~drizzle/schema";
import { findFirstUser } from "./User"; import { findFirstUser } from "./User";
export type LysandObject = InferSelectModel<typeof lysandObject>; export type LysandObject = InferSelectModel<typeof LysandObjects>;
/** /**
* Represents a Lysand object in the database. * Represents a Lysand object in the database.
@ -29,7 +29,7 @@ export const createFromObject = async (
where: (user, { eq }) => eq(user.uri, authorUri), where: (user, { eq }) => eq(user.uri, authorUri),
}); });
return await db.insert(lysandObject).values({ return await db.insert(LysandObjects).values({
authorId: author?.id, authorId: author?.id,
createdAt: new Date(object.created_at).toISOString(), createdAt: new Date(object.created_at).toISOString(),
extensions: object.extensions, extensions: object.extensions,

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import type { InferSelectModel } from "drizzle-orm"; import type { InferSelectModel } from "drizzle-orm";
import type { token } from "~drizzle/schema"; import type { Tokens } from "~drizzle/schema";
/** /**
* The type of token. * The type of token.
@ -8,4 +8,4 @@ export enum TokenType {
BEARER = "Bearer", 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 type * as Lysand from "lysand-types";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { import {
application, Applications,
emojiToUser, EmojiToUser,
instance, Instances,
notification, Notifications,
relationship, Relationships,
token, Tokens,
user, Users,
} from "~drizzle/schema"; } from "~drizzle/schema";
import { LogLevel } from "~packages/log-manager"; import { LogLevel } from "~packages/log-manager";
import type { Account as APIAccount } from "~types/mastodon/account"; import type { Account as APIAccount } from "~types/mastodon/account";
@ -31,14 +31,14 @@ import { addInstanceIfNotExists } from "./Instance";
import { createNewRelationship } from "./Relationship"; import { createNewRelationship } from "./Relationship";
import type { Token } from "./Token"; import type { Token } from "./Token";
export type User = InferSelectModel<typeof user>; export type User = InferSelectModel<typeof Users>;
export type UserWithInstance = User & { export type UserWithInstance = User & {
instance: InferSelectModel<typeof instance> | null; instance: InferSelectModel<typeof Instances> | null;
}; };
export type UserWithRelations = User & { export type UserWithRelations = User & {
instance: InferSelectModel<typeof instance> | null; instance: InferSelectModel<typeof Instances> | null;
emojis: EmojiWithInstance[]; emojis: EmojiWithInstance[];
followerCount: number; followerCount: number;
followingCount: number; followingCount: number;
@ -46,8 +46,8 @@ export type UserWithRelations = User & {
}; };
export type UserWithRelationsAndRelationships = UserWithRelations & { export type UserWithRelationsAndRelationships = UserWithRelations & {
relationships: InferSelectModel<typeof relationship>[]; relationships: InferSelectModel<typeof Relationships>[];
relationshipSubjects: InferSelectModel<typeof relationship>[]; relationshipSubjects: InferSelectModel<typeof Relationships>[];
}; };
export const userRelations: { export const userRelations: {
@ -76,15 +76,15 @@ export const userRelations: {
export const userExtras = { export const userExtras = {
followerCount: 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", "follower_count",
), ),
followingCount: 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", "following_count",
), ),
statusCount: 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", "status_count",
), ),
}; };
@ -92,15 +92,15 @@ export const userExtras = {
export const userExtrasTemplate = (name: string) => ({ export const userExtrasTemplate = (name: string) => ({
// @ts-ignore // @ts-ignore
followerCount: sql([ 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"), ]).as("follower_count"),
// @ts-ignore // @ts-ignore
followingCount: sql([ 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"), ]).as("following_count"),
// @ts-ignore // @ts-ignore
statusCount: sql([ statusCount: sql([
`(SELECT COUNT(*) FROM "Status" "statuses" WHERE "statuses"."authorId" = "${name}".id)`, `(SELECT COUNT(*) FROM "Notes" WHERE "Notes"."authorId" = "${name}".id)`,
]).as("status_count"), ]).as("status_count"),
}); });
@ -151,12 +151,12 @@ export const followRequestUser = async (
reblogs = false, reblogs = false,
notify = false, notify = false,
languages: string[] = [], languages: string[] = [],
): Promise<InferSelectModel<typeof relationship>> => { ): Promise<InferSelectModel<typeof Relationships>> => {
const isRemote = followee.instanceId !== null; const isRemote = followee.instanceId !== null;
const updatedRelationship = ( const updatedRelationship = (
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
following: isRemote ? false : !followee.isLocked, following: isRemote ? false : !followee.isLocked,
requested: isRemote ? true : followee.isLocked, requested: isRemote ? true : followee.isLocked,
@ -164,7 +164,7 @@ export const followRequestUser = async (
notifying: notify, notifying: notify,
languages: languages, languages: languages,
}) })
.where(eq(relationship.id, relationshipId)) .where(eq(Relationships.id, relationshipId))
.returning() .returning()
)[0]; )[0];
@ -195,17 +195,17 @@ export const followRequestUser = async (
return ( return (
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
following: false, following: false,
requested: false, requested: false,
}) })
.where(eq(relationship.id, relationshipId)) .where(eq(Relationships.id, relationshipId))
.returning() .returning()
)[0]; )[0];
} }
} else { } else {
await db.insert(notification).values({ await db.insert(Notifications).values({
accountId: follower.id, accountId: follower.id,
type: followee.isLocked ? "follow_request" : "follow", type: followee.isLocked ? "follow_request" : "follow",
notifiedId: followee.id, notifiedId: followee.id,
@ -277,7 +277,7 @@ export const transformOutputToUserWithRelations = (
emojiId: string; emojiId: string;
emoji?: EmojiWithInstance; emoji?: EmojiWithInstance;
}[]; }[];
instance: InferSelectModel<typeof instance> | null; instance: InferSelectModel<typeof Instances> | null;
endpoints: unknown; endpoints: unknown;
}, },
): UserWithRelations => { ): UserWithRelations => {
@ -306,9 +306,9 @@ export const transformOutputToUserWithRelations = (
}; };
export const findManyUsers = async ( export const findManyUsers = async (
query: Parameters<typeof db.query.user.findMany>[0], query: Parameters<typeof db.query.Users.findMany>[0],
): Promise<UserWithRelations[]> => { ): Promise<UserWithRelations[]> => {
const output = await db.query.user.findMany({ const output = await db.query.Users.findMany({
...query, ...query,
with: { with: {
...userRelations, ...userRelations,
@ -324,9 +324,9 @@ export const findManyUsers = async (
}; };
export const findFirstUser = async ( export const findFirstUser = async (
query: Parameters<typeof db.query.user.findFirst>[0], query: Parameters<typeof db.query.Users.findFirst>[0],
): Promise<UserWithRelations | null> => { ): Promise<UserWithRelations | null> => {
const output = await db.query.user.findFirst({ const output = await db.query.Users.findFirst({
...query, ...query,
with: { with: {
...userRelations, ...userRelations,
@ -418,7 +418,7 @@ export const resolveUser = async (
const newUser = ( const newUser = (
await db await db
.insert(user) .insert(Users)
.values({ .values({
username: data.username, username: data.username,
uri: data.uri, uri: data.uri,
@ -456,7 +456,7 @@ export const resolveUser = async (
// Add emojis to user // Add emojis to user
if (emojis.length > 0) { if (emojis.length > 0) {
await db.insert(emojiToUser).values( await db.insert(EmojiToUser).values(
emojis.map((emoji) => ({ emojis.map((emoji) => ({
emojiId: emoji.id, emojiId: emoji.id,
userId: newUser.id, userId: newUser.id,
@ -494,15 +494,15 @@ export const resolveWebFinger = async (
// Check if user not already in database // Check if user not already in database
const foundUser = await db const foundUser = await db
.select() .select()
.from(user) .from(Users)
.innerJoin(instance, eq(user.instanceId, instance.id)) .innerJoin(Instances, eq(Users.instanceId, Instances.id))
.where(and(eq(user.username, identifier), eq(instance.baseUrl, host))) .where(and(eq(Users.username, identifier), eq(Instances.baseUrl, host)))
.limit(1); .limit(1);
if (foundUser[0]) if (foundUser[0])
return ( return (
(await findFirstUser({ (await findFirstUser({
where: (user, { eq }) => eq(user.id, foundUser[0].User.id), where: (user, { eq }) => eq(user.id, foundUser[0].Users.id),
})) || null })) || null
); );
@ -580,7 +580,7 @@ export const createNewLocalUser = async (data: {
const newUser = ( const newUser = (
await db await db
.insert(user) .insert(Users)
.values({ .values({
username: data.username, username: data.username,
displayName: data.display_name ?? data.username, displayName: data.display_name ?? data.username,
@ -662,12 +662,12 @@ export const retrieveUserAndApplicationFromToken = async (
const output = ( const output = (
await db await db
.select({ .select({
token: token, token: Tokens,
application: application, application: Applications,
}) })
.from(token) .from(Tokens)
.leftJoin(application, eq(token.applicationId, application.id)) .leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
.where(eq(token.accessToken, access_token)) .where(eq(Tokens.accessToken, access_token))
.limit(1) .limit(1)
)[0]; )[0];
@ -686,7 +686,7 @@ export const retrieveToken = async (
if (!access_token) return null; if (!access_token) return null;
return ( return (
(await db.query.token.findFirst({ (await db.query.Tokens.findFirst({
where: (tokens, { eq }) => eq(tokens.accessToken, access_token), where: (tokens, { eq }) => eq(tokens.accessToken, access_token),
})) ?? null })) ?? null
); );
@ -700,8 +700,8 @@ export const retrieveToken = async (
export const getRelationshipToOtherUser = async ( export const getRelationshipToOtherUser = async (
user: UserWithRelations, user: UserWithRelations,
other: User, other: User,
): Promise<InferSelectModel<typeof relationship>> => { ): Promise<InferSelectModel<typeof Relationships>> => {
const foundRelationship = await db.query.relationship.findFirst({ const foundRelationship = await db.query.Relationships.findFirst({
where: (relationship, { and, eq }) => where: (relationship, { and, eq }) =>
and( and(
eq(relationship.ownerId, user.id), 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", "version": "5",
"dialect": "pg", "dialect": "pg",
"entries": [ "entries": [
{ {
"idx": 0, "idx": 0,
"version": "5", "version": "5",
"when": 1712805159664, "when": 1712805159664,
"tag": "0000_illegal_living_lightning", "tag": "0000_illegal_living_lightning",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "5", "version": "5",
"when": 1713055774123, "when": 1713055774123,
"tag": "0001_salty_night_thrasher", "tag": "0001_salty_night_thrasher",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 2, "idx": 2,
"version": "5", "version": "5",
"when": 1713056370431, "when": 1713056370431,
"tag": "0002_stiff_ares", "tag": "0002_stiff_ares",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 3, "idx": 3,
"version": "5", "version": "5",
"when": 1713056528340, "when": 1713056528340,
"tag": "0003_spicy_arachne", "tag": "0003_spicy_arachne",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 4, "idx": 4,
"version": "5", "version": "5",
"when": 1713056712218, "when": 1713056712218,
"tag": "0004_burly_lockjaw", "tag": "0004_burly_lockjaw",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 5, "idx": 5,
"version": "5", "version": "5",
"when": 1713056917973, "when": 1713056917973,
"tag": "0005_sleepy_puma", "tag": "0005_sleepy_puma",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 6, "idx": 6,
"version": "5", "version": "5",
"when": 1713057159867, "when": 1713057159867,
"tag": "0006_messy_network", "tag": "0006_messy_network",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 7, "idx": 7,
"version": "5", "version": "5",
"when": 1713227918208, "when": 1713227918208,
"tag": "0007_naive_sleeper", "tag": "0007_naive_sleeper",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 8, "idx": 8,
"version": "5", "version": "5",
"when": 1713246700119, "when": 1713246700119,
"tag": "0008_flawless_brother_voodoo", "tag": "0008_flawless_brother_voodoo",
"breakpoints": true "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, uniqueIndex,
uuid, uuid,
} from "drizzle-orm/pg-core"; } 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(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
shortcode: text("shortcode").notNull(), shortcode: text("shortcode").notNull(),
url: text("url").notNull(), url: text("url").notNull(),
visibleInPicker: boolean("visible_in_picker").notNull(), visibleInPicker: boolean("visible_in_picker").notNull(),
alt: text("alt"), alt: text("alt"),
contentType: text("content_type").notNull(), contentType: text("content_type").notNull(),
instanceId: uuid("instanceId").references(() => instance.id, { instanceId: uuid("instanceId").references(() => Instances.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
}); });
export const like = pgTable("Like", { export const Likes = pgTable("Likes", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
likerId: uuid("likerId") likerId: uuid("likerId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
likedId: uuid("likedId") likedId: uuid("likedId")
.notNull() .notNull()
.references(() => status.id, { .references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -44,7 +45,7 @@ export const like = pgTable("Like", {
.notNull(), .notNull(),
}); });
export const lysandObject = pgTable( export const LysandObjects = pgTable(
"LysandObject", "LysandObject",
{ {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), 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(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
ownerId: uuid("ownerId") ownerId: uuid("ownerId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
subjectId: uuid("subjectId") subjectId: uuid("subjectId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -110,8 +111,8 @@ export const relationship = pgTable("Relationship", {
.notNull(), .notNull(),
}); });
export const application = pgTable( export const Applications = pgTable(
"Application", "Applications",
{ {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
name: text("name").notNull(), name: text("name").notNull(),
@ -129,12 +130,12 @@ export const application = pgTable(
}, },
); );
export const applicationRelations = relations(application, ({ many }) => ({ export const ApplicationsRelations = relations(Applications, ({ many }) => ({
tokens: many(token), tokens: many(Tokens),
loginFlows: many(openIdLoginFlow), loginFlows: many(OpenIdLoginFlows),
})); }));
export const token = pgTable("Token", { export const Tokens = pgTable("Tokens", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
tokenType: text("token_type").notNull(), tokenType: text("token_type").notNull(),
scope: text("scope").notNull(), scope: text("scope").notNull(),
@ -143,17 +144,17 @@ export const token = pgTable("Token", {
createdAt: timestamp("created_at", { precision: 3, mode: "string" }) createdAt: timestamp("created_at", { precision: 3, mode: "string" })
.defaultNow() .defaultNow()
.notNull(), .notNull(),
userId: uuid("userId").references(() => user.id, { userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
applicationId: uuid("applicationId").references(() => application.id, { applicationId: uuid("applicationId").references(() => Applications.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
}); });
export const attachment = pgTable("Attachment", { export const Attachments = pgTable("Attachments", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
url: text("url").notNull(), url: text("url").notNull(),
remoteUrl: text("remote_url"), remoteUrl: text("remote_url"),
@ -167,13 +168,13 @@ export const attachment = pgTable("Attachment", {
width: integer("width"), width: integer("width"),
height: integer("height"), height: integer("height"),
size: integer("size"), size: integer("size"),
statusId: uuid("statusId").references(() => status.id, { noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
}); });
export const notification = pgTable("Notification", { export const Notifications = pgTable("Notifications", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
type: text("type").notNull(), type: text("type").notNull(),
createdAt: timestamp("createdAt", { precision: 3, mode: "string" }) createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
@ -181,31 +182,31 @@ export const notification = pgTable("Notification", {
.notNull(), .notNull(),
notifiedId: uuid("notifiedId") notifiedId: uuid("notifiedId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
accountId: uuid("accountId") accountId: uuid("accountId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
statusId: uuid("statusId").references(() => status.id, { noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
dismissed: boolean("dismissed").default(false).notNull(), dismissed: boolean("dismissed").default(false).notNull(),
}); });
export const status = pgTable( export const Notes = pgTable(
"Status", "Notes",
{ {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
uri: text("uri"), uri: text("uri"),
authorId: uuid("authorId") authorId: uuid("authorId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -222,11 +223,11 @@ export const status = pgTable(
content: text("content").default("").notNull(), content: text("content").default("").notNull(),
contentType: text("content_type").default("text/plain").notNull(), contentType: text("content_type").default("text/plain").notNull(),
visibility: text("visibility").notNull(), visibility: text("visibility").notNull(),
inReplyToPostId: uuid("inReplyToPostId"), replyId: uuid("replyId"),
quotingPostId: uuid("quotingPostId"), quotingId: uuid("quoteId"),
sensitive: boolean("sensitive").notNull(), sensitive: boolean("sensitive").notNull(),
spoilerText: text("spoiler_text").default("").notNull(), spoilerText: text("spoiler_text").default("").notNull(),
applicationId: uuid("applicationId").references(() => application.id, { applicationId: uuid("applicationId").references(() => Applications.id, {
onDelete: "set null", onDelete: "set null",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -235,20 +236,20 @@ export const status = pgTable(
(table) => { (table) => {
return { return {
uriKey: uniqueIndex().on(table.uri), uriKey: uniqueIndex().on(table.uri),
statusReblogIdFkey: foreignKey({ noteReblogIdFkey: foreignKey({
columns: [table.reblogId], columns: [table.reblogId],
foreignColumns: [table.id], foreignColumns: [table.id],
}) })
.onUpdate("cascade") .onUpdate("cascade")
.onDelete("cascade"), .onDelete("cascade"),
statusInReplyToPostIdFkey: foreignKey({ noteReplyIdFkey: foreignKey({
columns: [table.inReplyToPostId], columns: [table.replyId],
foreignColumns: [table.id], foreignColumns: [table.id],
}) })
.onUpdate("cascade") .onUpdate("cascade")
.onDelete("set null"), .onDelete("set null"),
statusQuotingPostIdFkey: foreignKey({ noteQuotingIdFkey: foreignKey({
columns: [table.quotingPostId], columns: [table.quotingId],
foreignColumns: [table.id], foreignColumns: [table.id],
}) })
.onUpdate("cascade") .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(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
baseUrl: text("base_url").notNull(), baseUrl: text("base_url").notNull(),
name: text("name").notNull(), name: text("name").notNull(),
@ -268,9 +269,9 @@ export const instance = pgTable("Instance", {
.notNull(), .notNull(),
}); });
export const openIdAccount = pgTable("OpenIdAccount", { export const OpenIdAccounts = pgTable("OpenIdAccounts", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), 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", onDelete: "set null",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -278,8 +279,8 @@ export const openIdAccount = pgTable("OpenIdAccount", {
issuerId: text("issuer_id").notNull(), issuerId: text("issuer_id").notNull(),
}); });
export const user = pgTable( export const Users = pgTable(
"User", "Users",
{ {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
uri: text("uri"), uri: text("uri"),
@ -298,7 +299,7 @@ export const user = pgTable(
inbox: string; inbox: string;
outbox: string; outbox: string;
}> | null>(), }> | null>(),
source: jsonb("source").notNull(), source: jsonb("source").notNull().$type<APISource>(),
avatar: text("avatar").notNull(), avatar: text("avatar").notNull(),
header: text("header").notNull(), header: text("header").notNull(),
createdAt: timestamp("created_at", { precision: 3, mode: "string" }) createdAt: timestamp("created_at", { precision: 3, mode: "string" })
@ -316,7 +317,7 @@ export const user = pgTable(
sanctions: text("sanctions").array(), sanctions: text("sanctions").array(),
publicKey: text("public_key").notNull(), publicKey: text("public_key").notNull(),
privateKey: text("private_key"), privateKey: text("private_key"),
instanceId: uuid("instanceId").references(() => instance.id, { instanceId: uuid("instanceId").references(() => Instances.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "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(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
codeVerifier: text("code_verifier").notNull(), codeVerifier: text("code_verifier").notNull(),
applicationId: uuid("applicationId").references(() => application.id, { applicationId: uuid("applicationId").references(() => Applications.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
issuerId: text("issuer_id").notNull(), issuerId: text("issuer_id").notNull(),
}); });
export const openIdLoginFlowRelations = relations( export const OpenIdLoginFlowsRelations = relations(
openIdLoginFlow, OpenIdLoginFlows,
({ one }) => ({ ({ one }) => ({
application: one(application, { application: one(Applications, {
fields: [openIdLoginFlow.applicationId], fields: [OpenIdLoginFlows.applicationId],
references: [application.id], references: [Applications.id],
}), }),
}), }),
); );
export const flag = pgTable("Flag", { export const Flags = pgTable("Flags", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
flagType: text("flag_type").default("other").notNull(), flagType: text("flag_type").default("other").notNull(),
createdAt: timestamp("created_at", { precision: 3, mode: "string" }) createdAt: timestamp("created_at", { precision: 3, mode: "string" })
.defaultNow() .defaultNow()
.notNull(), .notNull(),
flaggeStatusId: uuid("flaggeStatusId").references(() => status.id, { noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
flaggedUserId: uuid("flaggedUserId").references(() => user.id, { userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
}); });
export const modNote = pgTable("ModNote", { export const ModNotes = pgTable("ModNotes", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
notedStatusId: uuid("notedStatusId").references(() => status.id, { nodeId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
notedUserId: uuid("notedUserId").references(() => user.id, { userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
modId: uuid("modId") modId: uuid("modId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -391,19 +392,19 @@ export const modNote = pgTable("ModNote", {
.notNull(), .notNull(),
}); });
export const modTag = pgTable("ModTag", { export const ModTags = pgTable("ModTags", {
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
taggedStatusId: uuid("taggedStatusId").references(() => status.id, { noteId: uuid("noteId").references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
taggedUserId: uuid("taggedUserId").references(() => user.id, { userId: uuid("userId").references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
modId: uuid("modId") modId: uuid("modId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -413,18 +414,18 @@ export const modTag = pgTable("ModTag", {
.notNull(), .notNull(),
}); });
export const emojiToUser = pgTable( export const EmojiToUser = pgTable(
"EmojiToUser", "EmojiToUser",
{ {
emojiId: uuid("emojiId") emojiId: uuid("emojiId")
.notNull() .notNull()
.references(() => emoji.id, { .references(() => Emojis.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
userId: uuid("userId") userId: uuid("userId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
@ -437,267 +438,267 @@ export const emojiToUser = pgTable(
}, },
); );
export const emojiToUserRelations = relations(emojiToUser, ({ one }) => ({ export const EmojiToUserRelations = relations(EmojiToUser, ({ one }) => ({
emoji: one(emoji, { emoji: one(Emojis, {
fields: [emojiToUser.emojiId], fields: [EmojiToUser.emojiId],
references: [emoji.id], references: [Emojis.id],
}), }),
user: one(user, { user: one(Users, {
fields: [emojiToUser.userId], fields: [EmojiToUser.userId],
references: [user.id], references: [Users.id],
}), }),
})); }));
export const emojiToStatus = pgTable( export const EmojiToNote = pgTable(
"EmojiToStatus", "EmojiToNote",
{ {
emojiId: uuid("emojiId") emojiId: uuid("emojiId")
.notNull() .notNull()
.references(() => emoji.id, { .references(() => Emojis.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
statusId: uuid("statusId") noteId: uuid("noteId")
.notNull() .notNull()
.references(() => status.id, { .references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
}, },
(table) => { (table) => {
return { return {
abUnique: uniqueIndex().on(table.emojiId, table.statusId), abUnique: uniqueIndex().on(table.emojiId, table.noteId),
bIdx: index().on(table.statusId), bIdx: index().on(table.noteId),
}; };
}, },
); );
export const statusToMentions = pgTable( export const NoteToMentions = pgTable(
"StatusToMentions", "NoteToMentions",
{ {
statusId: uuid("statusId") noteId: uuid("noteId")
.notNull() .notNull()
.references(() => status.id, { .references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
userId: uuid("userId") userId: uuid("userId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
}, },
(table) => { (table) => {
return { return {
abUnique: uniqueIndex().on(table.statusId, table.userId), abUnique: uniqueIndex().on(table.noteId, table.userId),
bIdx: index().on(table.userId), bIdx: index().on(table.userId),
}; };
}, },
); );
export const userPinnedNotes = pgTable( export const UserToPinnedNotes = pgTable(
"UserToPinnedNotes", "UserToPinnedNotes",
{ {
userId: uuid("userId") userId: uuid("userId")
.notNull() .notNull()
.references(() => status.id, { .references(() => Users.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
statusId: uuid("statusId") noteId: uuid("noteId")
.notNull() .notNull()
.references(() => user.id, { .references(() => Notes.id, {
onDelete: "cascade", onDelete: "cascade",
onUpdate: "cascade", onUpdate: "cascade",
}), }),
}, },
(table) => { (table) => {
return { return {
abUnique: uniqueIndex().on(table.userId, table.statusId), abUnique: uniqueIndex().on(table.userId, table.noteId),
bIdx: index().on(table.statusId), bIdx: index().on(table.noteId),
}; };
}, },
); );
export const attachmentRelations = relations(attachment, ({ one }) => ({ export const AttachmentsRelations = relations(Attachments, ({ one }) => ({
status: one(status, { notes: one(Notes, {
fields: [attachment.statusId], fields: [Attachments.noteId],
references: [status.id], references: [Notes.id],
}), }),
})); }));
export const userRelations = relations(user, ({ many, one }) => ({ export const UsersRelations = relations(Users, ({ many, one }) => ({
emojis: many(emojiToUser), emojis: many(EmojiToUser),
pinnedNotes: many(userPinnedNotes), pinnedNotes: many(UserToPinnedNotes),
statuses: many(status, { notes: many(Notes, {
relationName: "StatusToAuthor", relationName: "NoteToAuthor",
}), }),
likes: many(like), likes: many(Likes),
relationships: many(relationship, { relationships: many(Relationships, {
relationName: "RelationshipToOwner", relationName: "RelationshipToOwner",
}), }),
relationshipSubjects: many(relationship, { relationshipSubjects: many(Relationships, {
relationName: "RelationshipToSubject", relationName: "RelationshipToSubject",
}), }),
notificationsMade: many(notification, { notificationsMade: many(Notifications, {
relationName: "NotificationToAccount", relationName: "NotificationToAccount",
}), }),
notificationsReceived: many(notification, { notificationsReceived: many(Notifications, {
relationName: "NotificationToNotified", relationName: "NotificationToNotified",
}), }),
openIdAccounts: many(openIdAccount), openIdAccounts: many(OpenIdAccounts),
flags: many(flag), flags: many(Flags),
modNotes: many(modNote), modNotes: many(ModNotes),
modTags: many(modTag), modTags: many(ModTags),
tokens: many(token), tokens: many(Tokens),
instance: one(instance, { instance: one(Instances, {
fields: [user.instanceId], fields: [Users.instanceId],
references: [instance.id], references: [Instances.id],
}), }),
mentionedIn: many(statusToMentions), mentionedIn: many(NoteToMentions),
})); }));
export const relationshipRelations = relations(relationship, ({ one }) => ({ export const RelationshipsRelations = relations(Relationships, ({ one }) => ({
owner: one(user, { owner: one(Users, {
fields: [relationship.ownerId], fields: [Relationships.ownerId],
references: [user.id], references: [Users.id],
relationName: "RelationshipToOwner", relationName: "RelationshipToOwner",
}), }),
subject: one(user, { subject: one(Users, {
fields: [relationship.subjectId], fields: [Relationships.subjectId],
references: [user.id], references: [Users.id],
relationName: "RelationshipToSubject", relationName: "RelationshipToSubject",
}), }),
})); }));
export const tokenRelations = relations(token, ({ one }) => ({ export const TokensRelations = relations(Tokens, ({ one }) => ({
user: one(user, { user: one(Users, {
fields: [token.userId], fields: [Tokens.userId],
references: [user.id], references: [Users.id],
}), }),
application: one(application, { application: one(Applications, {
fields: [token.applicationId], fields: [Tokens.applicationId],
references: [application.id], references: [Applications.id],
}), }),
})); }));
export const statusToUserRelations = relations(statusToMentions, ({ one }) => ({ export const NotesToUsersRelations = relations(NoteToMentions, ({ one }) => ({
status: one(status, { note: one(Notes, {
fields: [statusToMentions.statusId], fields: [NoteToMentions.noteId],
references: [status.id], references: [Notes.id],
}), }),
user: one(user, { user: one(Users, {
fields: [statusToMentions.userId], fields: [NoteToMentions.userId],
references: [user.id], references: [Users.id],
}), }),
})); }));
export const userPinnedNotesRelations = relations( export const UserToPinnedNotesRelations = relations(
userPinnedNotes, UserToPinnedNotes,
({ one }) => ({ ({ one }) => ({
status: one(status, { note: one(Notes, {
fields: [userPinnedNotes.statusId], fields: [UserToPinnedNotes.noteId],
references: [status.id], references: [Notes.id],
}), }),
user: one(user, { user: one(Users, {
fields: [userPinnedNotes.userId], fields: [UserToPinnedNotes.userId],
references: [user.id], references: [Users.id],
}), }),
}), }),
); );
export const statusRelations = relations(status, ({ many, one }) => ({ export const NotesRelations = relations(Notes, ({ many, one }) => ({
emojis: many(emojiToStatus), emojis: many(EmojiToNote),
author: one(user, { author: one(Users, {
fields: [status.authorId], fields: [Notes.authorId],
references: [user.id], references: [Users.id],
relationName: "StatusToAuthor", relationName: "NoteToAuthor",
}), }),
attachments: many(attachment), attachments: many(Attachments),
mentions: many(statusToMentions), mentions: many(NoteToMentions),
reblog: one(status, { reblog: one(Notes, {
fields: [status.reblogId], fields: [Notes.reblogId],
references: [status.id], references: [Notes.id],
relationName: "StatusToReblog", relationName: "NoteToReblogs",
}), }),
usersThatHavePinned: many(userPinnedNotes), usersThatHavePinned: many(UserToPinnedNotes),
inReplyTo: one(status, { reply: one(Notes, {
fields: [status.inReplyToPostId], fields: [Notes.replyId],
references: [status.id], references: [Notes.id],
relationName: "StatusToReplying", relationName: "NoteToReplies",
}), }),
quoting: one(status, { quote: one(Notes, {
fields: [status.quotingPostId], fields: [Notes.quotingId],
references: [status.id], references: [Notes.id],
relationName: "StatusToQuoting", relationName: "NoteToQuotes",
}), }),
application: one(application, { application: one(Applications, {
fields: [status.applicationId], fields: [Notes.applicationId],
references: [application.id], references: [Applications.id],
}), }),
quotes: many(status, { quotes: many(Notes, {
relationName: "StatusToQuoting", relationName: "NoteToQuotes",
}), }),
replies: many(status, { replies: many(Notes, {
relationName: "StatusToReplying", relationName: "NoteToReplies",
}), }),
likes: many(like), likes: many(Likes),
reblogs: many(status, { reblogs: many(Notes, {
relationName: "StatusToReblog", relationName: "NoteToReblogs",
}), }),
notifications: many(notification), notifications: many(Notifications),
})); }));
export const notificationRelations = relations(notification, ({ one }) => ({ export const NotificationsRelations = relations(Notifications, ({ one }) => ({
account: one(user, { account: one(Users, {
fields: [notification.accountId], fields: [Notifications.accountId],
references: [user.id], references: [Users.id],
relationName: "NotificationToAccount", relationName: "NotificationToAccount",
}), }),
notified: one(user, { notified: one(Users, {
fields: [notification.notifiedId], fields: [Notifications.notifiedId],
references: [user.id], references: [Users.id],
relationName: "NotificationToNotified", relationName: "NotificationToNotified",
}), }),
status: one(status, { note: one(Notes, {
fields: [notification.statusId], fields: [Notifications.noteId],
references: [status.id], references: [Notes.id],
}), }),
})); }));
export const likeRelations = relations(like, ({ one }) => ({ export const LikesRelations = relations(Likes, ({ one }) => ({
liker: one(user, { liker: one(Users, {
fields: [like.likerId], fields: [Likes.likerId],
references: [user.id], references: [Users.id],
}), }),
liked: one(status, { liked: one(Notes, {
fields: [like.likedId], fields: [Likes.likedId],
references: [status.id], references: [Notes.id],
}), }),
})); }));
export const emojiRelations = relations(emoji, ({ one, many }) => ({ export const EmojisRelations = relations(Emojis, ({ one, many }) => ({
instance: one(instance, { instance: one(Instances, {
fields: [emoji.instanceId], fields: [Emojis.instanceId],
references: [instance.id], references: [Instances.id],
}), }),
users: many(emojiToUser), users: many(EmojiToUser),
statuses: many(emojiToStatus), notes: many(EmojiToNote),
})); }));
export const instanceRelations = relations(instance, ({ many }) => ({ export const InstancesRelations = relations(Instances, ({ many }) => ({
users: many(user), users: many(Users),
emojis: many(emoji), emojis: many(Emojis),
})); }));
export const emojiToStatusRelations = relations(emojiToStatus, ({ one }) => ({ export const EmojisToNotesRelations = relations(EmojiToNote, ({ one }) => ({
emoji: one(emoji, { emoji: one(Emojis, {
fields: [emojiToStatus.emojiId], fields: [EmojiToNote.emojiId],
references: [emoji.id], references: [Emojis.id],
}), }),
status: one(status, { note: one(Notes, {
fields: [emojiToStatus.statusId], fields: [EmojiToNote.noteId],
references: [status.id], references: [Notes.id],
}), }),
})); }));

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,4 @@
import { randomBytes } from "node:crypto";
import { apiRoute, applyConfig } from "@api"; 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"; import { config } from "~packages/config-manager";
export const meta = applyConfig({ export const meta = applyConfig({

View file

@ -1,7 +1,7 @@
import { apiRoute, applyConfig } from "@api"; import { apiRoute, applyConfig } from "@api";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { application, token } from "~drizzle/schema"; import { Applications, Tokens } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -18,10 +18,7 @@ export const meta = applyConfig({
/** /**
* OAuth Code flow * OAuth Code flow
*/ */
export default apiRoute<{ export default apiRoute(async (req, matchedRoute) => {
email: string;
password: string;
}>(async (req, matchedRoute) => {
const redirect_uri = decodeURIComponent(matchedRoute.query.redirect_uri); const redirect_uri = decodeURIComponent(matchedRoute.query.redirect_uri);
const client_id = matchedRoute.query.client_id; const client_id = matchedRoute.query.client_id;
const code = matchedRoute.query.code; const code = matchedRoute.query.code;
@ -37,9 +34,9 @@ export default apiRoute<{
const foundToken = await db const foundToken = await db
.select() .select()
.from(token) .from(Tokens)
.leftJoin(application, eq(token.applicationId, application.id)) .leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
.where(and(eq(token.code, code), eq(application.clientId, client_id))) .where(and(eq(Tokens.code, code), eq(Applications.clientId, client_id)))
.limit(1); .limit(1);
if (!foundToken || foundToken.length <= 0) 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, getRelationshipToOtherUser,
} from "~database/entities/User"; } from "~database/entities/User";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema"; import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -48,11 +48,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
} }
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
blocking: true, blocking: true,
}) })
.where(eq(relationship.id, foundRelationship.id)); .where(eq(Relationships.id, foundRelationship.id));
return jsonResponse(relationshipToAPI(foundRelationship)); 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, max_id ? lt(follower.id, max_id) : undefined,
since_id ? gte(follower.id, since_id) : undefined, since_id ? gte(follower.id, since_id) : undefined,
min_id ? gt(follower.id, min_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 // @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id), 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, max_id ? lt(following.id, max_id) : undefined,
since_id ? gte(following.id, since_id) : undefined, since_id ? gte(following.id, since_id) : undefined,
min_id ? gt(following.id, min_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 // @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id), 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, getRelationshipToOtherUser,
} from "~database/entities/User"; } from "~database/entities/User";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema"; import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -66,12 +66,12 @@ export default apiRoute<typeof meta, typeof schema>(
} }
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
muting: true, muting: true,
mutingNotifications: notifications ?? true, mutingNotifications: notifications ?? true,
}) })
.where(eq(relationship.id, foundRelationship.id)); .where(eq(Relationships.id, foundRelationship.id));
// TODO: Implement duration // TODO: Implement duration

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import {
getRelationshipToOtherUser, getRelationshipToOtherUser,
} from "~database/entities/User"; } from "~database/entities/User";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema"; import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -48,23 +48,23 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
foundRelationship.followedBy = false; foundRelationship.followedBy = false;
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
followedBy: false, followedBy: false,
}) })
.where(eq(relationship.id, foundRelationship.id)); .where(eq(Relationships.id, foundRelationship.id));
if (otherUser.instanceId === null) { if (otherUser.instanceId === null) {
// Also remove from followers list // Also remove from followers list
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
following: false, following: false,
}) })
.where( .where(
and( and(
eq(relationship.ownerId, otherUser.id), eq(Relationships.ownerId, otherUser.id),
eq(relationship.subjectId, self.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 { and, eq, gt, gte, isNull, lt, sql } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { findFirstUser } from "~database/entities/User"; import { findFirstUser } from "~database/entities/User";
import { status } from "~drizzle/schema"; import { Notes } from "~drizzle/schema";
import { Timeline } from "~packages/database-interface/timeline"; import { Timeline } from "~packages/database-interface/timeline";
export const meta = applyConfig({ 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); return errorResponse("Invalid ID, must be of type UUIDv7", 404);
} }
// TODO: Add pinned
const { const {
max_id, max_id,
min_id, min_id,
@ -49,6 +48,7 @@ export default apiRoute<typeof meta, typeof schema>(
limit, limit,
exclude_reblogs, exclude_reblogs,
only_media, only_media,
exclude_replies,
pinned, pinned,
} = extraData.parsedRequest; } = extraData.parsedRequest;
@ -58,41 +58,20 @@ export default apiRoute<typeof meta, typeof schema>(
if (!user) return errorResponse("User not found", 404); 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( const { objects, link } = await Timeline.getNoteTimeline(
and( and(
max_id ? lt(status.id, max_id) : undefined, max_id ? lt(Notes.id, max_id) : undefined,
since_id ? gte(status.id, since_id) : undefined, since_id ? gte(Notes.id, since_id) : undefined,
min_id ? gt(status.id, min_id) : undefined, min_id ? gt(Notes.id, min_id) : undefined,
eq(status.authorId, id), eq(Notes.authorId, id),
only_media 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, : 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, limit,
req.url, req.url,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ import {
resolveWebFinger, resolveWebFinger,
userToAPI, userToAPI,
} from "~database/entities/User"; } from "~database/entities/User";
import { user } from "~drizzle/schema"; import { Users } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["GET"], allowedMethods: ["GET"],
@ -94,7 +94,7 @@ export default apiRoute<typeof meta, typeof schema>(
like(account.displayName, `%${q}%`), like(account.displayName, `%${q}%`),
like(account.username, `%${q}%`), like(account.username, `%${q}%`),
following 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, : undefined,
), ),
offset, offset,

View file

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

View file

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

View file

@ -46,7 +46,7 @@ export default apiRoute<typeof meta, typeof schema>(
max_id ? lt(subject.id, max_id) : undefined, max_id ? lt(subject.id, max_id) : undefined,
since_id ? gte(subject.id, since_id) : undefined, since_id ? gte(subject.id, since_id) : undefined,
min_id ? gt(subject.id, min_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, limit,
// @ts-expect-error Yes I KNOW the types are wrong // @ts-expect-error Yes I KNOW the types are wrong

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ import {
sendFollowReject, sendFollowReject,
} from "~database/entities/User"; } from "~database/entities/User";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { relationship } from "~drizzle/schema"; import { Relationships } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -43,28 +43,28 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Reject follow request // Reject follow request
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
requested: false, requested: false,
following: false, following: false,
}) })
.where( .where(
and( and(
eq(relationship.subjectId, user.id), eq(Relationships.subjectId, user.id),
eq(relationship.ownerId, account.id), eq(Relationships.ownerId, account.id),
), ),
); );
// Update followedBy for other user // Update followedBy for other user
await db await db
.update(relationship) .update(Relationships)
.set({ .set({
followedBy: false, followedBy: false,
}) })
.where( .where(
and( and(
eq(relationship.subjectId, account.id), eq(Relationships.subjectId, account.id),
eq(relationship.ownerId, user.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, max_id ? lt(subject.id, max_id) : undefined,
since_id ? gte(subject.id, since_id) : undefined, since_id ? gte(subject.id, since_id) : undefined,
min_id ? gt(subject.id, min_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, limit,
// @ts-expect-error Yes I KNOW the types are wrong // @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 { and, count, countDistinct, eq, gte, isNull, sql } from "drizzle-orm";
import { findFirstUser, userToAPI } from "~database/entities/User"; import { findFirstUser, userToAPI } from "~database/entities/User";
import { db } from "~drizzle/db"; 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 manifest from "~package.json";
import type { Instance as APIInstance } from "~types/mastodon/instance"; import type { Instance as APIInstance } from "~types/mastodon/instance";
@ -30,9 +30,9 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({ .select({
count: count(), count: count(),
}) })
.from(status) .from(Notes)
.where( .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; )[0].count;
@ -41,8 +41,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({ .select({
count: count(), count: count(),
}) })
.from(user) .from(Users)
.where(isNull(user.instanceId)) .where(isNull(Users.instanceId))
)[0].count; )[0].count;
const contactAccount = await findFirstUser({ const contactAccount = await findFirstUser({
@ -54,15 +54,15 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const monthlyActiveUsers = ( const monthlyActiveUsers = (
await db await db
.select({ .select({
count: countDistinct(user), count: countDistinct(Users),
}) })
.from(user) .from(Users)
.leftJoin(status, eq(user.id, status.authorId)) .leftJoin(Notes, eq(Users.id, Notes.authorId))
.where( .where(
and( and(
isNull(user.instanceId), isNull(Users.instanceId),
gte( gte(
status.createdAt, Notes.createdAt,
new Date( new Date(
Date.now() - 30 * 24 * 60 * 60 * 1000, Date.now() - 30 * 24 * 60 * 60 * 1000,
).toISOString(), ).toISOString(),
@ -76,7 +76,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({ .select({
count: count(), count: count(),
}) })
.from(instance) .from(Instances)
)[0].count; )[0].count;
// TODO: fill in more values // TODO: fill in more values

View file

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

View file

@ -9,7 +9,7 @@ import sharp from "sharp";
import { z } from "zod"; import { z } from "zod";
import { attachmentToAPI, getUrl } from "~database/entities/Attachment"; import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { attachment } from "~drizzle/schema"; import { Attachments } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -128,7 +128,7 @@ export default apiRoute<typeof meta, typeof schema>(
const newAttachment = ( const newAttachment = (
await db await db
.insert(attachment) .insert(Attachments)
.values({ .values({
url, url,
thumbnailUrl, 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, max_id ? lt(subject.id, max_id) : undefined,
since_id ? gte(subject.id, since_id) : undefined, since_id ? gte(subject.id, since_id) : undefined,
min_id ? gt(subject.id, min_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, limit,
// @ts-expect-error Yes I KNOW the types are wrong // @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 { errorResponse, jsonResponse } from "@response";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { notification } from "~drizzle/schema"; import { Notifications } from "~drizzle/schema";
export const meta = applyConfig({ export const meta = applyConfig({
allowedMethods: ["POST"], allowedMethods: ["POST"],
@ -27,11 +27,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!user) return errorResponse("Unauthorized", 401); if (!user) return errorResponse("Unauthorized", 401);
await db await db
.update(notification) .update(Notifications)
.set({ .set({
dismissed: true, dismissed: true,
}) })
.where(eq(notification.id, id)); .where(eq(Notifications.id, id));
return jsonResponse({}); return jsonResponse({});
}); });

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -36,7 +36,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
if (!status?.isViewableByUser(user)) if (!status?.isViewableByUser(user))
return errorResponse("Record not found", 404); return errorResponse("Record not found", 404);
const existingLike = await db.query.like.findFirst({ const existingLike = await db.query.Likes.findFirst({
where: (like, { and, eq }) => where: (like, { and, eq }) =>
and( and(
eq(like.likedId, status.getStatus().id), 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, max_id ? lt(liker.id, max_id) : undefined,
since_id ? gte(liker.id, since_id) : undefined, since_id ? gte(liker.id, since_id) : undefined,
min_id ? gt(liker.id, min_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 status.getStatus().id
} AND "Like"."likerId" = ${liker.id})`, } AND "Likes"."likerId" = ${liker.id})`,
), ),
// @ts-expect-error Yes I KNOW the types are wrong // @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id), 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 // Check if media attachments are all valid
if (media_ids && media_ids.length > 0) { if (media_ids && media_ids.length > 0) {
const foundAttachments = await db.query.attachment.findMany({ const foundAttachments = await db.query.Attachments.findMany({
where: (attachment, { inArray }) => where: (attachment, { inArray }) =>
inArray(attachment.id, media_ids), inArray(attachment.id, media_ids),
}); });

View file

@ -1,7 +1,7 @@
import { apiRoute, applyConfig, idValidator } from "@api"; import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { statusToMentions } from "~drizzle/schema"; import { NoteToMentions, UserToPinnedNotes } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note"; import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -40,10 +40,10 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
// Check if post is already pinned // Check if post is already pinned
if ( if (
await db.query.userPinnedNotes.findFirst({ await db.query.UserToPinnedNotes.findFirst({
where: (userPinnedNote, { and, eq }) => where: (userPinnedNote, { and, eq }) =>
and( and(
eq(userPinnedNote.statusId, foundStatus.getStatus().id), eq(userPinnedNote.noteId, foundStatus.getStatus().id),
eq(userPinnedNote.userId, user.id), eq(userPinnedNote.userId, user.id),
), ),
}) })
@ -51,10 +51,7 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
return errorResponse("Already pinned", 422); return errorResponse("Already pinned", 422);
} }
await db.insert(statusToMentions).values({ await foundStatus.pin(user);
statusId: foundStatus.getStatus().id,
userId: user.id,
});
return jsonResponse(await foundStatus.toAPI(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 { and, eq } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { notification, status } from "~drizzle/schema"; import { Notes, Notifications } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note"; import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -44,13 +44,12 @@ export default apiRoute<typeof meta, typeof schema>(
if (!foundStatus?.isViewableByUser(user)) if (!foundStatus?.isViewableByUser(user))
return errorResponse("Record not found", 404); return errorResponse("Record not found", 404);
const existingReblog = await db.query.status.findFirst({ const existingReblog = await Note.fromSql(
where: (status, { and, eq }) => and(
and( eq(Notes.authorId, user.id),
eq(status.authorId, user.id), eq(Notes.reblogId, foundStatus.getStatus().id),
eq(status.reblogId, status.id), ),
), );
});
if (existingReblog) { if (existingReblog) {
return errorResponse("Already reblogged", 422); 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 // Create notification for reblog if reblogged user is on the same instance
if (foundStatus.getAuthor().instanceId === user.instanceId) { if (foundStatus.getAuthor().instanceId === user.instanceId) {
await db.insert(notification).values({ await db.insert(Notifications).values({
accountId: user.id, accountId: user.id,
notifiedId: foundStatus.getAuthor().id, notifiedId: foundStatus.getAuthor().id,
type: "reblog", 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, max_id ? lt(reblogger.id, max_id) : undefined,
since_id ? gte(reblogger.id, since_id) : undefined, since_id ? gte(reblogger.id, since_id) : undefined,
min_id ? gt(reblogger.id, min_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 status.getStatus().id
} AND "Status"."authorId" = ${reblogger.id})`, } AND "Notes"."authorId" = ${reblogger.id})`,
), ),
// @ts-expect-error Yes I KNOW the types are wrong // @ts-expect-error Yes I KNOW the types are wrong
orderBy: (liker, { desc }) => desc(liker.id), orderBy: (liker, { desc }) => desc(liker.id),

View file

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

View file

@ -70,7 +70,6 @@ export default apiRoute<typeof meta, typeof schema>(
const { const {
status, status,
media_ids, media_ids,
"poll[expires_in]": expires_in,
"poll[options]": options, "poll[options]": options,
in_reply_to_id, in_reply_to_id,
quote_id, quote_id,
@ -119,12 +118,6 @@ export default apiRoute<typeof meta, typeof schema>(
sanitizedStatus = await sanitizeHtml(status ?? ""); 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 // Check if status body doesnt match filters
if ( if (
config.filters.note_content.some((filter) => status?.match(filter)) 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 // Check if media attachments are all valid
if (media_ids && media_ids.length > 0) { if (media_ids && media_ids.length > 0) {
const foundAttachments = await db.query.attachment const foundAttachments = await db.query.Attachments.findMany({
.findMany({ where: (attachment, { inArray }) =>
where: (attachment, { inArray }) => inArray(attachment.id, media_ids),
inArray(attachment.id, media_ids), }).catch(() => []);
})
.catch(() => []);
if (foundAttachments.length !== (media_ids ?? []).length) { if (foundAttachments.length !== (media_ids ?? []).length) {
return errorResponse("Invalid media IDs", 422); 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 mentions = await parseTextMentions(sanitizedStatus);
const newNote = await Note.fromData( const newNote = await Note.fromData(

View file

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

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig, idValidator } from "@api";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { and, gt, gte, lt, sql } from "drizzle-orm"; import { and, gt, gte, lt, sql } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { status } from "~drizzle/schema"; import { Notes } from "~drizzle/schema";
import { Timeline } from "~packages/database-interface/timeline"; import { Timeline } from "~packages/database-interface/timeline";
export const meta = applyConfig({ export const meta = applyConfig({
@ -39,18 +39,18 @@ export default apiRoute<typeof meta, typeof schema>(
const { objects, link } = await Timeline.getNoteTimeline( const { objects, link } = await Timeline.getNoteTimeline(
and( and(
max_id ? lt(status.id, max_id) : undefined, max_id ? lt(Notes.id, max_id) : undefined,
since_id ? gte(status.id, since_id) : undefined, since_id ? gte(Notes.id, since_id) : undefined,
min_id ? gt(status.id, min_id) : undefined, min_id ? gt(Notes.id, min_id) : undefined,
// use authorId to grab user, then use user.instanceId to filter local/remote statuses // use authorId to grab user, then use user.instanceId to filter local/remote statuses
remote 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, : undefined,
local 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, : undefined,
only_media 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, : undefined,
), ),
limit, limit,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -45,7 +45,7 @@ export default apiRoute<typeof meta, typeof schema>(
); );
// Get associated token // Get associated token
const application = await db.query.application.findFirst({ const application = await db.query.Applications.findFirst({
where: (application, { eq, and }) => where: (application, { eq, and }) =>
and( and(
eq(application.clientId, client_id), eq(application.clientId, client_id),
@ -61,7 +61,7 @@ export default apiRoute<typeof meta, typeof schema>(
401, 401,
); );
const token = await db.query.token.findFirst({ const token = await db.query.Tokens.findFirst({
where: (token, { eq }) => where: (token, { eq }) =>
eq(token.code, code) && eq(token.applicationId, application.id), 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 * as Lysand from "lysand-types";
import { type Like, likeToLysand } from "~database/entities/Like"; import { type Like, likeToLysand } from "~database/entities/Like";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { status } from "~drizzle/schema"; import { Notes } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note"; import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -27,15 +27,15 @@ export default apiRoute(async (req, matchedRoute) => {
foundObject = await Note.fromSql( foundObject = await Note.fromSql(
and( and(
eq(status.id, uuid), eq(Notes.id, uuid),
inArray(status.visibility, ["public", "unlisted"]), inArray(Notes.visibility, ["public", "unlisted"]),
), ),
); );
apiObject = foundObject ? foundObject.toLysand() : null; apiObject = foundObject ? foundObject.toLysand() : null;
if (!foundObject) { if (!foundObject) {
foundObject = foundObject =
(await db.query.like.findFirst({ (await db.query.Likes.findFirst({
where: (like, { eq, and }) => where: (like, { eq, and }) =>
and( and(
eq(like.id, uuid), eq(like.id, uuid),

View file

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

View file

@ -2,7 +2,7 @@ import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response"; import { jsonResponse } from "@response";
import { and, count, eq, inArray } from "drizzle-orm"; import { and, count, eq, inArray } from "drizzle-orm";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { status } from "~drizzle/schema"; import { Notes } from "~drizzle/schema";
import { Note } from "~packages/database-interface/note"; import { Note } from "~packages/database-interface/note";
export const meta = applyConfig({ export const meta = applyConfig({
@ -25,8 +25,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const notes = await Note.manyFromSql( const notes = await Note.manyFromSql(
and( and(
eq(status.authorId, uuid), eq(Notes.authorId, uuid),
inArray(status.visibility, ["public", "unlisted"]), inArray(Notes.visibility, ["public", "unlisted"]),
), ),
undefined, undefined,
20, 20,
@ -37,11 +37,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
.select({ .select({
count: count(), count: count(),
}) })
.from(status) .from(Notes)
.where( .where(
and( and(
eq(status.authorId, uuid), eq(Notes.authorId, uuid),
inArray(status.visibility, ["public", "unlisted"]), 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 { config } from "config-manager";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "~drizzle/db"; 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 { Emoji as APIEmoji } from "~types/mastodon/emoji";
import type { Instance as APIInstance } from "~types/mastodon/instance"; import type { Instance as APIInstance } from "~types/mastodon/instance";
import { getTestUsers, sendTestRequest, wrapRelativeUrl } from "./utils"; import { getTestUsers, sendTestRequest, wrapRelativeUrl } from "./utils";
@ -18,7 +18,7 @@ describe("API Tests", () => {
describe("GET /api/v1/custom_emojis", () => { describe("GET /api/v1/custom_emojis", () => {
beforeAll(async () => { beforeAll(async () => {
await db.insert(emoji).values({ await db.insert(Emojis).values({
shortcode: "test", shortcode: "test",
url: "https://example.com/test.png", url: "https://example.com/test.png",
contentType: "image/png", contentType: "image/png",
@ -55,7 +55,7 @@ describe("API Tests", () => {
}); });
afterAll(async () => { 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", () => { 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 () => { test("should remove the specified user from the authenticated user's followers and return an APIRelationship object", async () => {
const response = await sendTestRequest( 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", () => { describe("POST /api/v1/accounts/:id/pin", () => {
test("should pin the specified user and return an APIRelationship object", async () => { test("should pin the specified user and return an APIRelationship object", async () => {
const response = await sendTestRequest( 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", () => { describe("POST /api/v1/statuses/:id/unfavourite", () => {
test("should unfavourite the specified status object", async () => { test("should unfavourite the specified status object", async () => {
// Unfavourite the status // Unfavourite the status

View file

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

View file

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

View file

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