mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Begin moving project to use Drizzle instead of prisma
This commit is contained in:
parent
b107bed935
commit
f7abe06a60
|
|
@ -1,10 +1,9 @@
|
||||||
import type { Application } from "@prisma/client";
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
import { client } from "~database/datasource";
|
import { db } from "~drizzle/db";
|
||||||
|
import type { application } from "~drizzle/schema";
|
||||||
import type { APIApplication } from "~types/entities/application";
|
import type { APIApplication } from "~types/entities/application";
|
||||||
|
|
||||||
/**
|
export type Application = InferSelectModel<typeof application>;
|
||||||
* Represents an application that can authenticate with the API.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the application associated with the given access token.
|
* Retrieves the application associated with the given access token.
|
||||||
|
|
@ -14,16 +13,14 @@ import type { APIApplication } from "~types/entities/application";
|
||||||
export const getFromToken = async (
|
export const getFromToken = async (
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<Application | null> => {
|
): Promise<Application | null> => {
|
||||||
const dbToken = await client.token.findFirst({
|
const result = await db.query.token.findFirst({
|
||||||
where: {
|
where: (tokens, { eq }) => eq(tokens.accessToken, token),
|
||||||
access_token: token,
|
with: {
|
||||||
},
|
|
||||||
include: {
|
|
||||||
application: true,
|
application: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return dbToken?.application || null;
|
return result?.application || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,6 +31,6 @@ export const applicationToAPI = (app: Application): APIApplication => {
|
||||||
return {
|
return {
|
||||||
name: app.name,
|
name: app.name,
|
||||||
website: app.website,
|
website: app.website,
|
||||||
vapid_key: app.vapid_key,
|
vapid_key: app.vapidKey,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
import type { Attachment } from "@prisma/client";
|
|
||||||
import type { Config } from "config-manager";
|
import type { Config } from "config-manager";
|
||||||
import { MediaBackendType } from "media-manager";
|
import { MediaBackendType } from "media-manager";
|
||||||
import type { APIAsyncAttachment } from "~types/entities/async_attachment";
|
import type { APIAsyncAttachment } from "~types/entities/async_attachment";
|
||||||
import type { APIAttachment } from "~types/entities/attachment";
|
import type { APIAttachment } from "~types/entities/attachment";
|
||||||
import type * as Lysand from "lysand-types";
|
import type * as Lysand from "lysand-types";
|
||||||
import { client } from "~database/datasource";
|
import { db } from "~drizzle/db";
|
||||||
|
import { attachment } from "~drizzle/schema";
|
||||||
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
|
|
||||||
|
export type Attachment = InferSelectModel<typeof attachment>;
|
||||||
|
|
||||||
export const attachmentToAPI = (
|
export const attachmentToAPI = (
|
||||||
attachment: Attachment,
|
attachment: Attachment,
|
||||||
): APIAsyncAttachment | APIAttachment => {
|
): APIAsyncAttachment | APIAttachment => {
|
||||||
let type = "unknown";
|
let type = "unknown";
|
||||||
|
|
||||||
if (attachment.mime_type.startsWith("image/")) {
|
if (attachment.mimeType.startsWith("image/")) {
|
||||||
type = "image";
|
type = "image";
|
||||||
} else if (attachment.mime_type.startsWith("video/")) {
|
} else if (attachment.mimeType.startsWith("video/")) {
|
||||||
type = "video";
|
type = "video";
|
||||||
} else if (attachment.mime_type.startsWith("audio/")) {
|
} else if (attachment.mimeType.startsWith("audio/")) {
|
||||||
type = "audio";
|
type = "audio";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,8 +26,8 @@ export const attachmentToAPI = (
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
type: type as "image" | "video" | "audio" | "unknown",
|
type: type as "image" | "video" | "audio" | "unknown",
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
remote_url: attachment.remote_url,
|
remote_url: attachment.remoteUrl,
|
||||||
preview_url: attachment.thumbnail_url,
|
preview_url: attachment.thumbnailUrl,
|
||||||
text_url: null,
|
text_url: null,
|
||||||
meta: {
|
meta: {
|
||||||
width: attachment.width || undefined,
|
width: attachment.width || undefined,
|
||||||
|
|
@ -63,7 +66,7 @@ export const attachmentToLysand = (
|
||||||
attachment: Attachment,
|
attachment: Attachment,
|
||||||
): Lysand.ContentFormat => {
|
): Lysand.ContentFormat => {
|
||||||
return {
|
return {
|
||||||
[attachment.mime_type]: {
|
[attachment.mimeType]: {
|
||||||
content: attachment.url,
|
content: attachment.url,
|
||||||
blurhash: attachment.blurhash ?? undefined,
|
blurhash: attachment.blurhash ?? undefined,
|
||||||
description: attachment.description ?? undefined,
|
description: attachment.description ?? undefined,
|
||||||
|
|
@ -82,13 +85,15 @@ export const attachmentToLysand = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const attachmentFromLysand = async (
|
export const attachmentFromLysand = async (
|
||||||
attachment: Lysand.ContentFormat,
|
attachmentToConvert: Lysand.ContentFormat,
|
||||||
): Promise<Attachment> => {
|
): Promise<InferSelectModel<typeof attachment>> => {
|
||||||
const key = Object.keys(attachment)[0];
|
const key = Object.keys(attachmentToConvert)[0];
|
||||||
const value = attachment[key];
|
const value = attachmentToConvert[key];
|
||||||
|
|
||||||
return await client.attachment.create({
|
const result = await db
|
||||||
data: {
|
.insert(attachment)
|
||||||
|
.values({
|
||||||
|
mimeType: key,
|
||||||
url: value.content,
|
url: value.content,
|
||||||
description: value.description || undefined,
|
description: value.description || undefined,
|
||||||
duration: value.duration || undefined,
|
duration: value.duration || undefined,
|
||||||
|
|
@ -97,10 +102,11 @@ export const attachmentFromLysand = async (
|
||||||
size: value.size || undefined,
|
size: value.size || undefined,
|
||||||
width: value.width || undefined,
|
width: value.width || undefined,
|
||||||
sha256: value.hash?.sha256 || undefined,
|
sha256: value.hash?.sha256 || undefined,
|
||||||
mime_type: key,
|
|
||||||
blurhash: value.blurhash || undefined,
|
blurhash: value.blurhash || undefined,
|
||||||
},
|
})
|
||||||
});
|
.returning();
|
||||||
|
|
||||||
|
return result[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUrl = (name: string, config: Config) => {
|
export const getUrl = (name: string, config: Config) => {
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,36 @@
|
||||||
import type { Emoji } from "@prisma/client";
|
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import type { APIEmoji } from "~types/entities/emoji";
|
import type { APIEmoji } from "~types/entities/emoji";
|
||||||
import type * as Lysand from "lysand-types";
|
import type * as Lysand from "lysand-types";
|
||||||
import { addInstanceIfNotExists } from "./Instance";
|
import { addInstanceIfNotExists } from "./Instance";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { emoji, instance } from "~drizzle/schema";
|
||||||
|
import { and, eq, type InferSelectModel } from "drizzle-orm";
|
||||||
|
|
||||||
/**
|
export type EmojiWithInstance = InferSelectModel<typeof emoji> & {
|
||||||
* Represents an emoji entity in the database.
|
instance: InferSelectModel<typeof instance> | null;
|
||||||
*/
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for parsing emojis from local text
|
* Used for parsing emojis from local text
|
||||||
* @param text The text to parse
|
* @param text The text to parse
|
||||||
* @returns An array of emojis
|
* @returns An array of emojis
|
||||||
*/
|
*/
|
||||||
export const parseEmojis = async (text: string): Promise<Emoji[]> => {
|
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 [];
|
||||||
return await client.emoji.findMany({
|
const emojis = await db.query.emoji.findMany({
|
||||||
where: {
|
where: (emoji, { eq, or }) =>
|
||||||
shortcode: {
|
or(
|
||||||
in: matches.map((match) => match.replace(/:/g, "")),
|
...matches
|
||||||
},
|
.map((match) => match.replace(/:/g, ""))
|
||||||
instanceId: null,
|
.map((match) => eq(emoji.shortcode, match)),
|
||||||
},
|
),
|
||||||
include: {
|
with: {
|
||||||
instance: true,
|
instance: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return emojis;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,62 +39,72 @@ export const parseEmojis = async (text: string): Promise<Emoji[]> => {
|
||||||
* @param host Host to fetch the emoji from if remote
|
* @param host Host to fetch the emoji from if remote
|
||||||
* @returns The emoji
|
* @returns The emoji
|
||||||
*/
|
*/
|
||||||
export const fetchEmoji = async (emoji: Lysand.Emoji, host?: string) => {
|
export const fetchEmoji = async (
|
||||||
const existingEmoji = await client.emoji.findFirst({
|
emojiToFetch: Lysand.Emoji,
|
||||||
where: {
|
host?: string,
|
||||||
shortcode: emoji.name,
|
): Promise<EmojiWithInstance> => {
|
||||||
instance: host
|
const existingEmoji = await db
|
||||||
? {
|
.select()
|
||||||
base_url: host,
|
.from(emoji)
|
||||||
}
|
.innerJoin(instance, eq(emoji.instanceId, instance.id))
|
||||||
: null,
|
.where(
|
||||||
},
|
and(
|
||||||
});
|
eq(emoji.shortcode, emojiToFetch.name),
|
||||||
|
host ? eq(instance.baseUrl, host) : undefined,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
if (existingEmoji) return existingEmoji;
|
if (existingEmoji[0])
|
||||||
|
return {
|
||||||
|
...existingEmoji[0].Emoji,
|
||||||
|
instance: existingEmoji[0].Instance,
|
||||||
|
};
|
||||||
|
|
||||||
const instance = host ? await addInstanceIfNotExists(host) : null;
|
const foundInstance = host ? await addInstanceIfNotExists(host) : null;
|
||||||
|
|
||||||
return await client.emoji.create({
|
const result = (
|
||||||
data: {
|
await db
|
||||||
shortcode: emoji.name,
|
.insert(emoji)
|
||||||
url: Object.entries(emoji.url)[0][1].content,
|
.values({
|
||||||
|
shortcode: emojiToFetch.name,
|
||||||
|
url: Object.entries(emojiToFetch.url)[0][1].content,
|
||||||
alt:
|
alt:
|
||||||
emoji.alt ||
|
emojiToFetch.alt ||
|
||||||
Object.entries(emoji.url)[0][1].description ||
|
Object.entries(emojiToFetch.url)[0][1].description ||
|
||||||
undefined,
|
undefined,
|
||||||
content_type: Object.keys(emoji.url)[0],
|
contentType: Object.keys(emojiToFetch.url)[0],
|
||||||
visible_in_picker: true,
|
visibleInPicker: true,
|
||||||
instance: host
|
instanceId: foundInstance?.id,
|
||||||
? {
|
})
|
||||||
connect: {
|
.returning()
|
||||||
id: instance?.id,
|
)[0];
|
||||||
},
|
|
||||||
}
|
return {
|
||||||
: undefined,
|
...result,
|
||||||
},
|
instance: foundInstance,
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the emoji to an APIEmoji object.
|
* Converts the emoji to an APIEmoji object.
|
||||||
* @returns The APIEmoji object.
|
* @returns The APIEmoji object.
|
||||||
*/
|
*/
|
||||||
export const emojiToAPI = (emoji: Emoji): APIEmoji => {
|
export const emojiToAPI = (emoji: EmojiWithInstance): APIEmoji => {
|
||||||
return {
|
return {
|
||||||
shortcode: emoji.shortcode,
|
shortcode: emoji.shortcode,
|
||||||
static_url: emoji.url, // TODO: Add static version
|
static_url: emoji.url, // TODO: Add static version
|
||||||
url: emoji.url,
|
url: emoji.url,
|
||||||
visible_in_picker: emoji.visible_in_picker,
|
visible_in_picker: emoji.visibleInPicker,
|
||||||
category: undefined,
|
category: undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emojiToLysand = (emoji: Emoji): Lysand.Emoji => {
|
export const emojiToLysand = (emoji: EmojiWithInstance): Lysand.Emoji => {
|
||||||
return {
|
return {
|
||||||
name: emoji.shortcode,
|
name: emoji.shortcode,
|
||||||
url: {
|
url: {
|
||||||
[emoji.content_type]: {
|
[emoji.contentType]: {
|
||||||
content: emoji.url,
|
content: emoji.url,
|
||||||
description: emoji.alt || undefined,
|
description: emoji.alt || undefined,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import type { User } from "@prisma/client";
|
|
||||||
import type * as Lysand from "lysand-types";
|
import type * as Lysand from "lysand-types";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { getUserUri } from "./User";
|
import { getUserUri, type User } from "./User";
|
||||||
|
|
||||||
export const objectToInboxRequest = async (
|
export const objectToInboxRequest = async (
|
||||||
object: Lysand.Entity,
|
object: Lysand.Entity,
|
||||||
author: User,
|
author: User,
|
||||||
userToSendTo: User,
|
userToSendTo: User,
|
||||||
): Promise<Request> => {
|
): Promise<Request> => {
|
||||||
if (!userToSendTo.instanceId || !userToSendTo.endpoints.inbox) {
|
if (!userToSendTo.instanceId || !userToSendTo.endpoints?.inbox) {
|
||||||
throw new Error("UserToSendTo has no inbox or is a local user");
|
throw new Error("UserToSendTo has no inbox or is a local user");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Instance } from "@prisma/client";
|
import { db } from "~drizzle/db";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import type * as Lysand from "lysand-types";
|
import type * as Lysand from "lysand-types";
|
||||||
|
import { instance } from "~drizzle/schema";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an instance in the database.
|
* Represents an instance in the database.
|
||||||
|
|
@ -11,16 +11,12 @@ import type * as Lysand from "lysand-types";
|
||||||
* @param url
|
* @param url
|
||||||
* @returns Either the database instance if it already exists, or a newly created instance.
|
* @returns Either the database instance if it already exists, or a newly created instance.
|
||||||
*/
|
*/
|
||||||
export const addInstanceIfNotExists = async (
|
export const addInstanceIfNotExists = async (url: string) => {
|
||||||
url: string,
|
|
||||||
): Promise<Instance> => {
|
|
||||||
const origin = new URL(url).origin;
|
const origin = new URL(url).origin;
|
||||||
const host = new URL(url).host;
|
const host = new URL(url).host;
|
||||||
|
|
||||||
const found = await client.instance.findFirst({
|
const found = await db.query.instance.findFirst({
|
||||||
where: {
|
where: (instance, { eq }) => eq(instance.baseUrl, host),
|
||||||
base_url: host,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (found) return found;
|
if (found) return found;
|
||||||
|
|
@ -40,12 +36,15 @@ export const addInstanceIfNotExists = async (
|
||||||
throw new Error("Invalid instance metadata (missing name or version)");
|
throw new Error("Invalid instance metadata (missing name or version)");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await client.instance.create({
|
return (
|
||||||
data: {
|
await db
|
||||||
base_url: host,
|
.insert(instance)
|
||||||
|
.values({
|
||||||
|
baseUrl: host,
|
||||||
name: metadata.name,
|
name: metadata.name,
|
||||||
version: metadata.version,
|
version: metadata.version,
|
||||||
logo: metadata.logo,
|
logo: metadata.logo,
|
||||||
},
|
})
|
||||||
});
|
.returning()
|
||||||
|
)[0];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import type { Like } from "@prisma/client";
|
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import type { StatusWithRelations } from "./Status";
|
import type { StatusWithRelations } from "./Status";
|
||||||
import type { UserWithRelations } from "./User";
|
import type { UserWithRelations } from "./User";
|
||||||
import type * as Lysand from "lysand-types";
|
import type * as Lysand from "lysand-types";
|
||||||
|
import { and, eq, type InferSelectModel } from "drizzle-orm";
|
||||||
|
import { notification, like } from "~drizzle/schema";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
|
export type Like = InferSelectModel<typeof like>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Like entity in the database.
|
* Represents a Like entity in the database.
|
||||||
|
|
@ -33,22 +36,18 @@ export const createLike = async (
|
||||||
user: UserWithRelations,
|
user: UserWithRelations,
|
||||||
status: StatusWithRelations,
|
status: StatusWithRelations,
|
||||||
) => {
|
) => {
|
||||||
await client.like.create({
|
await db.insert(like).values({
|
||||||
data: {
|
|
||||||
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 client.notification.create({
|
await db.insert(notification).values({
|
||||||
data: {
|
|
||||||
accountId: user.id,
|
accountId: user.id,
|
||||||
type: "favourite",
|
type: "favourite",
|
||||||
notifiedId: status.authorId,
|
notifiedId: status.authorId,
|
||||||
statusId: status.id,
|
statusId: status.id,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// TODO: Add database jobs for federating this
|
// TODO: Add database jobs for federating this
|
||||||
|
|
@ -64,22 +63,21 @@ export const deleteLike = async (
|
||||||
user: UserWithRelations,
|
user: UserWithRelations,
|
||||||
status: StatusWithRelations,
|
status: StatusWithRelations,
|
||||||
) => {
|
) => {
|
||||||
await client.like.deleteMany({
|
await db
|
||||||
where: {
|
.delete(like)
|
||||||
likedId: status.id,
|
.where(and(eq(like.likedId, status.id), eq(like.likerId, user.id)));
|
||||||
likerId: user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Notify the user that their post has been favourited
|
// Notify the user that their post has been favourited
|
||||||
await client.notification.deleteMany({
|
await db
|
||||||
where: {
|
.delete(notification)
|
||||||
accountId: user.id,
|
.where(
|
||||||
type: "favourite",
|
and(
|
||||||
notifiedId: status.authorId,
|
eq(notification.accountId, user.id),
|
||||||
statusId: status.id,
|
eq(notification.type, "favourite"),
|
||||||
},
|
eq(notification.notifiedId, status.authorId),
|
||||||
});
|
eq(notification.statusId, status.id),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (user.instanceId === null && status.author.instanceId !== null) {
|
if (user.instanceId === null && status.author.instanceId !== null) {
|
||||||
// User is local, federate the delete
|
// User is local, federate the delete
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import type { Notification } from "@prisma/client";
|
|
||||||
import type { APINotification } from "~types/entities/notification";
|
import type { APINotification } from "~types/entities/notification";
|
||||||
import { type StatusWithRelations, statusToAPI } from "./Status";
|
import { type StatusWithRelations, statusToAPI } from "./Status";
|
||||||
import { type UserWithRelations, userToAPI } from "./User";
|
import { type UserWithRelations, userToAPI } from "./User";
|
||||||
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
|
import type { notification } from "~drizzle/schema";
|
||||||
|
|
||||||
|
export type Notification = InferSelectModel<typeof notification>;
|
||||||
|
|
||||||
export type NotificationWithRelations = Notification & {
|
export type NotificationWithRelations = Notification & {
|
||||||
status: StatusWithRelations | null;
|
status: StatusWithRelations | null;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
import type { LysandObject } from "@prisma/client";
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
import { client } from "~database/datasource";
|
import { db } from "~drizzle/db";
|
||||||
import type { LysandObjectType } from "~types/lysand/Object";
|
import { lysandObject } from "~drizzle/schema";
|
||||||
|
import { findFirstUser } from "./User";
|
||||||
|
import type * as Lysand from "lysand-types";
|
||||||
|
|
||||||
|
export type LysandObject = InferSelectModel<typeof lysandObject>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Lysand object in the database.
|
* Represents a Lysand object in the database.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const createFromObject = async (object: LysandObjectType) => {
|
export const createFromObject = async (
|
||||||
const foundObject = await client.lysandObject.findFirst({
|
object: Lysand.Entity,
|
||||||
where: { remote_id: object.id },
|
authorUri: string,
|
||||||
include: {
|
) => {
|
||||||
|
const foundObject = await db.query.lysandObject.findFirst({
|
||||||
|
where: (o, { eq }) => eq(o.remoteId, object.id),
|
||||||
|
with: {
|
||||||
author: true,
|
author: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -18,21 +25,19 @@ export const createFromObject = async (object: LysandObjectType) => {
|
||||||
return foundObject;
|
return foundObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const author = await client.lysandObject.findFirst({
|
const author = await findFirstUser({
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
where: (user, { eq }) => eq(user.uri, authorUri),
|
||||||
where: { uri: (object as any).author },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return await client.lysandObject.create({
|
return await db.insert(lysandObject).values({
|
||||||
data: {
|
|
||||||
authorId: author?.id,
|
authorId: author?.id,
|
||||||
created_at: new Date(object.created_at).toISOString(),
|
createdAt: new Date(object.created_at).toISOString(),
|
||||||
extensions: object.extensions || {},
|
extensions: object.extensions,
|
||||||
remote_id: object.id,
|
remoteId: object.id,
|
||||||
type: object.type,
|
type: object.type,
|
||||||
uri: object.uri,
|
uri: object.uri,
|
||||||
// Rest of data (remove id, author, created_at, extensions, type, uri)
|
// Rest of data (remove id, author, created_at, extensions, type, uri)
|
||||||
extra_data: Object.fromEntries(
|
extraData: Object.fromEntries(
|
||||||
Object.entries(object).filter(
|
Object.entries(object).filter(
|
||||||
([key]) =>
|
([key]) =>
|
||||||
![
|
![
|
||||||
|
|
@ -45,18 +50,18 @@ export const createFromObject = async (object: LysandObjectType) => {
|
||||||
].includes(key),
|
].includes(key),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toLysand = (lyObject: LysandObject): LysandObjectType => {
|
export const toLysand = (lyObject: LysandObject): Lysand.Entity => {
|
||||||
return {
|
return {
|
||||||
id: lyObject.remote_id || lyObject.id,
|
id: lyObject.remoteId || lyObject.id,
|
||||||
created_at: new Date(lyObject.created_at).toISOString(),
|
created_at: new Date(lyObject.createdAt).toISOString(),
|
||||||
type: lyObject.type,
|
type: lyObject.type,
|
||||||
uri: lyObject.uri,
|
uri: lyObject.uri,
|
||||||
...lyObject.extra_data,
|
...(lyObject.extraData as object),
|
||||||
extensions: lyObject.extensions,
|
// @ts-expect-error Assume stored JSON is valid
|
||||||
|
extensions: lyObject.extensions as object,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import type { User } from "@prisma/client";
|
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
// import { Worker } from "bullmq";
|
// import { Worker } from "bullmq";
|
||||||
import { type StatusWithRelations, statusToLysand } from "./Status";
|
import { type StatusWithRelations, statusToLysand } from "./Status";
|
||||||
|
|
@ -118,70 +117,6 @@ import { type StatusWithRelations, statusToLysand } from "./Status";
|
||||||
}
|
}
|
||||||
); */
|
); */
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a string into an ArrayBuffer
|
|
||||||
* from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
|
|
||||||
*/
|
|
||||||
export const str2ab = (str: string) => {
|
|
||||||
const buf = new ArrayBuffer(str.length);
|
|
||||||
const bufView = new Uint8Array(buf);
|
|
||||||
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
|
||||||
bufView[i] = str.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const federateStatusTo = async (
|
|
||||||
status: StatusWithRelations,
|
|
||||||
sender: User,
|
|
||||||
user: User,
|
|
||||||
) => {
|
|
||||||
const privateKey = await crypto.subtle.importKey(
|
|
||||||
"pkcs8",
|
|
||||||
str2ab(atob(user.privateKey ?? "")),
|
|
||||||
"Ed25519",
|
|
||||||
false,
|
|
||||||
["sign"],
|
|
||||||
);
|
|
||||||
|
|
||||||
const digest = await crypto.subtle.digest(
|
|
||||||
"SHA-256",
|
|
||||||
new TextEncoder().encode("request_body"),
|
|
||||||
);
|
|
||||||
|
|
||||||
const userInbox = new URL(user.endpoints.inbox);
|
|
||||||
|
|
||||||
const date = new Date();
|
|
||||||
|
|
||||||
const signature = await crypto.subtle.sign(
|
|
||||||
"Ed25519",
|
|
||||||
privateKey,
|
|
||||||
new TextEncoder().encode(
|
|
||||||
`(request-target): post ${userInbox.pathname}\n` +
|
|
||||||
`host: ${userInbox.host}\n` +
|
|
||||||
`date: ${date.toUTCString()}\n` +
|
|
||||||
`digest: SHA-256=${btoa(
|
|
||||||
String.fromCharCode(...new Uint8Array(digest)),
|
|
||||||
)}\n`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const signatureBase64 = btoa(
|
|
||||||
String.fromCharCode(...new Uint8Array(signature)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return fetch(userInbox, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Date: date.toUTCString(),
|
|
||||||
Origin: config.http.base_url,
|
|
||||||
Signature: `keyId="${sender.uri}",algorithm="ed25519",headers="(request-target) host date digest",signature="${signatureBase64}"`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(statusToLysand(status)),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addStatusFederationJob = async (statusId: string) => {
|
export const addStatusFederationJob = async (statusId: string) => {
|
||||||
/* await federationQueue.add("federation", {
|
/* await federationQueue.add("federation", {
|
||||||
id: statusId,
|
id: statusId,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import type { Relationship, User } from "@prisma/client";
|
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import type { APIRelationship } from "~types/entities/relationship";
|
import type { APIRelationship } from "~types/entities/relationship";
|
||||||
|
import type { User } from "./User";
|
||||||
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
|
import { relationship } from "~drizzle/schema";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
/**
|
export type Relationship = InferSelectModel<typeof relationship>;
|
||||||
* Stores Mastodon API relationships
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new relationship between two users.
|
* Creates a new relationship between two users.
|
||||||
|
|
@ -16,8 +17,10 @@ export const createNewRelationship = async (
|
||||||
owner: User,
|
owner: User,
|
||||||
other: User,
|
other: User,
|
||||||
): Promise<Relationship> => {
|
): Promise<Relationship> => {
|
||||||
return await client.relationship.create({
|
return (
|
||||||
data: {
|
await db
|
||||||
|
.insert(relationship)
|
||||||
|
.values({
|
||||||
ownerId: owner.id,
|
ownerId: owner.id,
|
||||||
subjectId: other.id,
|
subjectId: other.id,
|
||||||
languages: [],
|
languages: [],
|
||||||
|
|
@ -33,8 +36,10 @@ export const createNewRelationship = async (
|
||||||
domainBlocking: false,
|
domainBlocking: false,
|
||||||
endorsed: false,
|
endorsed: false,
|
||||||
note: "",
|
note: "",
|
||||||
},
|
updatedAt: new Date().toISOString(),
|
||||||
});
|
})
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkForBidirectionalRelationships = async (
|
export const checkForBidirectionalRelationships = async (
|
||||||
|
|
@ -42,18 +47,14 @@ export const checkForBidirectionalRelationships = async (
|
||||||
user2: User,
|
user2: User,
|
||||||
createIfNotExists = true,
|
createIfNotExists = true,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const relationship1 = await client.relationship.findFirst({
|
const relationship1 = await db.query.relationship.findFirst({
|
||||||
where: {
|
where: (rel, { and, eq }) =>
|
||||||
ownerId: user1.id,
|
and(eq(rel.ownerId, user1.id), eq(rel.subjectId, user2.id)),
|
||||||
subjectId: user2.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const relationship2 = await client.relationship.findFirst({
|
const relationship2 = await db.query.relationship.findFirst({
|
||||||
where: {
|
where: (rel, { and, eq }) =>
|
||||||
ownerId: user2.id,
|
and(eq(rel.ownerId, user2.id), eq(rel.subjectId, user1.id)),
|
||||||
subjectId: user1.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!relationship1 && !relationship2 && createIfNotExists) {
|
if (!relationship1 && !relationship2 && createIfNotExists) {
|
||||||
|
|
@ -82,7 +83,7 @@ export const relationshipToAPI = (rel: Relationship): APIRelationship => {
|
||||||
notifying: rel.notifying,
|
notifying: rel.notifying,
|
||||||
requested: rel.requested,
|
requested: rel.requested,
|
||||||
showing_reblogs: rel.showingReblogs,
|
showing_reblogs: rel.showingReblogs,
|
||||||
languages: rel.languages,
|
languages: rel.languages ?? [],
|
||||||
note: rel.note,
|
note: rel.note,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,35 +1,128 @@
|
||||||
import { addUserToMeilisearch } from "@meilisearch";
|
import { addUserToMeilisearch } from "@meilisearch";
|
||||||
import type { User } from "@prisma/client";
|
|
||||||
import { Prisma } from "@prisma/client";
|
|
||||||
import { type Config, config } from "config-manager";
|
import { type Config, config } from "config-manager";
|
||||||
import { htmlToText } from "html-to-text";
|
import { htmlToText } from "html-to-text";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import type { APIAccount } from "~types/entities/account";
|
import type { APIAccount } from "~types/entities/account";
|
||||||
import type { APISource } from "~types/entities/source";
|
import type { APISource } from "~types/entities/source";
|
||||||
import type * as Lysand from "lysand-types";
|
import type * as Lysand from "lysand-types";
|
||||||
import { fetchEmoji, emojiToAPI, emojiToLysand } from "./Emoji";
|
import {
|
||||||
|
fetchEmoji,
|
||||||
|
emojiToAPI,
|
||||||
|
emojiToLysand,
|
||||||
|
type EmojiWithInstance,
|
||||||
|
} from "./Emoji";
|
||||||
import { addInstanceIfNotExists } from "./Instance";
|
import { addInstanceIfNotExists } from "./Instance";
|
||||||
import { userRelations } from "./relations";
|
|
||||||
import { createNewRelationship } from "./Relationship";
|
import { createNewRelationship } from "./Relationship";
|
||||||
import { getBestContentType, urlToContentFormat } from "@content_types";
|
import { getBestContentType, urlToContentFormat } from "@content_types";
|
||||||
import { objectToInboxRequest } from "./Federation";
|
import { objectToInboxRequest } from "./Federation";
|
||||||
|
import { and, eq, sql, type InferSelectModel } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
emojiToUser,
|
||||||
|
instance,
|
||||||
|
notification,
|
||||||
|
relationship,
|
||||||
|
user,
|
||||||
|
} from "~drizzle/schema";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
|
export type User = InferSelectModel<typeof user> & {
|
||||||
|
endpoints?: Partial<{
|
||||||
|
dislikes: string;
|
||||||
|
featured: string;
|
||||||
|
likes: string;
|
||||||
|
followers: string;
|
||||||
|
following: string;
|
||||||
|
inbox: string;
|
||||||
|
outbox: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserWithRelations = User & {
|
||||||
|
instance: InferSelectModel<typeof instance> | null;
|
||||||
|
emojis: EmojiWithInstance[];
|
||||||
|
followerCount: number;
|
||||||
|
followingCount: number;
|
||||||
|
statusCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserWithRelationsAndRelationships = UserWithRelations & {
|
||||||
|
relationships: InferSelectModel<typeof relationship>[];
|
||||||
|
relationshipSubjects: InferSelectModel<typeof relationship>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userRelations: {
|
||||||
|
instance: true;
|
||||||
|
emojis: {
|
||||||
|
with: {
|
||||||
|
emoji: {
|
||||||
|
with: {
|
||||||
|
instance: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} = {
|
||||||
|
instance: true,
|
||||||
|
emojis: {
|
||||||
|
with: {
|
||||||
|
emoji: {
|
||||||
|
with: {
|
||||||
|
instance: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userExtras = {
|
||||||
|
followerCount:
|
||||||
|
sql`(SELECT COUNT(*) FROM "Relationship" "relationships" WHERE ("relationships"."ownerId" = "user".id AND "relationships"."following" = true))`.as(
|
||||||
|
"follower_count",
|
||||||
|
),
|
||||||
|
followingCount:
|
||||||
|
sql`(SELECT COUNT(*) FROM "Relationship" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "user".id AND "relationshipSubjects"."following" = true))`.as(
|
||||||
|
"following_count",
|
||||||
|
),
|
||||||
|
statusCount:
|
||||||
|
sql`(SELECT COUNT(*) FROM "Status" "statuses" WHERE "statuses"."authorId" = "user".id)`.as(
|
||||||
|
"status_count",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userExtrasTemplate = (name: string) => ({
|
||||||
|
// @ts-ignore
|
||||||
|
followerCount: sql([
|
||||||
|
`(SELECT COUNT(*) FROM "Relationship" "relationships" WHERE ("relationships"."ownerId" = "${name}".id AND "relationships"."following" = true))`,
|
||||||
|
]).as("follower_count"),
|
||||||
|
// @ts-ignore
|
||||||
|
followingCount: sql([
|
||||||
|
`(SELECT COUNT(*) FROM "Relationship" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "${name}".id AND "relationshipSubjects"."following" = true))`,
|
||||||
|
]).as("following_count"),
|
||||||
|
// @ts-ignore
|
||||||
|
statusCount: sql([
|
||||||
|
`(SELECT COUNT(*) FROM "Status" "statuses" WHERE "statuses"."authorId" = "${name}".id)`,
|
||||||
|
]).as("status_count"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/* const a = await db.query.user.findFirst({
|
||||||
|
with: {
|
||||||
|
instance: true,
|
||||||
|
emojis: {
|
||||||
|
with: {
|
||||||
|
instance: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extras: {
|
||||||
|
//
|
||||||
|
followerCount: sql`SELECT COUNT(*) FROM relationship WHERE owner_id = user.id AND following = true`,
|
||||||
|
},
|
||||||
|
}); */
|
||||||
|
|
||||||
export interface AuthData {
|
export interface AuthData {
|
||||||
user: UserWithRelations | null;
|
user: UserWithRelations | null;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a user in the database.
|
|
||||||
* Stores local and remote users
|
|
||||||
*/
|
|
||||||
|
|
||||||
const userRelations2 = Prisma.validator<Prisma.UserDefaultArgs>()({
|
|
||||||
include: userRelations,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type UserWithRelations = Prisma.UserGetPayload<typeof userRelations2>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the user's avatar in raw URL format
|
* Get the user's avatar in raw URL format
|
||||||
* @param config The config to use
|
* @param config The config to use
|
||||||
|
|
@ -68,19 +161,22 @@ export const followRequestUser = async (
|
||||||
reblogs = false,
|
reblogs = false,
|
||||||
notify = false,
|
notify = false,
|
||||||
languages: string[] = [],
|
languages: string[] = [],
|
||||||
) => {
|
): Promise<InferSelectModel<typeof relationship>> => {
|
||||||
const isRemote = follower.instanceId !== followee.instanceId;
|
const isRemote = follower.instanceId !== followee.instanceId;
|
||||||
|
|
||||||
const relationship = await client.relationship.update({
|
const updatedRelationship = (
|
||||||
where: { id: relationshipId },
|
await db
|
||||||
data: {
|
.update(relationship)
|
||||||
|
.set({
|
||||||
following: isRemote ? false : !followee.isLocked,
|
following: isRemote ? false : !followee.isLocked,
|
||||||
requested: isRemote ? true : followee.isLocked,
|
requested: isRemote ? true : followee.isLocked,
|
||||||
showingReblogs: reblogs,
|
showingReblogs: reblogs,
|
||||||
notifying: notify,
|
notifying: notify,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
},
|
})
|
||||||
});
|
.where(eq(relationship.id, relationshipId))
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
// Federate
|
// Federate
|
||||||
|
|
@ -100,35 +196,26 @@ export const followRequestUser = async (
|
||||||
`Failed to federate follow request from ${follower.id} to ${followee.uri}`,
|
`Failed to federate follow request from ${follower.id} to ${followee.uri}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return await client.relationship.update({
|
return (
|
||||||
where: { id: relationshipId },
|
await db
|
||||||
data: {
|
.update(relationship)
|
||||||
|
.set({
|
||||||
following: false,
|
following: false,
|
||||||
requested: false,
|
requested: false,
|
||||||
},
|
})
|
||||||
});
|
.where(eq(relationship.id, relationshipId))
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (followee.isLocked) {
|
await db.insert(notification).values({
|
||||||
await client.notification.create({
|
accountId: followee.id,
|
||||||
data: {
|
type: followee.isLocked ? "follow_request" : "follow",
|
||||||
accountId: follower.id,
|
notifiedId: follower.id,
|
||||||
type: "follow_request",
|
|
||||||
notifiedId: followee.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
await client.notification.create({
|
|
||||||
data: {
|
|
||||||
accountId: follower.id,
|
|
||||||
type: "follow",
|
|
||||||
notifiedId: followee.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return relationship;
|
return updatedRelationship;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendFollowAccept = async (follower: User, followee: User) => {
|
export const sendFollowAccept = async (follower: User, followee: User) => {
|
||||||
|
|
@ -169,13 +256,88 @@ export const sendFollowReject = async (follower: User, followee: User) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resolveUser = async (uri: string) => {
|
export const transformOutputToUserWithRelations = (
|
||||||
// Check if user not already in database
|
user: Omit<User, "endpoints"> & {
|
||||||
const foundUser = await client.user.findUnique({
|
followerCount: unknown;
|
||||||
where: {
|
followingCount: unknown;
|
||||||
uri,
|
statusCount: unknown;
|
||||||
|
emojis: {
|
||||||
|
a: string;
|
||||||
|
b: string;
|
||||||
|
emoji?: EmojiWithInstance;
|
||||||
|
}[];
|
||||||
|
instance: InferSelectModel<typeof instance> | null;
|
||||||
|
endpoints: unknown;
|
||||||
},
|
},
|
||||||
include: userRelations,
|
): UserWithRelations => {
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
followerCount: Number(user.followerCount),
|
||||||
|
followingCount: Number(user.followingCount),
|
||||||
|
statusCount: Number(user.statusCount),
|
||||||
|
endpoints:
|
||||||
|
user.endpoints ??
|
||||||
|
({} as Partial<{
|
||||||
|
dislikes: string;
|
||||||
|
featured: string;
|
||||||
|
likes: string;
|
||||||
|
followers: string;
|
||||||
|
following: string;
|
||||||
|
inbox: string;
|
||||||
|
outbox: string;
|
||||||
|
}>),
|
||||||
|
emojis: user.emojis.map(
|
||||||
|
(emoji) =>
|
||||||
|
(emoji as unknown as Record<string, object>)
|
||||||
|
.emoji as EmojiWithInstance,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findManyUsers = async (
|
||||||
|
query: Parameters<typeof db.query.user.findMany>[0],
|
||||||
|
): Promise<UserWithRelations[]> => {
|
||||||
|
const output = await db.query.user.findMany({
|
||||||
|
...query,
|
||||||
|
with: {
|
||||||
|
...userRelations,
|
||||||
|
...query?.with,
|
||||||
|
},
|
||||||
|
extras: {
|
||||||
|
...userExtras,
|
||||||
|
...query?.extras,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return output.map((user) => transformOutputToUserWithRelations(user));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findFirstUser = async (
|
||||||
|
query: Parameters<typeof db.query.user.findFirst>[0],
|
||||||
|
): Promise<UserWithRelations | null> => {
|
||||||
|
const output = await db.query.user.findFirst({
|
||||||
|
...query,
|
||||||
|
with: {
|
||||||
|
...userRelations,
|
||||||
|
...query?.with,
|
||||||
|
},
|
||||||
|
extras: {
|
||||||
|
...userExtras,
|
||||||
|
...query?.extras,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!output) return null;
|
||||||
|
|
||||||
|
return transformOutputToUserWithRelations(output);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resolveUser = async (
|
||||||
|
uri: string,
|
||||||
|
): Promise<UserWithRelations | null> => {
|
||||||
|
// Check if user not already in database
|
||||||
|
const foundUser = await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.uri, uri),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (foundUser) return foundUser;
|
if (foundUser) return foundUser;
|
||||||
|
|
@ -192,12 +354,12 @@ export const resolveUser = async (uri: string) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.user.findUnique({
|
const foundLocalUser = await findFirstUser({
|
||||||
where: {
|
where: (user, { eq }) => eq(user.id, uuid[0]),
|
||||||
id: uuid[0],
|
with: userRelations,
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return foundLocalUser || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!URL.canParse(uri)) {
|
if (!URL.canParse(uri)) {
|
||||||
|
|
@ -244,11 +406,13 @@ export const resolveUser = async (uri: string) => {
|
||||||
emojis.push(await fetchEmoji(emoji));
|
emojis.push(await fetchEmoji(emoji));
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await client.user.create({
|
const newUser = (
|
||||||
data: {
|
await db
|
||||||
|
.insert(user)
|
||||||
|
.values({
|
||||||
username: data.username,
|
username: data.username,
|
||||||
uri: data.uri,
|
uri: data.uri,
|
||||||
createdAt: new Date(data.created_at),
|
createdAt: new Date(data.created_at).toISOString(),
|
||||||
endpoints: {
|
endpoints: {
|
||||||
dislikes: data.dislikes,
|
dislikes: data.dislikes,
|
||||||
featured: data.featured,
|
featured: data.featured,
|
||||||
|
|
@ -258,11 +422,6 @@ export const resolveUser = async (uri: string) => {
|
||||||
inbox: data.inbox,
|
inbox: data.inbox,
|
||||||
outbox: data.outbox,
|
outbox: data.outbox,
|
||||||
},
|
},
|
||||||
emojis: {
|
|
||||||
connect: emojis.map((emoji) => ({
|
|
||||||
id: emoji.id,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
instanceId: instance.id,
|
instanceId: instance.id,
|
||||||
avatar: data.avatar
|
avatar: data.avatar
|
||||||
? Object.entries(data.avatar)[0][1].content
|
? Object.entries(data.avatar)[0][1].content
|
||||||
|
|
@ -280,14 +439,29 @@ export const resolveUser = async (uri: string) => {
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
fields: [],
|
fields: [],
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
include: userRelations,
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
// Add emojis to user
|
||||||
|
await db.insert(emojiToUser).values(
|
||||||
|
emojis.map((emoji) => ({
|
||||||
|
a: emoji.id,
|
||||||
|
b: newUser.id,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const finalUser = await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.id, newUser.id),
|
||||||
|
with: userRelations,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to Meilisearch
|
if (!finalUser) return null;
|
||||||
await addUserToMeilisearch(user);
|
|
||||||
|
|
||||||
return user;
|
// Add to Meilisearch
|
||||||
|
await addUserToMeilisearch(finalUser);
|
||||||
|
|
||||||
|
return finalUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserUri = (user: User) => {
|
export const getUserUri = (user: User) => {
|
||||||
|
|
@ -301,19 +475,25 @@ export const getUserUri = (user: User) => {
|
||||||
* Resolves a WebFinger identifier to a user.
|
* Resolves a WebFinger identifier to a user.
|
||||||
* @param identifier Either a UUID or a username
|
* @param identifier Either a UUID or a username
|
||||||
*/
|
*/
|
||||||
export const resolveWebFinger = async (identifier: string, host: string) => {
|
export const resolveWebFinger = async (
|
||||||
|
identifier: string,
|
||||||
|
host: string,
|
||||||
|
): Promise<UserWithRelations | null> => {
|
||||||
// Check if user not already in database
|
// Check if user not already in database
|
||||||
const foundUser = await client.user.findUnique({
|
const foundUser = await db
|
||||||
where: {
|
.select()
|
||||||
username: identifier,
|
.from(user)
|
||||||
instance: {
|
.innerJoin(instance, eq(user.instanceId, instance.id))
|
||||||
base_url: host,
|
.where(and(eq(user.username, identifier), eq(instance.baseUrl, host)))
|
||||||
},
|
.limit(1);
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (foundUser) return foundUser;
|
if (foundUser[0])
|
||||||
|
return (
|
||||||
|
(await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.id, foundUser[0].User.id),
|
||||||
|
with: userRelations,
|
||||||
|
})) || null
|
||||||
|
);
|
||||||
|
|
||||||
const hostWithProtocol = host.startsWith("http") ? host : `https://${host}`;
|
const hostWithProtocol = host.startsWith("http") ? host : `https://${host}`;
|
||||||
|
|
||||||
|
|
@ -383,11 +563,13 @@ export const createNewLocalUser = async (data: {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
header?: string;
|
header?: string;
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
}) => {
|
}): Promise<UserWithRelations | null> => {
|
||||||
const keys = await generateUserKeys();
|
const keys = await generateUserKeys();
|
||||||
|
|
||||||
const user = await client.user.create({
|
const newUser = (
|
||||||
data: {
|
await db
|
||||||
|
.insert(user)
|
||||||
|
.values({
|
||||||
username: data.username,
|
username: data.username,
|
||||||
displayName: data.display_name ?? data.username,
|
displayName: data.display_name ?? data.username,
|
||||||
password: await Bun.password.hash(data.password),
|
password: await Bun.password.hash(data.password),
|
||||||
|
|
@ -398,6 +580,7 @@ export const createNewLocalUser = async (data: {
|
||||||
isAdmin: data.admin ?? false,
|
isAdmin: data.admin ?? false,
|
||||||
publicKey: keys.public_key,
|
publicKey: keys.public_key,
|
||||||
privateKey: keys.private_key,
|
privateKey: keys.private_key,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
source: {
|
source: {
|
||||||
language: null,
|
language: null,
|
||||||
note: "",
|
note: "",
|
||||||
|
|
@ -405,27 +588,32 @@ export const createNewLocalUser = async (data: {
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
fields: [],
|
fields: [],
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
include: userRelations,
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
const finalUser = await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.id, newUser.id),
|
||||||
|
with: userRelations,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to Meilisearch
|
if (!finalUser) return null;
|
||||||
await addUserToMeilisearch(user);
|
|
||||||
|
|
||||||
return user;
|
// Add to Meilisearch
|
||||||
|
await addUserToMeilisearch(finalUser);
|
||||||
|
|
||||||
|
return finalUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses mentions from a list of URIs
|
* Parses mentions from a list of URIs
|
||||||
*/
|
*/
|
||||||
export const parseMentionsUris = async (mentions: string[]) => {
|
export const parseMentionsUris = async (
|
||||||
return await client.user.findMany({
|
mentions: string[],
|
||||||
where: {
|
): Promise<UserWithRelations[]> => {
|
||||||
uri: {
|
return await findManyUsers({
|
||||||
in: mentions,
|
where: (user, { inArray }) => inArray(user.uri, mentions),
|
||||||
},
|
with: userRelations,
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -434,23 +622,22 @@ export const parseMentionsUris = async (mentions: string[]) => {
|
||||||
* @param access_token The access token to retrieve the user from.
|
* @param access_token The access token to retrieve the user from.
|
||||||
* @returns The user associated with the given access token.
|
* @returns The user associated with the given access token.
|
||||||
*/
|
*/
|
||||||
export const retrieveUserFromToken = async (access_token: string) => {
|
export const retrieveUserFromToken = async (
|
||||||
|
access_token: string,
|
||||||
|
): Promise<UserWithRelations | null> => {
|
||||||
if (!access_token) return null;
|
if (!access_token) return null;
|
||||||
|
|
||||||
const token = await client.token.findFirst({
|
const token = await db.query.token.findFirst({
|
||||||
where: {
|
where: (tokens, { eq }) => eq(tokens.accessToken, access_token),
|
||||||
access_token,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
user: {
|
|
||||||
include: userRelations,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!token) return null;
|
if (!token || !token.userId) return null;
|
||||||
|
|
||||||
return token.user;
|
const user = await findFirstUser({
|
||||||
|
where: (user, { eq }) => eq(user.id, token.userId ?? ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -461,34 +648,24 @@ export const retrieveUserFromToken = async (access_token: string) => {
|
||||||
export const getRelationshipToOtherUser = async (
|
export const getRelationshipToOtherUser = async (
|
||||||
user: UserWithRelations,
|
user: UserWithRelations,
|
||||||
other: User,
|
other: User,
|
||||||
) => {
|
): Promise<InferSelectModel<typeof relationship>> => {
|
||||||
const relationship = await client.relationship.findFirst({
|
const foundRelationship = await db.query.relationship.findFirst({
|
||||||
where: {
|
where: (relationship, { and, eq }) =>
|
||||||
ownerId: user.id,
|
and(
|
||||||
subjectId: other.id,
|
eq(relationship.ownerId, user.id),
|
||||||
},
|
eq(relationship.subjectId, other.id),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!relationship) {
|
if (!foundRelationship) {
|
||||||
// Create new relationship
|
// Create new relationship
|
||||||
|
|
||||||
const newRelationship = await createNewRelationship(user, other);
|
const newRelationship = await createNewRelationship(user, other);
|
||||||
|
|
||||||
await client.user.update({
|
|
||||||
where: { id: user.id },
|
|
||||||
data: {
|
|
||||||
relationships: {
|
|
||||||
connect: {
|
|
||||||
id: newRelationship.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return newRelationship;
|
return newRelationship;
|
||||||
}
|
}
|
||||||
|
|
||||||
return relationship;
|
return foundRelationship;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -526,40 +703,42 @@ export const generateUserKeys = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userToAPI = (
|
export const userToAPI = (
|
||||||
user: UserWithRelations,
|
userToConvert: UserWithRelations,
|
||||||
isOwnAccount = false,
|
isOwnAccount = false,
|
||||||
): APIAccount => {
|
): APIAccount => {
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: userToConvert.id,
|
||||||
username: user.username,
|
username: userToConvert.username,
|
||||||
display_name: user.displayName,
|
display_name: userToConvert.displayName,
|
||||||
note: user.note,
|
note: userToConvert.note,
|
||||||
url:
|
url:
|
||||||
user.uri ||
|
userToConvert.uri ||
|
||||||
new URL(`/@${user.username}`, config.http.base_url).toString(),
|
new URL(
|
||||||
avatar: getAvatarUrl(user, config),
|
`/@${userToConvert.username}`,
|
||||||
header: getHeaderUrl(user, config),
|
config.http.base_url,
|
||||||
locked: user.isLocked,
|
).toString(),
|
||||||
created_at: new Date(user.createdAt).toISOString(),
|
avatar: getAvatarUrl(userToConvert, config),
|
||||||
followers_count: user.relationshipSubjects.filter((r) => r.following)
|
header: getHeaderUrl(userToConvert, config),
|
||||||
.length,
|
locked: userToConvert.isLocked,
|
||||||
following_count: user.relationships.filter((r) => r.following).length,
|
created_at: new Date(userToConvert.createdAt).toISOString(),
|
||||||
statuses_count: user._count.statuses,
|
followers_count: userToConvert.followerCount,
|
||||||
emojis: user.emojis.map((emoji) => emojiToAPI(emoji)),
|
following_count: userToConvert.followingCount,
|
||||||
|
statuses_count: userToConvert.statusCount,
|
||||||
|
emojis: userToConvert.emojis.map((emoji) => emojiToAPI(emoji)),
|
||||||
// TODO: Add fields
|
// TODO: Add fields
|
||||||
fields: [],
|
fields: [],
|
||||||
bot: user.isBot,
|
bot: userToConvert.isBot,
|
||||||
source:
|
source:
|
||||||
isOwnAccount && user.source
|
isOwnAccount && userToConvert.source
|
||||||
? (user.source as APISource)
|
? (userToConvert.source as APISource)
|
||||||
: undefined,
|
: undefined,
|
||||||
// TODO: Add static avatar and header
|
// TODO: Add static avatar and header
|
||||||
avatar_static: "",
|
avatar_static: "",
|
||||||
header_static: "",
|
header_static: "",
|
||||||
acct:
|
acct:
|
||||||
user.instance === null
|
userToConvert.instance === null
|
||||||
? user.username
|
? userToConvert.username
|
||||||
: `${user.username}@${user.instance.base_url}`,
|
: `${userToConvert.username}@${userToConvert.instance.baseUrl}`,
|
||||||
// TODO: Add these fields
|
// TODO: Add these fields
|
||||||
limited: false,
|
limited: false,
|
||||||
moved: null,
|
moved: null,
|
||||||
|
|
@ -569,8 +748,8 @@ export const userToAPI = (
|
||||||
mute_expires_at: undefined,
|
mute_expires_at: undefined,
|
||||||
group: false,
|
group: false,
|
||||||
pleroma: {
|
pleroma: {
|
||||||
is_admin: user.isAdmin,
|
is_admin: userToConvert.isAdmin,
|
||||||
is_moderator: user.isAdmin,
|
is_moderator: userToConvert.isAdmin,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
462
drizzle-scanned/0000_third_misty_knight.sql
Normal file
462
drizzle-scanned/0000_third_misty_knight.sql
Normal file
|
|
@ -0,0 +1,462 @@
|
||||||
|
-- Current sql file was generated after introspecting the database
|
||||||
|
-- If you want to run this migration please uncomment this code before executing migrations
|
||||||
|
/*
|
||||||
|
CREATE TABLE IF NOT EXISTS "_prisma_migrations" (
|
||||||
|
"id" varchar(36) PRIMARY KEY NOT NULL,
|
||||||
|
"checksum" varchar(64) NOT NULL,
|
||||||
|
"finished_at" timestamp with time zone,
|
||||||
|
"migration_name" varchar(255) NOT NULL,
|
||||||
|
"logs" text,
|
||||||
|
"rolled_back_at" timestamp with time zone,
|
||||||
|
"started_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"applied_steps_count" integer DEFAULT 0 NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Emoji" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"shortcode" text NOT NULL,
|
||||||
|
"url" text NOT NULL,
|
||||||
|
"visible_in_picker" boolean NOT NULL,
|
||||||
|
"instanceId" uuid,
|
||||||
|
"alt" text,
|
||||||
|
"content_type" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Like" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"likerId" uuid NOT NULL,
|
||||||
|
"likedId" uuid NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "LysandObject" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"remote_id" text NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"uri" text NOT NULL,
|
||||||
|
"created_at" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"authorId" uuid,
|
||||||
|
"extra_data" jsonb NOT NULL,
|
||||||
|
"extensions" jsonb NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Relationship" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"ownerId" uuid NOT NULL,
|
||||||
|
"subjectId" uuid NOT NULL,
|
||||||
|
"following" boolean NOT NULL,
|
||||||
|
"showingReblogs" boolean NOT NULL,
|
||||||
|
"notifying" boolean NOT NULL,
|
||||||
|
"followedBy" boolean NOT NULL,
|
||||||
|
"blocking" boolean NOT NULL,
|
||||||
|
"blockedBy" boolean NOT NULL,
|
||||||
|
"muting" boolean NOT NULL,
|
||||||
|
"mutingNotifications" boolean NOT NULL,
|
||||||
|
"requested" boolean NOT NULL,
|
||||||
|
"domainBlocking" boolean NOT NULL,
|
||||||
|
"endorsed" boolean NOT NULL,
|
||||||
|
"languages" text[],
|
||||||
|
"note" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"updatedAt" timestamp(3) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Application" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"website" text,
|
||||||
|
"vapid_key" text,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"secret" text NOT NULL,
|
||||||
|
"scopes" text NOT NULL,
|
||||||
|
"redirect_uris" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Token" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"token_type" text NOT NULL,
|
||||||
|
"scope" text NOT NULL,
|
||||||
|
"access_token" text NOT NULL,
|
||||||
|
"code" text NOT NULL,
|
||||||
|
"created_at" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"applicationId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_EmojiToUser" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_EmojiToStatus" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_StatusToUser" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_UserPinnedNotes" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Attachment" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"url" text NOT NULL,
|
||||||
|
"remote_url" text,
|
||||||
|
"thumbnail_url" text,
|
||||||
|
"mime_type" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"blurhash" text,
|
||||||
|
"sha256" text,
|
||||||
|
"fps" integer,
|
||||||
|
"duration" integer,
|
||||||
|
"width" integer,
|
||||||
|
"height" integer,
|
||||||
|
"size" integer,
|
||||||
|
"statusId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Notification" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"notifiedId" uuid NOT NULL,
|
||||||
|
"accountId" uuid NOT NULL,
|
||||||
|
"statusId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Status" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"uri" text,
|
||||||
|
"authorId" uuid NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"updatedAt" timestamp(3) NOT NULL,
|
||||||
|
"reblogId" uuid,
|
||||||
|
"content" text DEFAULT '' NOT NULL,
|
||||||
|
"contentType" text DEFAULT 'text/plain' NOT NULL,
|
||||||
|
"visibility" text NOT NULL,
|
||||||
|
"inReplyToPostId" uuid,
|
||||||
|
"quotingPostId" uuid,
|
||||||
|
"instanceId" uuid,
|
||||||
|
"sensitive" boolean NOT NULL,
|
||||||
|
"spoilerText" text DEFAULT '' NOT NULL,
|
||||||
|
"applicationId" uuid,
|
||||||
|
"contentSource" text DEFAULT '' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Instance" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"base_url" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"version" text NOT NULL,
|
||||||
|
"logo" jsonb NOT NULL,
|
||||||
|
"disableAutomoderation" boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "OpenIdAccount" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"serverId" text NOT NULL,
|
||||||
|
"issuerId" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "User" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"uri" text,
|
||||||
|
"username" text NOT NULL,
|
||||||
|
"displayName" text NOT NULL,
|
||||||
|
"password" text,
|
||||||
|
"email" text,
|
||||||
|
"note" text DEFAULT '' NOT NULL,
|
||||||
|
"isAdmin" boolean DEFAULT false NOT NULL,
|
||||||
|
"endpoints" jsonb,
|
||||||
|
"source" jsonb NOT NULL,
|
||||||
|
"avatar" text NOT NULL,
|
||||||
|
"header" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"updatedAt" timestamp(3) NOT NULL,
|
||||||
|
"isBot" boolean DEFAULT false NOT NULL,
|
||||||
|
"isLocked" boolean DEFAULT false NOT NULL,
|
||||||
|
"isDiscoverable" boolean DEFAULT false NOT NULL,
|
||||||
|
"sanctions" text[] DEFAULT 'RRAY[',
|
||||||
|
"publicKey" text NOT NULL,
|
||||||
|
"privateKey" text,
|
||||||
|
"instanceId" uuid,
|
||||||
|
"disableAutomoderation" boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "OpenIdLoginFlow" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"codeVerifier" text NOT NULL,
|
||||||
|
"applicationId" uuid,
|
||||||
|
"issuerId" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Flag" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"flagType" text DEFAULT 'other' NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"flaggeStatusId" uuid,
|
||||||
|
"flaggedUserId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "ModNote" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"notedStatusId" uuid,
|
||||||
|
"notedUserId" uuid,
|
||||||
|
"modId" uuid NOT NULL,
|
||||||
|
"note" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "ModTag" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"taggedStatusId" uuid,
|
||||||
|
"taggedUserId" uuid,
|
||||||
|
"modId" uuid NOT NULL,
|
||||||
|
"tag" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "LysandObject_remote_id_key" ON "LysandObject" ("remote_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "LysandObject_uri_key" ON "LysandObject" ("uri");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "Application_client_id_key" ON "Application" ("client_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_EmojiToUser_AB_unique" ON "_EmojiToUser" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_EmojiToUser_B_index" ON "_EmojiToUser" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_EmojiToStatus_AB_unique" ON "_EmojiToStatus" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_EmojiToStatus_B_index" ON "_EmojiToStatus" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_StatusToUser_AB_unique" ON "_StatusToUser" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_StatusToUser_B_index" ON "_StatusToUser" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_UserPinnedNotes_AB_unique" ON "_UserPinnedNotes" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_UserPinnedNotes_B_index" ON "_UserPinnedNotes" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "Status_uri_key" ON "Status" ("uri");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "User_uri_key" ON "User" ("uri");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "User_username_key" ON "User" ("username");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "User_email_key" ON "User" ("email");--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Emoji" ADD CONSTRAINT "Emoji_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Like" ADD CONSTRAINT "Like_likerId_fkey" FOREIGN KEY ("likerId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Like" ADD CONSTRAINT "Like_likedId_fkey" FOREIGN KEY ("likedId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "LysandObject" ADD CONSTRAINT "LysandObject_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "public"."LysandObject"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Relationship" ADD CONSTRAINT "Relationship_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Relationship" ADD CONSTRAINT "Relationship_subjectId_fkey" FOREIGN KEY ("subjectId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Token" ADD CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Token" ADD CONSTRAINT "Token_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "public"."Application"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToUser" ADD CONSTRAINT "_EmojiToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Emoji"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToUser" ADD CONSTRAINT "_EmojiToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToStatus" ADD CONSTRAINT "_EmojiToStatus_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Emoji"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToStatus" ADD CONSTRAINT "_EmojiToStatus_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_StatusToUser" ADD CONSTRAINT "_StatusToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_StatusToUser" ADD CONSTRAINT "_StatusToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_UserPinnedNotes" ADD CONSTRAINT "_UserPinnedNotes_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_UserPinnedNotes" ADD CONSTRAINT "_UserPinnedNotes_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Attachment" ADD CONSTRAINT "Attachment_statusId_fkey" FOREIGN KEY ("statusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_notifiedId_fkey" FOREIGN KEY ("notifiedId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_statusId_fkey" FOREIGN KEY ("statusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_reblogId_fkey" FOREIGN KEY ("reblogId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_inReplyToPostId_fkey" FOREIGN KEY ("inReplyToPostId") REFERENCES "public"."Status"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_quotingPostId_fkey" FOREIGN KEY ("quotingPostId") REFERENCES "public"."Status"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "public"."Application"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "OpenIdAccount" ADD CONSTRAINT "OpenIdAccount_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "User" ADD CONSTRAINT "User_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "OpenIdLoginFlow" ADD CONSTRAINT "OpenIdLoginFlow_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "public"."Application"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Flag" ADD CONSTRAINT "Flag_flaggeStatusId_fkey" FOREIGN KEY ("flaggeStatusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Flag" ADD CONSTRAINT "Flag_flaggedUserId_fkey" FOREIGN KEY ("flaggedUserId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_notedStatusId_fkey" FOREIGN KEY ("notedStatusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_notedUserId_fkey" FOREIGN KEY ("notedUserId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_modId_fkey" FOREIGN KEY ("modId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_taggedStatusId_fkey" FOREIGN KEY ("taggedStatusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_taggedUserId_fkey" FOREIGN KEY ("taggedUserId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_modId_fkey" FOREIGN KEY ("modId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
*/
|
||||||
1867
drizzle-scanned/meta/0000_snapshot.json
Normal file
1867
drizzle-scanned/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
13
drizzle-scanned/meta/_journal.json
Normal file
13
drizzle-scanned/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1712812153499,
|
||||||
|
"tag": "0000_third_misty_knight",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
292
drizzle-scanned/schema.ts
Normal file
292
drizzle-scanned/schema.ts
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
import { pgTable, varchar, timestamp, text, integer, foreignKey, uuid, boolean, uniqueIndex, jsonb, index } from "drizzle-orm/pg-core"
|
||||||
|
import { sql } from "drizzle-orm"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const prismaMigrations = pgTable("_prisma_migrations", {
|
||||||
|
id: varchar("id", { length: 36 }).primaryKey().notNull(),
|
||||||
|
checksum: varchar("checksum", { length: 64 }).notNull(),
|
||||||
|
finishedAt: timestamp("finished_at", { withTimezone: true, mode: 'string' }),
|
||||||
|
migrationName: varchar("migration_name", { length: 255 }).notNull(),
|
||||||
|
logs: text("logs"),
|
||||||
|
rolledBackAt: timestamp("rolled_back_at", { withTimezone: true, mode: 'string' }),
|
||||||
|
startedAt: timestamp("started_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
appliedStepsCount: integer("applied_steps_count").default(0).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emoji = pgTable("Emoji", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
shortcode: text("shortcode").notNull(),
|
||||||
|
url: text("url").notNull(),
|
||||||
|
visibleInPicker: boolean("visible_in_picker").notNull(),
|
||||||
|
instanceId: uuid("instanceId").references(() => instance.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
alt: text("alt"),
|
||||||
|
contentType: text("content_type").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const like = pgTable("Like", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
likerId: uuid("likerId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
likedId: uuid("likedId").notNull().references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const lysandObject = pgTable("LysandObject", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
remoteId: text("remote_id").notNull(),
|
||||||
|
type: text("type").notNull(),
|
||||||
|
uri: text("uri").notNull(),
|
||||||
|
createdAt: timestamp("created_at", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
authorId: uuid("authorId"),
|
||||||
|
extraData: jsonb("extra_data").notNull(),
|
||||||
|
extensions: jsonb("extensions").notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
remoteIdKey: uniqueIndex("LysandObject_remote_id_key").on(table.remoteId),
|
||||||
|
uriKey: uniqueIndex("LysandObject_uri_key").on(table.uri),
|
||||||
|
lysandObjectAuthorIdFkey: foreignKey({
|
||||||
|
columns: [table.authorId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "LysandObject_authorId_fkey"
|
||||||
|
}).onUpdate("cascade").onDelete("cascade"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const relationship = pgTable("Relationship", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
ownerId: uuid("ownerId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
subjectId: uuid("subjectId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
following: boolean("following").notNull(),
|
||||||
|
showingReblogs: boolean("showingReblogs").notNull(),
|
||||||
|
notifying: boolean("notifying").notNull(),
|
||||||
|
followedBy: boolean("followedBy").notNull(),
|
||||||
|
blocking: boolean("blocking").notNull(),
|
||||||
|
blockedBy: boolean("blockedBy").notNull(),
|
||||||
|
muting: boolean("muting").notNull(),
|
||||||
|
mutingNotifications: boolean("mutingNotifications").notNull(),
|
||||||
|
requested: boolean("requested").notNull(),
|
||||||
|
domainBlocking: boolean("domainBlocking").notNull(),
|
||||||
|
endorsed: boolean("endorsed").notNull(),
|
||||||
|
languages: text("languages").array(),
|
||||||
|
note: text("note").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt", { precision: 3, mode: 'string' }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const application = pgTable("Application", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
website: text("website"),
|
||||||
|
vapidKey: text("vapid_key"),
|
||||||
|
clientId: text("client_id").notNull(),
|
||||||
|
secret: text("secret").notNull(),
|
||||||
|
scopes: text("scopes").notNull(),
|
||||||
|
redirectUris: text("redirect_uris").notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
clientIdKey: uniqueIndex("Application_client_id_key").on(table.clientId),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const token = pgTable("Token", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
tokenType: text("token_type").notNull(),
|
||||||
|
scope: text("scope").notNull(),
|
||||||
|
accessToken: text("access_token").notNull(),
|
||||||
|
code: text("code").notNull(),
|
||||||
|
createdAt: timestamp("created_at", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
userId: uuid("userId").references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
applicationId: uuid("applicationId").references(() => application.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emojiToUser = pgTable("_EmojiToUser", {
|
||||||
|
a: uuid("A").notNull().references(() => emoji.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
b: uuid("B").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_EmojiToUser_AB_unique").on(table.a, table.b),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emojiToStatus = pgTable("_EmojiToStatus", {
|
||||||
|
a: uuid("A").notNull().references(() => emoji.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
b: uuid("B").notNull().references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_EmojiToStatus_AB_unique").on(table.a, table.b),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const statusToUser = pgTable("_StatusToUser", {
|
||||||
|
a: uuid("A").notNull().references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
b: uuid("B").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_StatusToUser_AB_unique").on(table.a, table.b),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const userPinnedNotes = pgTable("_UserPinnedNotes", {
|
||||||
|
a: uuid("A").notNull().references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
b: uuid("B").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_UserPinnedNotes_AB_unique").on(table.a, table.b),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const attachment = pgTable("Attachment", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
url: text("url").notNull(),
|
||||||
|
remoteUrl: text("remote_url"),
|
||||||
|
thumbnailUrl: text("thumbnail_url"),
|
||||||
|
mimeType: text("mime_type").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
blurhash: text("blurhash"),
|
||||||
|
sha256: text("sha256"),
|
||||||
|
fps: integer("fps"),
|
||||||
|
duration: integer("duration"),
|
||||||
|
width: integer("width"),
|
||||||
|
height: integer("height"),
|
||||||
|
size: integer("size"),
|
||||||
|
statusId: uuid("statusId").references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notification = pgTable("Notification", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
type: text("type").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
notifiedId: uuid("notifiedId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
accountId: uuid("accountId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
statusId: uuid("statusId").references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const status = pgTable("Status", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
uri: text("uri"),
|
||||||
|
authorId: uuid("authorId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt", { precision: 3, mode: 'string' }).notNull(),
|
||||||
|
reblogId: uuid("reblogId"),
|
||||||
|
content: text("content").default('').notNull(),
|
||||||
|
contentType: text("contentType").default('text/plain').notNull(),
|
||||||
|
visibility: text("visibility").notNull(),
|
||||||
|
inReplyToPostId: uuid("inReplyToPostId"),
|
||||||
|
quotingPostId: uuid("quotingPostId"),
|
||||||
|
instanceId: uuid("instanceId").references(() => instance.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
sensitive: boolean("sensitive").notNull(),
|
||||||
|
spoilerText: text("spoilerText").default('').notNull(),
|
||||||
|
applicationId: uuid("applicationId").references(() => application.id, { onDelete: "set null", onUpdate: "cascade" } ),
|
||||||
|
contentSource: text("contentSource").default('').notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
uriKey: uniqueIndex("Status_uri_key").on(table.uri),
|
||||||
|
statusReblogIdFkey: foreignKey({
|
||||||
|
columns: [table.reblogId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "Status_reblogId_fkey"
|
||||||
|
}).onUpdate("cascade").onDelete("cascade"),
|
||||||
|
statusInReplyToPostIdFkey: foreignKey({
|
||||||
|
columns: [table.inReplyToPostId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "Status_inReplyToPostId_fkey"
|
||||||
|
}).onUpdate("cascade").onDelete("set null"),
|
||||||
|
statusQuotingPostIdFkey: foreignKey({
|
||||||
|
columns: [table.quotingPostId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "Status_quotingPostId_fkey"
|
||||||
|
}).onUpdate("cascade").onDelete("set null"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const instance = pgTable("Instance", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
baseUrl: text("base_url").notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
version: text("version").notNull(),
|
||||||
|
logo: jsonb("logo").notNull(),
|
||||||
|
disableAutomoderation: boolean("disableAutomoderation").default(false).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const openIdAccount = pgTable("OpenIdAccount", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
userId: uuid("userId").references(() => user.id, { onDelete: "set null", onUpdate: "cascade" } ),
|
||||||
|
serverId: text("serverId").notNull(),
|
||||||
|
issuerId: text("issuerId").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const user = pgTable("User", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
uri: text("uri"),
|
||||||
|
username: text("username").notNull(),
|
||||||
|
displayName: text("displayName").notNull(),
|
||||||
|
password: text("password"),
|
||||||
|
email: text("email"),
|
||||||
|
note: text("note").default('').notNull(),
|
||||||
|
isAdmin: boolean("isAdmin").default(false).notNull(),
|
||||||
|
endpoints: jsonb("endpoints"),
|
||||||
|
source: jsonb("source").notNull(),
|
||||||
|
avatar: text("avatar").notNull(),
|
||||||
|
header: text("header").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt", { precision: 3, mode: 'string' }).notNull(),
|
||||||
|
isBot: boolean("isBot").default(false).notNull(),
|
||||||
|
isLocked: boolean("isLocked").default(false).notNull(),
|
||||||
|
isDiscoverable: boolean("isDiscoverable").default(false).notNull(),
|
||||||
|
sanctions: text("sanctions").default('RRAY[').array(),
|
||||||
|
publicKey: text("publicKey").notNull(),
|
||||||
|
privateKey: text("privateKey"),
|
||||||
|
instanceId: uuid("instanceId").references(() => instance.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
disableAutomoderation: boolean("disableAutomoderation").default(false).notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
uriKey: uniqueIndex("User_uri_key").on(table.uri),
|
||||||
|
usernameKey: uniqueIndex("User_username_key").on(table.username),
|
||||||
|
emailKey: uniqueIndex("User_email_key").on(table.email),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const openIdLoginFlow = pgTable("OpenIdLoginFlow", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
codeVerifier: text("codeVerifier").notNull(),
|
||||||
|
applicationId: uuid("applicationId").references(() => application.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
issuerId: text("issuerId").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const flag = pgTable("Flag", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
flagType: text("flagType").default('other').notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
flaggeStatusId: uuid("flaggeStatusId").references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
flaggedUserId: uuid("flaggedUserId").references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const modNote = pgTable("ModNote", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
notedStatusId: uuid("notedStatusId").references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
notedUserId: uuid("notedUserId").references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
modId: uuid("modId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
note: text("note").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const modTag = pgTable("ModTag", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
taggedStatusId: uuid("taggedStatusId").references(() => status.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
taggedUserId: uuid("taggedUserId").references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
modId: uuid("modId").notNull().references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" } ),
|
||||||
|
tag: text("tag").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: 'string' }).defaultNow().notNull(),
|
||||||
|
});
|
||||||
19
drizzle.config.ts
Normal file
19
drizzle.config.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import type { Config } from "drizzle-kit";
|
||||||
|
import { config } from "config-manager";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
driver: "pg",
|
||||||
|
out: "./drizzle",
|
||||||
|
schema: "./drizzle/schema.ts",
|
||||||
|
dbCredentials: {
|
||||||
|
host: config.database.host,
|
||||||
|
port: Number(config.database.port),
|
||||||
|
user: config.database.username,
|
||||||
|
password: config.database.password,
|
||||||
|
database: config.database.database,
|
||||||
|
},
|
||||||
|
// Print all statements
|
||||||
|
verbose: true,
|
||||||
|
// Always ask for confirmation
|
||||||
|
strict: true,
|
||||||
|
} satisfies Config;
|
||||||
462
drizzle/0000_illegal_living_lightning.sql
Normal file
462
drizzle/0000_illegal_living_lightning.sql
Normal file
|
|
@ -0,0 +1,462 @@
|
||||||
|
-- Current sql file was generated after introspecting the database
|
||||||
|
-- If you want to run this migration please uncomment this code before executing migrations
|
||||||
|
/*
|
||||||
|
CREATE TABLE IF NOT EXISTS "_prisma_migrations" (
|
||||||
|
"id" varchar(36) PRIMARY KEY NOT NULL,
|
||||||
|
"checksum" varchar(64) NOT NULL,
|
||||||
|
"finished_at" timestamp with time zone,
|
||||||
|
"migration_name" varchar(255) NOT NULL,
|
||||||
|
"logs" text,
|
||||||
|
"rolled_back_at" timestamp with time zone,
|
||||||
|
"started_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"applied_steps_count" integer DEFAULT 0 NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Emoji" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"shortcode" text NOT NULL,
|
||||||
|
"url" text NOT NULL,
|
||||||
|
"visible_in_picker" boolean NOT NULL,
|
||||||
|
"instanceId" uuid,
|
||||||
|
"alt" text,
|
||||||
|
"content_type" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Like" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"likerId" uuid NOT NULL,
|
||||||
|
"likedId" uuid NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "LysandObject" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"remote_id" text NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"uri" text NOT NULL,
|
||||||
|
"created_at" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"authorId" uuid,
|
||||||
|
"extra_data" jsonb NOT NULL,
|
||||||
|
"extensions" jsonb NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Relationship" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"ownerId" uuid NOT NULL,
|
||||||
|
"subjectId" uuid NOT NULL,
|
||||||
|
"following" boolean NOT NULL,
|
||||||
|
"showingReblogs" boolean NOT NULL,
|
||||||
|
"notifying" boolean NOT NULL,
|
||||||
|
"followedBy" boolean NOT NULL,
|
||||||
|
"blocking" boolean NOT NULL,
|
||||||
|
"blockedBy" boolean NOT NULL,
|
||||||
|
"muting" boolean NOT NULL,
|
||||||
|
"mutingNotifications" boolean NOT NULL,
|
||||||
|
"requested" boolean NOT NULL,
|
||||||
|
"domainBlocking" boolean NOT NULL,
|
||||||
|
"endorsed" boolean NOT NULL,
|
||||||
|
"languages" text[],
|
||||||
|
"note" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"updatedAt" timestamp(3) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Application" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"website" text,
|
||||||
|
"vapid_key" text,
|
||||||
|
"client_id" text NOT NULL,
|
||||||
|
"secret" text NOT NULL,
|
||||||
|
"scopes" text NOT NULL,
|
||||||
|
"redirect_uris" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Token" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"token_type" text NOT NULL,
|
||||||
|
"scope" text NOT NULL,
|
||||||
|
"access_token" text NOT NULL,
|
||||||
|
"code" text NOT NULL,
|
||||||
|
"created_at" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"applicationId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_EmojiToUser" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_EmojiToStatus" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_StatusToUser" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "_UserPinnedNotes" (
|
||||||
|
"A" uuid NOT NULL,
|
||||||
|
"B" uuid NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Attachment" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"url" text NOT NULL,
|
||||||
|
"remote_url" text,
|
||||||
|
"thumbnail_url" text,
|
||||||
|
"mime_type" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"blurhash" text,
|
||||||
|
"sha256" text,
|
||||||
|
"fps" integer,
|
||||||
|
"duration" integer,
|
||||||
|
"width" integer,
|
||||||
|
"height" integer,
|
||||||
|
"size" integer,
|
||||||
|
"statusId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Notification" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"notifiedId" uuid NOT NULL,
|
||||||
|
"accountId" uuid NOT NULL,
|
||||||
|
"statusId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Status" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"uri" text,
|
||||||
|
"authorId" uuid NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"updatedAt" timestamp(3) NOT NULL,
|
||||||
|
"reblogId" uuid,
|
||||||
|
"content" text DEFAULT '' NOT NULL,
|
||||||
|
"contentType" text DEFAULT 'text/plain' NOT NULL,
|
||||||
|
"visibility" text NOT NULL,
|
||||||
|
"inReplyToPostId" uuid,
|
||||||
|
"quotingPostId" uuid,
|
||||||
|
"instanceId" uuid,
|
||||||
|
"sensitive" boolean NOT NULL,
|
||||||
|
"spoilerText" text DEFAULT '' NOT NULL,
|
||||||
|
"applicationId" uuid,
|
||||||
|
"contentSource" text DEFAULT '' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Instance" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"base_url" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"version" text NOT NULL,
|
||||||
|
"logo" jsonb NOT NULL,
|
||||||
|
"disableAutomoderation" boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "OpenIdAccount" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"serverId" text NOT NULL,
|
||||||
|
"issuerId" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "User" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"uri" text,
|
||||||
|
"username" text NOT NULL,
|
||||||
|
"displayName" text NOT NULL,
|
||||||
|
"password" text,
|
||||||
|
"email" text,
|
||||||
|
"note" text DEFAULT '' NOT NULL,
|
||||||
|
"isAdmin" boolean DEFAULT false NOT NULL,
|
||||||
|
"endpoints" jsonb,
|
||||||
|
"source" jsonb NOT NULL,
|
||||||
|
"avatar" text NOT NULL,
|
||||||
|
"header" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"updatedAt" timestamp(3) NOT NULL,
|
||||||
|
"isBot" boolean DEFAULT false NOT NULL,
|
||||||
|
"isLocked" boolean DEFAULT false NOT NULL,
|
||||||
|
"isDiscoverable" boolean DEFAULT false NOT NULL,
|
||||||
|
"sanctions" text[] DEFAULT 'RRAY[',
|
||||||
|
"publicKey" text NOT NULL,
|
||||||
|
"privateKey" text,
|
||||||
|
"instanceId" uuid,
|
||||||
|
"disableAutomoderation" boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "OpenIdLoginFlow" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"codeVerifier" text NOT NULL,
|
||||||
|
"applicationId" uuid,
|
||||||
|
"issuerId" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "Flag" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"flagType" text DEFAULT 'other' NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
"flaggeStatusId" uuid,
|
||||||
|
"flaggedUserId" uuid
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "ModNote" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"notedStatusId" uuid,
|
||||||
|
"notedUserId" uuid,
|
||||||
|
"modId" uuid NOT NULL,
|
||||||
|
"note" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "ModTag" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v7() NOT NULL,
|
||||||
|
"taggedStatusId" uuid,
|
||||||
|
"taggedUserId" uuid,
|
||||||
|
"modId" uuid NOT NULL,
|
||||||
|
"tag" text NOT NULL,
|
||||||
|
"createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "LysandObject_remote_id_key" ON "LysandObject" ("remote_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "LysandObject_uri_key" ON "LysandObject" ("uri");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "Application_client_id_key" ON "Application" ("client_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_EmojiToUser_AB_unique" ON "_EmojiToUser" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_EmojiToUser_B_index" ON "_EmojiToUser" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_EmojiToStatus_AB_unique" ON "_EmojiToStatus" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_EmojiToStatus_B_index" ON "_EmojiToStatus" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_StatusToUser_AB_unique" ON "_StatusToUser" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_StatusToUser_B_index" ON "_StatusToUser" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "_UserPinnedNotes_AB_unique" ON "_UserPinnedNotes" ("A","B");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "_UserPinnedNotes_B_index" ON "_UserPinnedNotes" ("B");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "Status_uri_key" ON "Status" ("uri");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "User_uri_key" ON "User" ("uri");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "User_username_key" ON "User" ("username");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "User_email_key" ON "User" ("email");--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Emoji" ADD CONSTRAINT "Emoji_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Like" ADD CONSTRAINT "Like_likerId_fkey" FOREIGN KEY ("likerId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Like" ADD CONSTRAINT "Like_likedId_fkey" FOREIGN KEY ("likedId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "LysandObject" ADD CONSTRAINT "LysandObject_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "public"."LysandObject"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Relationship" ADD CONSTRAINT "Relationship_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Relationship" ADD CONSTRAINT "Relationship_subjectId_fkey" FOREIGN KEY ("subjectId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Token" ADD CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Token" ADD CONSTRAINT "Token_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "public"."Application"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToUser" ADD CONSTRAINT "_EmojiToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Emoji"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToUser" ADD CONSTRAINT "_EmojiToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToStatus" ADD CONSTRAINT "_EmojiToStatus_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Emoji"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_EmojiToStatus" ADD CONSTRAINT "_EmojiToStatus_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_StatusToUser" ADD CONSTRAINT "_StatusToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_StatusToUser" ADD CONSTRAINT "_StatusToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_UserPinnedNotes" ADD CONSTRAINT "_UserPinnedNotes_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_UserPinnedNotes" ADD CONSTRAINT "_UserPinnedNotes_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Attachment" ADD CONSTRAINT "Attachment_statusId_fkey" FOREIGN KEY ("statusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_notifiedId_fkey" FOREIGN KEY ("notifiedId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_statusId_fkey" FOREIGN KEY ("statusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_reblogId_fkey" FOREIGN KEY ("reblogId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_inReplyToPostId_fkey" FOREIGN KEY ("inReplyToPostId") REFERENCES "public"."Status"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_quotingPostId_fkey" FOREIGN KEY ("quotingPostId") REFERENCES "public"."Status"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Status" ADD CONSTRAINT "Status_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "public"."Application"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "OpenIdAccount" ADD CONSTRAINT "OpenIdAccount_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE set null ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "User" ADD CONSTRAINT "User_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "OpenIdLoginFlow" ADD CONSTRAINT "OpenIdLoginFlow_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "public"."Application"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Flag" ADD CONSTRAINT "Flag_flaggeStatusId_fkey" FOREIGN KEY ("flaggeStatusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Flag" ADD CONSTRAINT "Flag_flaggedUserId_fkey" FOREIGN KEY ("flaggedUserId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_notedStatusId_fkey" FOREIGN KEY ("notedStatusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_notedUserId_fkey" FOREIGN KEY ("notedUserId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_modId_fkey" FOREIGN KEY ("modId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_taggedStatusId_fkey" FOREIGN KEY ("taggedStatusId") REFERENCES "public"."Status"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_taggedUserId_fkey" FOREIGN KEY ("taggedUserId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_modId_fkey" FOREIGN KEY ("modId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
*/
|
||||||
14
drizzle/db.ts
Normal file
14
drizzle/db.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Client } from "pg";
|
||||||
|
import { config } from "config-manager";
|
||||||
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
|
import * as schema from "./schema";
|
||||||
|
|
||||||
|
export const client = new Client({
|
||||||
|
host: config.database.host,
|
||||||
|
port: Number(config.database.port),
|
||||||
|
user: config.database.username,
|
||||||
|
password: config.database.password,
|
||||||
|
database: config.database.database,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const db = drizzle(client, { schema });
|
||||||
1867
drizzle/meta/0000_snapshot.json
Normal file
1867
drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1712805159664,
|
||||||
|
"tag": "0000_illegal_living_lightning",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
677
drizzle/schema.ts
Normal file
677
drizzle/schema.ts
Normal file
|
|
@ -0,0 +1,677 @@
|
||||||
|
import {
|
||||||
|
pgTable,
|
||||||
|
timestamp,
|
||||||
|
text,
|
||||||
|
integer,
|
||||||
|
foreignKey,
|
||||||
|
uuid,
|
||||||
|
boolean,
|
||||||
|
uniqueIndex,
|
||||||
|
jsonb,
|
||||||
|
index,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { relations, sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
export const emoji = pgTable("Emoji", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
shortcode: text("shortcode").notNull(),
|
||||||
|
url: text("url").notNull(),
|
||||||
|
visibleInPicker: boolean("visible_in_picker").notNull(),
|
||||||
|
alt: text("alt"),
|
||||||
|
contentType: text("content_type").notNull(),
|
||||||
|
instanceId: uuid("instanceId").references(() => instance.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const like = pgTable("Like", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
likerId: uuid("likerId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
likedId: uuid("likedId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const lysandObject = pgTable(
|
||||||
|
"LysandObject",
|
||||||
|
{
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
remoteId: text("remote_id").notNull(),
|
||||||
|
type: text("type").notNull(),
|
||||||
|
uri: text("uri").notNull(),
|
||||||
|
createdAt: timestamp("created_at", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
authorId: uuid("authorId"),
|
||||||
|
extraData: jsonb("extra_data").notNull(),
|
||||||
|
extensions: jsonb("extensions").notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
remoteIdKey: uniqueIndex("LysandObject_remote_id_key").on(
|
||||||
|
table.remoteId,
|
||||||
|
),
|
||||||
|
uriKey: uniqueIndex("LysandObject_uri_key").on(table.uri),
|
||||||
|
lysandObjectAuthorIdFkey: foreignKey({
|
||||||
|
columns: [table.authorId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "LysandObject_authorId_fkey",
|
||||||
|
})
|
||||||
|
.onUpdate("cascade")
|
||||||
|
.onDelete("cascade"),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const relationship = pgTable("Relationship", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
ownerId: uuid("ownerId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
subjectId: uuid("subjectId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
following: boolean("following").notNull(),
|
||||||
|
showingReblogs: boolean("showingReblogs").notNull(),
|
||||||
|
notifying: boolean("notifying").notNull(),
|
||||||
|
followedBy: boolean("followedBy").notNull(),
|
||||||
|
blocking: boolean("blocking").notNull(),
|
||||||
|
blockedBy: boolean("blockedBy").notNull(),
|
||||||
|
muting: boolean("muting").notNull(),
|
||||||
|
mutingNotifications: boolean("mutingNotifications").notNull(),
|
||||||
|
requested: boolean("requested").notNull(),
|
||||||
|
domainBlocking: boolean("domainBlocking").notNull(),
|
||||||
|
endorsed: boolean("endorsed").notNull(),
|
||||||
|
languages: text("languages").array(),
|
||||||
|
note: text("note").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt", {
|
||||||
|
precision: 3,
|
||||||
|
mode: "string",
|
||||||
|
}).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const application = pgTable(
|
||||||
|
"Application",
|
||||||
|
{
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
website: text("website"),
|
||||||
|
vapidKey: text("vapid_key"),
|
||||||
|
clientId: text("client_id").notNull(),
|
||||||
|
secret: text("secret").notNull(),
|
||||||
|
scopes: text("scopes").notNull(),
|
||||||
|
redirectUris: text("redirect_uris").notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
clientIdKey: uniqueIndex("Application_client_id_key").on(
|
||||||
|
table.clientId,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const token = pgTable("Token", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
tokenType: text("token_type").notNull(),
|
||||||
|
scope: text("scope").notNull(),
|
||||||
|
accessToken: text("access_token").notNull(),
|
||||||
|
code: text("code").notNull(),
|
||||||
|
createdAt: timestamp("created_at", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
userId: uuid("userId").references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
applicationId: uuid("applicationId").references(() => application.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const attachment = pgTable("Attachment", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
url: text("url").notNull(),
|
||||||
|
remoteUrl: text("remote_url"),
|
||||||
|
thumbnailUrl: text("thumbnail_url"),
|
||||||
|
mimeType: text("mime_type").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
blurhash: text("blurhash"),
|
||||||
|
sha256: text("sha256"),
|
||||||
|
fps: integer("fps"),
|
||||||
|
duration: integer("duration"),
|
||||||
|
width: integer("width"),
|
||||||
|
height: integer("height"),
|
||||||
|
size: integer("size"),
|
||||||
|
statusId: uuid("statusId").references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notification = pgTable("Notification", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
type: text("type").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
notifiedId: uuid("notifiedId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
accountId: uuid("accountId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
statusId: uuid("statusId").references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const status = pgTable(
|
||||||
|
"Status",
|
||||||
|
{
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
uri: text("uri"),
|
||||||
|
authorId: uuid("authorId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt", {
|
||||||
|
precision: 3,
|
||||||
|
mode: "string",
|
||||||
|
}).notNull(),
|
||||||
|
reblogId: uuid("reblogId"),
|
||||||
|
content: text("content").default("").notNull(),
|
||||||
|
contentType: text("contentType").default("text/plain").notNull(),
|
||||||
|
visibility: text("visibility").notNull(),
|
||||||
|
inReplyToPostId: uuid("inReplyToPostId"),
|
||||||
|
quotingPostId: uuid("quotingPostId"),
|
||||||
|
instanceId: uuid("instanceId").references(() => instance.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
sensitive: boolean("sensitive").notNull(),
|
||||||
|
spoilerText: text("spoilerText").default("").notNull(),
|
||||||
|
applicationId: uuid("applicationId").references(() => application.id, {
|
||||||
|
onDelete: "set null",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
contentSource: text("contentSource").default("").notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
uriKey: uniqueIndex("Status_uri_key").on(table.uri),
|
||||||
|
statusReblogIdFkey: foreignKey({
|
||||||
|
columns: [table.reblogId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "Status_reblogId_fkey",
|
||||||
|
})
|
||||||
|
.onUpdate("cascade")
|
||||||
|
.onDelete("cascade"),
|
||||||
|
statusInReplyToPostIdFkey: foreignKey({
|
||||||
|
columns: [table.inReplyToPostId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "Status_inReplyToPostId_fkey",
|
||||||
|
})
|
||||||
|
.onUpdate("cascade")
|
||||||
|
.onDelete("set null"),
|
||||||
|
statusQuotingPostIdFkey: foreignKey({
|
||||||
|
columns: [table.quotingPostId],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
name: "Status_quotingPostId_fkey",
|
||||||
|
})
|
||||||
|
.onUpdate("cascade")
|
||||||
|
.onDelete("set null"),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const instance = pgTable("Instance", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
baseUrl: text("base_url").notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
version: text("version").notNull(),
|
||||||
|
logo: jsonb("logo").notNull(),
|
||||||
|
disableAutomoderation: boolean("disableAutomoderation")
|
||||||
|
.default(false)
|
||||||
|
.notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const openIdAccount = pgTable("OpenIdAccount", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
userId: uuid("userId").references(() => user.id, {
|
||||||
|
onDelete: "set null",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
serverId: text("serverId").notNull(),
|
||||||
|
issuerId: text("issuerId").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const user = pgTable(
|
||||||
|
"User",
|
||||||
|
{
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
uri: text("uri"),
|
||||||
|
username: text("username").notNull(),
|
||||||
|
displayName: text("displayName").notNull(),
|
||||||
|
password: text("password"),
|
||||||
|
email: text("email"),
|
||||||
|
note: text("note").default("").notNull(),
|
||||||
|
isAdmin: boolean("isAdmin").default(false).notNull(),
|
||||||
|
endpoints: jsonb("endpoints"),
|
||||||
|
source: jsonb("source").notNull(),
|
||||||
|
avatar: text("avatar").notNull(),
|
||||||
|
header: text("header").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt", {
|
||||||
|
precision: 3,
|
||||||
|
mode: "string",
|
||||||
|
})
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
isBot: boolean("isBot").default(false).notNull(),
|
||||||
|
isLocked: boolean("isLocked").default(false).notNull(),
|
||||||
|
isDiscoverable: boolean("isDiscoverable").default(false).notNull(),
|
||||||
|
sanctions: text("sanctions").default("RRAY[").array(),
|
||||||
|
publicKey: text("publicKey").notNull(),
|
||||||
|
privateKey: text("privateKey"),
|
||||||
|
instanceId: uuid("instanceId").references(() => instance.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
disableAutomoderation: boolean("disableAutomoderation")
|
||||||
|
.default(false)
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
uriKey: uniqueIndex("User_uri_key").on(table.uri),
|
||||||
|
usernameKey: uniqueIndex("User_username_key").on(table.username),
|
||||||
|
emailKey: uniqueIndex("User_email_key").on(table.email),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const openIdLoginFlow = pgTable("OpenIdLoginFlow", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
codeVerifier: text("codeVerifier").notNull(),
|
||||||
|
applicationId: uuid("applicationId").references(() => application.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
issuerId: text("issuerId").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const flag = pgTable("Flag", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
flagType: text("flagType").default("other").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
flaggeStatusId: uuid("flaggeStatusId").references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
flaggedUserId: uuid("flaggedUserId").references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const modNote = pgTable("ModNote", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
notedStatusId: uuid("notedStatusId").references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
notedUserId: uuid("notedUserId").references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
modId: uuid("modId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
note: text("note").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const modTag = pgTable("ModTag", {
|
||||||
|
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||||
|
taggedStatusId: uuid("taggedStatusId").references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
taggedUserId: uuid("taggedUserId").references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
modId: uuid("modId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
tag: text("tag").notNull(),
|
||||||
|
createdAt: timestamp("createdAt", { precision: 3, mode: "string" })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emojiToUser = pgTable(
|
||||||
|
"_EmojiToUser",
|
||||||
|
{
|
||||||
|
a: uuid("A")
|
||||||
|
.notNull()
|
||||||
|
.references(() => emoji.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
b: uuid("B")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_EmojiToUser_AB_unique").on(
|
||||||
|
table.a,
|
||||||
|
table.b,
|
||||||
|
),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const emojiToUserRelations = relations(emojiToUser, ({ one }) => ({
|
||||||
|
emoji: one(emoji, {
|
||||||
|
fields: [emojiToUser.a],
|
||||||
|
references: [emoji.id],
|
||||||
|
}),
|
||||||
|
user: one(user, {
|
||||||
|
fields: [emojiToUser.b],
|
||||||
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const emojiToStatus = pgTable(
|
||||||
|
"_EmojiToStatus",
|
||||||
|
{
|
||||||
|
a: uuid("A")
|
||||||
|
.notNull()
|
||||||
|
.references(() => emoji.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
b: uuid("B")
|
||||||
|
.notNull()
|
||||||
|
.references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_EmojiToStatus_AB_unique").on(
|
||||||
|
table.a,
|
||||||
|
table.b,
|
||||||
|
),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const statusToUser = pgTable(
|
||||||
|
"_StatusToUser",
|
||||||
|
{
|
||||||
|
a: uuid("A")
|
||||||
|
.notNull()
|
||||||
|
.references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
b: uuid("B")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_StatusToUser_AB_unique").on(
|
||||||
|
table.a,
|
||||||
|
table.b,
|
||||||
|
),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const userPinnedNotes = pgTable(
|
||||||
|
"_UserPinnedNotes",
|
||||||
|
{
|
||||||
|
a: uuid("A")
|
||||||
|
.notNull()
|
||||||
|
.references(() => status.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
b: uuid("B")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
(table) => {
|
||||||
|
return {
|
||||||
|
abUnique: uniqueIndex("_UserPinnedNotes_AB_unique").on(
|
||||||
|
table.a,
|
||||||
|
table.b,
|
||||||
|
),
|
||||||
|
bIdx: index().on(table.b),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const attachmentRelations = relations(attachment, ({ one }) => ({
|
||||||
|
status: one(status, {
|
||||||
|
fields: [attachment.statusId],
|
||||||
|
references: [status.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const userRelations = relations(user, ({ many, one }) => ({
|
||||||
|
emojis: many(emojiToUser),
|
||||||
|
pinnedNotes: many(userPinnedNotes),
|
||||||
|
statuses: many(status, {
|
||||||
|
relationName: "StatusToAuthor",
|
||||||
|
}),
|
||||||
|
likes: many(like),
|
||||||
|
relationships: many(relationship, {
|
||||||
|
relationName: "RelationshipToOwner",
|
||||||
|
}),
|
||||||
|
relationshipSubjects: many(relationship, {
|
||||||
|
relationName: "RelationshipToSubject",
|
||||||
|
}),
|
||||||
|
notifications: many(notification),
|
||||||
|
openIdAccounts: many(openIdAccount),
|
||||||
|
flags: many(flag),
|
||||||
|
modNotes: many(modNote),
|
||||||
|
modTags: many(modTag),
|
||||||
|
tokens: many(token),
|
||||||
|
instance: one(instance, {
|
||||||
|
fields: [user.instanceId],
|
||||||
|
references: [instance.id],
|
||||||
|
}),
|
||||||
|
mentionedIn: many(statusToUser),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const relationshipRelations = relations(relationship, ({ one }) => ({
|
||||||
|
owner: one(user, {
|
||||||
|
fields: [relationship.ownerId],
|
||||||
|
references: [user.id],
|
||||||
|
relationName: "RelationshipToOwner",
|
||||||
|
}),
|
||||||
|
subject: one(user, {
|
||||||
|
fields: [relationship.subjectId],
|
||||||
|
references: [user.id],
|
||||||
|
relationName: "RelationshipToSubject",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const tokenRelations = relations(token, ({ one }) => ({
|
||||||
|
user: one(user, {
|
||||||
|
fields: [token.userId],
|
||||||
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
application: one(application, {
|
||||||
|
fields: [token.applicationId],
|
||||||
|
references: [application.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const statusToUserRelations = relations(statusToUser, ({ one }) => ({
|
||||||
|
status: one(status, {
|
||||||
|
fields: [statusToUser.a],
|
||||||
|
references: [status.id],
|
||||||
|
}),
|
||||||
|
user: one(user, {
|
||||||
|
fields: [statusToUser.b],
|
||||||
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const userPinnedNotesRelations = relations(
|
||||||
|
userPinnedNotes,
|
||||||
|
({ one }) => ({
|
||||||
|
status: one(status, {
|
||||||
|
fields: [userPinnedNotes.a],
|
||||||
|
references: [status.id],
|
||||||
|
}),
|
||||||
|
user: one(user, {
|
||||||
|
fields: [userPinnedNotes.b],
|
||||||
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const statusRelations = relations(status, ({ many, one }) => ({
|
||||||
|
emojis: many(emojiToStatus),
|
||||||
|
author: one(user, {
|
||||||
|
fields: [status.authorId],
|
||||||
|
references: [user.id],
|
||||||
|
relationName: "StatusToAuthor",
|
||||||
|
}),
|
||||||
|
attachments: many(attachment),
|
||||||
|
mentions: many(statusToUser),
|
||||||
|
reblog: one(status, {
|
||||||
|
fields: [status.reblogId],
|
||||||
|
references: [status.id],
|
||||||
|
relationName: "StatusToReblog",
|
||||||
|
}),
|
||||||
|
usersThatHavePinned: many(userPinnedNotes),
|
||||||
|
inReplyTo: one(status, {
|
||||||
|
fields: [status.inReplyToPostId],
|
||||||
|
references: [status.id],
|
||||||
|
relationName: "StatusToReplying",
|
||||||
|
}),
|
||||||
|
quoting: one(status, {
|
||||||
|
fields: [status.quotingPostId],
|
||||||
|
references: [status.id],
|
||||||
|
relationName: "StatusToQuoting",
|
||||||
|
}),
|
||||||
|
application: one(application, {
|
||||||
|
fields: [status.applicationId],
|
||||||
|
references: [application.id],
|
||||||
|
}),
|
||||||
|
quotes: many(status, {
|
||||||
|
relationName: "StatusToQuoting",
|
||||||
|
}),
|
||||||
|
replies: many(status, {
|
||||||
|
relationName: "StatusToReplying",
|
||||||
|
}),
|
||||||
|
likes: many(like),
|
||||||
|
reblogs: many(status, {
|
||||||
|
relationName: "StatusToReblog",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const likeRelations = relations(like, ({ one }) => ({
|
||||||
|
liker: one(user, {
|
||||||
|
fields: [like.likerId],
|
||||||
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
liked: one(status, {
|
||||||
|
fields: [like.likedId],
|
||||||
|
references: [status.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const emojiRelations = relations(emoji, ({ one, many }) => ({
|
||||||
|
instance: one(instance, {
|
||||||
|
fields: [emoji.instanceId],
|
||||||
|
references: [instance.id],
|
||||||
|
}),
|
||||||
|
users: many(emojiToUser),
|
||||||
|
statuses: many(emojiToStatus),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const instanceRelations = relations(instance, ({ many }) => ({
|
||||||
|
users: many(user),
|
||||||
|
statuses: many(status),
|
||||||
|
emojis: many(emoji),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const emojiToStatusRelations = relations(emojiToStatus, ({ one }) => ({
|
||||||
|
emoji: one(emoji, {
|
||||||
|
fields: [emojiToStatus.a],
|
||||||
|
references: [emoji.id],
|
||||||
|
}),
|
||||||
|
status: one(status, {
|
||||||
|
fields: [emojiToStatus.b],
|
||||||
|
references: [status.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
20
index.ts
20
index.ts
|
|
@ -2,13 +2,15 @@ import { exists, mkdir, writeFile } from "node:fs/promises";
|
||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
import { connectMeili } from "@meilisearch";
|
import { connectMeili } from "@meilisearch";
|
||||||
import { moduleIsEntry } from "@module";
|
import { moduleIsEntry } from "@module";
|
||||||
import type { PrismaClientInitializationError } from "@prisma/client/runtime/library";
|
|
||||||
import { initializeRedisCache } from "@redis";
|
import { initializeRedisCache } from "@redis";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { LogLevel, LogManager, MultiLogManager } from "log-manager";
|
import { LogLevel, LogManager, MultiLogManager } from "log-manager";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import { createServer } from "~server";
|
import { createServer } from "~server";
|
||||||
|
import { db, client as pgClient } from "~drizzle/db";
|
||||||
|
import { status } from "~drizzle/schema";
|
||||||
|
import { count, sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
await pgClient.connect();
|
||||||
const timeAtStart = performance.now();
|
const timeAtStart = performance.now();
|
||||||
|
|
||||||
// Create requests file if it doesnt exist
|
// Create requests file if it doesnt exist
|
||||||
|
|
@ -43,16 +45,18 @@ if (config.meilisearch.enabled) {
|
||||||
await connectMeili(dualLogger);
|
await connectMeili(dualLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redisCache) {
|
|
||||||
client.$use(redisCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if database is reachable
|
// Check if database is reachable
|
||||||
let postCount = 0;
|
let postCount = 0;
|
||||||
try {
|
try {
|
||||||
postCount = await client.status.count();
|
postCount = (
|
||||||
|
await db
|
||||||
|
.select({
|
||||||
|
count: count(),
|
||||||
|
})
|
||||||
|
.from(status)
|
||||||
|
)[0].count;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as PrismaClientInitializationError;
|
const error = e as Error;
|
||||||
await logger.logError(LogLevel.CRITICAL, "Database", error);
|
await logger.logError(LogLevel.CRITICAL, "Database", error);
|
||||||
await consoleLogger.logError(LogLevel.CRITICAL, "Database", error);
|
await consoleLogger.logError(LogLevel.CRITICAL, "Database", error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@
|
||||||
"@fortawesome/free-solid-svg-icons",
|
"@fortawesome/free-solid-svg-icons",
|
||||||
"@prisma/client",
|
"@prisma/client",
|
||||||
"@prisma/engines",
|
"@prisma/engines",
|
||||||
|
"es5-ext",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"json-editor-vue",
|
"json-editor-vue",
|
||||||
"msgpackr-extract",
|
"msgpackr-extract",
|
||||||
|
|
@ -72,6 +73,7 @@
|
||||||
"@types/ioredis": "^5.0.0",
|
"@types/ioredis": "^5.0.0",
|
||||||
"@types/jsonld": "^1.5.13",
|
"@types/jsonld": "^1.5.13",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
|
"@types/pg": "^8.11.5",
|
||||||
"@typescript-eslint/eslint-plugin": "latest",
|
"@typescript-eslint/eslint-plugin": "latest",
|
||||||
"@unocss/cli": "latest",
|
"@unocss/cli": "latest",
|
||||||
"@unocss/transformer-directives": "^0.59.0",
|
"@unocss/transformer-directives": "^0.59.0",
|
||||||
|
|
@ -79,6 +81,7 @@
|
||||||
"@vueuse/head": "^2.0.0",
|
"@vueuse/head": "^2.0.0",
|
||||||
"activitypub-types": "^1.0.3",
|
"activitypub-types": "^1.0.3",
|
||||||
"bun-types": "latest",
|
"bun-types": "latest",
|
||||||
|
"drizzle-kit": "^0.20.14",
|
||||||
"shiki": "^1.2.4",
|
"shiki": "^1.2.4",
|
||||||
"typescript": "latest",
|
"typescript": "latest",
|
||||||
"unocss": "latest",
|
"unocss": "latest",
|
||||||
|
|
@ -103,6 +106,7 @@
|
||||||
"cli-parser": "workspace:*",
|
"cli-parser": "workspace:*",
|
||||||
"cli-table": "^0.3.11",
|
"cli-table": "^0.3.11",
|
||||||
"config-manager": "workspace:*",
|
"config-manager": "workspace:*",
|
||||||
|
"drizzle-orm": "^0.30.7",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
|
|
@ -123,6 +127,7 @@
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"next-route-matcher": "^1.0.1",
|
"next-route-matcher": "^1.0.1",
|
||||||
"oauth4webapi": "^2.4.0",
|
"oauth4webapi": "^2.4.0",
|
||||||
|
"pg": "^8.11.5",
|
||||||
"prisma": "^5.6.0",
|
"prisma": "^5.6.0",
|
||||||
"prisma-json-types-generator": "^3.0.4",
|
"prisma-json-types-generator": "^3.0.4",
|
||||||
"prisma-redis-middleware": "^4.8.0",
|
"prisma-redis-middleware": "^4.8.0",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { fetchTimeline } from "@timelines";
|
import { fetchTimeline } from "@timelines";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import {
|
import {
|
||||||
|
findManyStatuses,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
type StatusWithRelations,
|
type StatusWithRelations,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
|
import { findFirstUser } from "~database/entities/User";
|
||||||
import {
|
import {
|
||||||
statusAndUserRelations,
|
statusAndUserRelations,
|
||||||
userRelations,
|
userRelations,
|
||||||
|
|
@ -52,38 +55,30 @@ export default apiRoute<{
|
||||||
pinned,
|
pinned,
|
||||||
} = extraData.parsedRequest;
|
} = extraData.parsedRequest;
|
||||||
|
|
||||||
const user = await client.user.findUnique({
|
const user = await findFirstUser({
|
||||||
where: { id },
|
where: (user, { eq }) => eq(user.id, id),
|
||||||
include: userRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) return errorResponse("User not found", 404);
|
if (!user) return errorResponse("User not found", 404);
|
||||||
|
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
||||||
client.status,
|
findManyStatuses,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-ignore
|
||||||
authorId: id,
|
where: (status, { and, lt, gt, gte, eq, sql }) =>
|
||||||
reblogId: null,
|
and(
|
||||||
pinnedBy: {
|
max_id ? lt(status.id, max_id) : undefined,
|
||||||
some: {
|
since_id ? gte(status.id, since_id) : undefined,
|
||||||
id: user.id,
|
min_id ? gt(status.id, min_id) : undefined,
|
||||||
},
|
eq(status.authorId, id),
|
||||||
},
|
sql`EXISTS (SELECT 1 FROM _UserPinnedNotes WHERE _UserPinnedNotes.status_id = ${status.id} AND _UserPinnedNotes.user_id = ${user.id})`,
|
||||||
// If only_media is true, only return statuses with attachments
|
only_media
|
||||||
attachments: only_media ? { some: {} } : undefined,
|
? sql`EXISTS (SELECT 1 FROM "Attachment" WHERE "Attachment"."statusId" = ${status.id})`
|
||||||
id: {
|
: undefined,
|
||||||
lt: max_id,
|
),
|
||||||
gt: min_id,
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
gte: since_id,
|
orderBy: (status, { desc }) => desc(status.id),
|
||||||
},
|
|
||||||
},
|
|
||||||
include: statusAndUserRelations,
|
|
||||||
take: Number(limit),
|
|
||||||
orderBy: {
|
|
||||||
id: "desc",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
@ -100,22 +95,22 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
||||||
client.status,
|
findManyStatuses,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-ignore
|
||||||
authorId: id,
|
where: (status, { and, lt, gt, gte, eq, sql }) =>
|
||||||
reblogId: exclude_reblogs ? null : undefined,
|
and(
|
||||||
id: {
|
max_id ? lt(status.id, max_id) : undefined,
|
||||||
lt: max_id,
|
since_id ? gte(status.id, since_id) : undefined,
|
||||||
gt: min_id,
|
min_id ? gt(status.id, min_id) : undefined,
|
||||||
gte: since_id,
|
eq(status.authorId, id),
|
||||||
},
|
only_media
|
||||||
},
|
? sql`EXISTS (SELECT 1 FROM "Attachment" WHERE "Attachment"."statusId" = ${status.id})`
|
||||||
include: statusAndUserRelations,
|
: undefined,
|
||||||
take: Number(limit),
|
exclude_reblogs ? eq(status.reblogId, null) : undefined,
|
||||||
orderBy: {
|
),
|
||||||
id: "desc",
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
},
|
orderBy: (status, { desc }) => desc(status.id),
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
name: application.name,
|
name: application.name,
|
||||||
website: application.website,
|
website: application.website,
|
||||||
vapid_key: application.vapid_key,
|
vapid_key: application.vapidKey,
|
||||||
redirect_uris: application.redirect_uris,
|
redirect_uris: application.redirectUris,
|
||||||
scopes: application.scopes,
|
scopes: application.scopes,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { fetchTimeline } from "@timelines";
|
import { fetchTimeline } from "@timelines";
|
||||||
import { client } from "~database/datasource";
|
import {
|
||||||
import { userToAPI, type UserWithRelations } from "~database/entities/User";
|
findManyUsers,
|
||||||
import { userRelations } from "~database/entities/relations";
|
userToAPI,
|
||||||
|
type UserWithRelations,
|
||||||
|
} from "~database/entities/User";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -28,25 +30,22 @@ export default apiRoute<{
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const { max_id, since_id, limit = 40 } = extraData.parsedRequest;
|
const { max_id, since_id, min_id, limit = 40 } = extraData.parsedRequest;
|
||||||
|
|
||||||
const { objects: blocks, link } = await fetchTimeline<UserWithRelations>(
|
const { objects: blocks, link } = await fetchTimeline<UserWithRelations>(
|
||||||
client.user,
|
findManyUsers,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
relationshipSubjects: {
|
where: (subject, { lt, gte, gt, and, sql }) =>
|
||||||
some: {
|
and(
|
||||||
ownerId: user.id,
|
max_id ? lt(subject.id, max_id) : undefined,
|
||||||
blocking: true,
|
since_id ? gte(subject.id, since_id) : undefined,
|
||||||
},
|
min_id ? gt(subject.id, min_id) : undefined,
|
||||||
},
|
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${subject.id} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."blocking" = true)`,
|
||||||
id: {
|
),
|
||||||
lt: max_id,
|
limit: Number(limit),
|
||||||
gte: since_id,
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
},
|
orderBy: (subject, { desc }) => desc(subject.id),
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
take: Number(limit),
|
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { apiRoute, applyConfig } from "@api";
|
||||||
import { jsonResponse } from "@response";
|
import { jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { emojiToAPI } from "~database/entities/Emoji";
|
import { emojiToAPI } from "~database/entities/Emoji";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -16,9 +17,10 @@ export const meta = applyConfig({
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiRoute(async () => {
|
export default apiRoute(async () => {
|
||||||
const emojis = await client.emoji.findMany({
|
const emojis = await db.query.emoji.findMany({
|
||||||
where: {
|
where: (emoji, { isNull }) => isNull(emoji.instanceId),
|
||||||
instanceId: null,
|
with: {
|
||||||
|
instance: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { jsonResponse } from "@response";
|
import { jsonResponse } from "@response";
|
||||||
|
import { count, isNull } from "drizzle-orm";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { userToAPI } from "~database/entities/User";
|
import { userToAPI } from "~database/entities/User";
|
||||||
import { userRelations } from "~database/entities/relations";
|
import { userRelations } from "~database/entities/relations";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { status, user } from "~drizzle/schema";
|
||||||
import manifest from "~package.json";
|
import manifest from "~package.json";
|
||||||
import type { APIInstance } from "~types/entities/instance";
|
import type { APIInstance } from "~types/entities/instance";
|
||||||
|
|
||||||
|
|
@ -24,16 +27,23 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
// Get software version from package.json
|
// Get software version from package.json
|
||||||
const version = manifest.version;
|
const version = manifest.version;
|
||||||
|
|
||||||
const statusCount = await client.status.count({
|
const statusCount = (
|
||||||
where: {
|
await db
|
||||||
instanceId: null,
|
.select({
|
||||||
},
|
count: count(),
|
||||||
});
|
})
|
||||||
const userCount = await client.user.count({
|
.from(status)
|
||||||
where: {
|
.where(isNull(status.instanceId))
|
||||||
instanceId: null,
|
)[0].count;
|
||||||
},
|
|
||||||
});
|
const userCount = (
|
||||||
|
await db
|
||||||
|
.select({
|
||||||
|
count: count(),
|
||||||
|
})
|
||||||
|
.from(user)
|
||||||
|
.where(isNull(user.instanceId))
|
||||||
|
)[0].count;
|
||||||
|
|
||||||
// Get the first created admin user
|
// Get the first created admin user
|
||||||
const contactAccount = await client.user.findFirst({
|
const contactAccount = await client.user.findFirst({
|
||||||
|
|
@ -47,10 +57,6 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
include: userRelations,
|
include: userRelations,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!contactAccount) {
|
|
||||||
throw new Error("No admin user found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user that have posted once in the last 30 days
|
// Get user that have posted once in the last 30 days
|
||||||
const monthlyActiveUsers = await client.user.count({
|
const monthlyActiveUsers = await client.user.count({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -189,7 +195,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
},
|
},
|
||||||
vapid_public_key: "",
|
vapid_public_key: "",
|
||||||
},
|
},
|
||||||
contact_account: userToAPI(contactAccount),
|
// @ts-expect-error Sometimes there just isnt an admin
|
||||||
|
contact_account: contactAccount ? userToAPI(contactAccount) : undefined,
|
||||||
} satisfies APIInstance & {
|
} satisfies APIInstance & {
|
||||||
pleroma: object;
|
pleroma: object;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { fetchTimeline } from "@timelines";
|
import { fetchTimeline } from "@timelines";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { userToAPI, type UserWithRelations } from "~database/entities/User";
|
import {
|
||||||
|
findManyUsers,
|
||||||
|
userToAPI,
|
||||||
|
type UserWithRelations,
|
||||||
|
} from "~database/entities/User";
|
||||||
import { userRelations } from "~database/entities/relations";
|
import { userRelations } from "~database/entities/relations";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -21,30 +25,28 @@ export const meta = applyConfig({
|
||||||
export default apiRoute<{
|
export default apiRoute<{
|
||||||
max_id?: string;
|
max_id?: string;
|
||||||
since_id?: string;
|
since_id?: string;
|
||||||
|
min_id?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}>(async (req, matchedRoute, extraData) => {
|
}>(async (req, matchedRoute, extraData) => {
|
||||||
const { user } = extraData.auth;
|
const { user } = extraData.auth;
|
||||||
const { max_id, since_id, limit = 40 } = extraData.parsedRequest;
|
const { max_id, since_id, limit = 40, min_id } = extraData.parsedRequest;
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const { objects: blocks, link } = await fetchTimeline<UserWithRelations>(
|
const { objects: blocks, link } = await fetchTimeline<UserWithRelations>(
|
||||||
client.user,
|
findManyUsers,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
relationshipSubjects: {
|
where: (subject, { lt, gte, gt, and, sql }) =>
|
||||||
some: {
|
and(
|
||||||
ownerId: user.id,
|
max_id ? lt(subject.id, max_id) : undefined,
|
||||||
muting: true,
|
since_id ? gte(subject.id, since_id) : undefined,
|
||||||
},
|
min_id ? gt(subject.id, min_id) : undefined,
|
||||||
},
|
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${subject.id} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."muting" = true)`,
|
||||||
id: {
|
),
|
||||||
lt: max_id,
|
limit: Number(limit),
|
||||||
gte: since_id,
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
},
|
orderBy: (subject, { desc }) => desc(subject.id),
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
take: Number(limit),
|
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import type { Relationship } from "~database/entities/Relationship";
|
||||||
import {
|
import {
|
||||||
|
findFirstStatuses,
|
||||||
getAncestors,
|
getAncestors,
|
||||||
getDescendants,
|
getDescendants,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -30,16 +31,47 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
const { user } = extraData.auth;
|
const { user } = extraData.auth;
|
||||||
|
|
||||||
const foundStatus = await client.status.findUnique({
|
const foundStatus = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!foundStatus) return errorResponse("Record not found", 404);
|
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
|
const relations = user
|
||||||
|
? await db.query.relationship.findMany({
|
||||||
|
where: (relationship, { eq }) =>
|
||||||
|
eq(relationship.ownerId, user.id),
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const relationSubjects = user
|
||||||
|
? await db.query.relationship.findMany({
|
||||||
|
where: (relationship, { eq }) =>
|
||||||
|
eq(relationship.subjectId, user.id),
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
// Get all ancestors
|
// Get all ancestors
|
||||||
const ancestors = await getAncestors(foundStatus, user);
|
const ancestors = await getAncestors(
|
||||||
const descendants = await getDescendants(foundStatus, user);
|
foundStatus,
|
||||||
|
user
|
||||||
|
? {
|
||||||
|
...user,
|
||||||
|
relationships: relations as Relationship[],
|
||||||
|
relationshipSubjects: relationSubjects as Relationship[],
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
const descendants = await getDescendants(
|
||||||
|
foundStatus,
|
||||||
|
user
|
||||||
|
? {
|
||||||
|
...user,
|
||||||
|
relationships: relations as Relationship[],
|
||||||
|
relationshipSubjects: relationSubjects as Relationship[],
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
ancestors: await Promise.all(
|
ancestors: await Promise.all(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { createLike } from "~database/entities/Like";
|
import { createLike } from "~database/entities/Like";
|
||||||
import { isViewableByUser, statusToAPI } from "~database/entities/Status";
|
import {
|
||||||
|
findFirstStatuses,
|
||||||
|
isViewableByUser,
|
||||||
|
statusToAPI,
|
||||||
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { statusAndUserRelations } from "~database/entities/relations";
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import type { APIStatus } from "~types/entities/status";
|
||||||
|
|
||||||
|
|
@ -28,9 +32,8 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const status = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
|
|
@ -51,6 +54,6 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
...(await statusToAPI(status, user)),
|
...(await statusToAPI(status, user)),
|
||||||
favourited: true,
|
favourited: true,
|
||||||
favourites_count: status._count.likes + 1,
|
favourites_count: status.likeCount + 1,
|
||||||
} as APIStatus);
|
} as APIStatus);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { fetchTimeline } from "@timelines";
|
import { fetchTimeline } from "@timelines";
|
||||||
import { client } from "~database/datasource";
|
import { findFirstStatuses, isViewableByUser } from "~database/entities/Status";
|
||||||
import { isViewableByUser } from "~database/entities/Status";
|
|
||||||
import { userToAPI, type UserWithRelations } from "~database/entities/User";
|
|
||||||
import {
|
import {
|
||||||
statusAndUserRelations,
|
findManyUsers,
|
||||||
userRelations,
|
userToAPI,
|
||||||
} from "~database/entities/relations";
|
type UserWithRelations,
|
||||||
|
} from "~database/entities/User";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -34,9 +33,8 @@ export default apiRoute<{
|
||||||
|
|
||||||
const { user } = extraData.auth;
|
const { user } = extraData.auth;
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const status = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
|
|
@ -50,32 +48,18 @@ export default apiRoute<{
|
||||||
if (limit < 1) return errorResponse("Invalid limit", 400);
|
if (limit < 1) return errorResponse("Invalid limit", 400);
|
||||||
|
|
||||||
const { objects, link } = await fetchTimeline<UserWithRelations>(
|
const { objects, link } = await fetchTimeline<UserWithRelations>(
|
||||||
client.user,
|
findManyUsers,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-ignore
|
||||||
likes: {
|
where: (liker, { and, lt, gt, gte, eq, sql }) =>
|
||||||
some: {
|
and(
|
||||||
likedId: status.id,
|
max_id ? lt(liker.id, max_id) : undefined,
|
||||||
},
|
since_id ? gte(liker.id, since_id) : undefined,
|
||||||
},
|
min_id ? gt(liker.id, min_id) : undefined,
|
||||||
id: {
|
sql`EXISTS (SELECT 1 FROM "Like" WHERE "Like"."likedId" = ${status.id} AND "Like"."likerId" = ${liker.id})`,
|
||||||
lt: max_id,
|
),
|
||||||
gte: since_id,
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
gt: min_id,
|
orderBy: (liker, { desc }) => desc(liker.id),
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
...userRelations,
|
|
||||||
likes: {
|
|
||||||
where: {
|
|
||||||
likedId: status.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
take: Number(limit),
|
|
||||||
orderBy: {
|
|
||||||
id: "desc",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { sanitizeHtml } from "@sanitization";
|
import { sanitizeHtml } from "@sanitization";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { parse } from "marked";
|
import { parse } from "marked";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import {
|
import {
|
||||||
editStatus,
|
editStatus,
|
||||||
|
findFirstStatuses,
|
||||||
isViewableByUser,
|
isViewableByUser,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { statusAndUserRelations } from "~database/entities/relations";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { status } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET", "DELETE", "PUT"],
|
allowedMethods: ["GET", "DELETE", "PUT"],
|
||||||
|
|
@ -42,37 +45,32 @@ export default apiRoute<{
|
||||||
|
|
||||||
const { user } = extraData.auth;
|
const { user } = extraData.auth;
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const foundStatus = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const config = await extraData.configManager.getConfig();
|
const config = await extraData.configManager.getConfig();
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
if (!status || !isViewableByUser(status, user))
|
if (!foundStatus || !isViewableByUser(foundStatus, user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
return jsonResponse(await statusToAPI(status));
|
return jsonResponse(await statusToAPI(foundStatus));
|
||||||
}
|
}
|
||||||
if (req.method === "DELETE") {
|
if (req.method === "DELETE") {
|
||||||
if (status.authorId !== user?.id) {
|
if (foundStatus.authorId !== user?.id) {
|
||||||
return errorResponse("Unauthorized", 401);
|
return errorResponse("Unauthorized", 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement delete and redraft functionality
|
// TODO: Implement delete and redraft functionality
|
||||||
|
|
||||||
// Get associated Status object
|
|
||||||
|
|
||||||
// Delete status and all associated objects
|
// Delete status and all associated objects
|
||||||
await client.status.delete({
|
await db.delete(status).where(eq(status.id, id));
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
{
|
{
|
||||||
...(await statusToAPI(status, user)),
|
...(await statusToAPI(foundStatus, user)),
|
||||||
// TODO: Add
|
// TODO: Add
|
||||||
// text: Add source text
|
// text: Add source text
|
||||||
// poll: Add source poll
|
// poll: Add source poll
|
||||||
|
|
@ -82,7 +80,7 @@ export default apiRoute<{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (req.method === "PUT") {
|
if (req.method === "PUT") {
|
||||||
if (status.authorId !== user?.id) {
|
if (foundStatus.authorId !== user?.id) {
|
||||||
return errorResponse("Unauthorized", 401);
|
return errorResponse("Unauthorized", 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,21 +189,19 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if media attachments are all valid
|
// Check if media attachments are all valid
|
||||||
|
if (media_ids && media_ids.length > 0) {
|
||||||
const foundAttachments = await client.attachment.findMany({
|
const foundAttachments = await db.query.attachment.findMany({
|
||||||
where: {
|
where: (attachment, { inArray }) =>
|
||||||
id: {
|
inArray(attachment.id, media_ids),
|
||||||
in: media_ids ?? [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (foundAttachments.length !== (media_ids ?? []).length) {
|
if (foundAttachments.length !== (media_ids ?? []).length) {
|
||||||
return errorResponse("Invalid media IDs", 422);
|
return errorResponse("Invalid media IDs", 422);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update status
|
// Update status
|
||||||
const newStatus = await editStatus(status, {
|
const newStatus = await editStatus(foundStatus, {
|
||||||
content: sanitizedStatus,
|
content: sanitizedStatus,
|
||||||
content_type,
|
content_type,
|
||||||
media_attachments: media_ids,
|
media_attachments: media_ids,
|
||||||
|
|
@ -213,6 +209,10 @@ export default apiRoute<{
|
||||||
sensitive: sensitive ?? false,
|
sensitive: sensitive ?? false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!newStatus) {
|
||||||
|
return errorResponse("Failed to update status", 500);
|
||||||
|
}
|
||||||
|
|
||||||
return jsonResponse(await statusToAPI(newStatus, user));
|
return jsonResponse(await statusToAPI(newStatus, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { statusToAPI } from "~database/entities/Status";
|
import { findFirstStatuses, statusToAPI } from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { statusAndUserRelations } from "~database/entities/relations";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { statusToUser } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["POST"],
|
allowedMethods: ["POST"],
|
||||||
|
|
@ -26,34 +28,34 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
let status = await client.status.findUnique({
|
const foundStatus = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if status exists
|
// Check if status exists
|
||||||
if (!status) return errorResponse("Record not found", 404);
|
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
// Check if status is user's
|
// Check if status is user's
|
||||||
if (status.authorId !== user.id) return errorResponse("Unauthorized", 401);
|
if (foundStatus.authorId !== user.id)
|
||||||
|
return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
await client.user.update({
|
// Check if post is already pinned
|
||||||
where: { id: user.id },
|
if (
|
||||||
data: {
|
await db.query.statusToUser.findFirst({
|
||||||
pinnedNotes: {
|
where: (statusToUser, { and, eq }) =>
|
||||||
connect: {
|
and(
|
||||||
id: status.id,
|
eq(statusToUser.a, foundStatus.id),
|
||||||
},
|
eq(statusToUser.b, user.id),
|
||||||
},
|
),
|
||||||
},
|
})
|
||||||
|
) {
|
||||||
|
return errorResponse("Already pinned", 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.insert(statusToUser).values({
|
||||||
|
a: foundStatus.id,
|
||||||
|
b: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
status = await client.status.findUnique({
|
return jsonResponse(statusToAPI(foundStatus, user));
|
||||||
where: { id },
|
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!status) return errorResponse("Record not found", 404);
|
|
||||||
|
|
||||||
return jsonResponse(statusToAPI(status, user));
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import {
|
||||||
import { isViewableByUser, statusToAPI } from "~database/entities/Status";
|
findFirstStatuses,
|
||||||
import type { UserWithRelations } from "~database/entities/User";
|
isViewableByUser,
|
||||||
|
statusToAPI,
|
||||||
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { statusAndUserRelations } from "~database/entities/relations";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { notification, status } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["POST"],
|
allowedMethods: ["POST"],
|
||||||
|
|
@ -31,47 +35,57 @@ export default apiRoute<{
|
||||||
|
|
||||||
const { visibility = "public" } = extraData.parsedRequest;
|
const { visibility = "public" } = extraData.parsedRequest;
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const foundStatus = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
if (!status || !isViewableByUser(status, user))
|
if (!foundStatus || !isViewableByUser(foundStatus, user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
const existingReblog = await client.status.findFirst({
|
const existingReblog = await db.query.status.findFirst({
|
||||||
where: {
|
where: (status, { and, eq }) =>
|
||||||
authorId: user.id,
|
and(eq(status.authorId, user.id), eq(status.reblogId, status.id)),
|
||||||
reblogId: status.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingReblog) {
|
if (existingReblog) {
|
||||||
return errorResponse("Already reblogged", 422);
|
return errorResponse("Already reblogged", 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newReblog = await client.status.create({
|
const newReblog = (
|
||||||
data: {
|
await db
|
||||||
|
.insert(status)
|
||||||
|
.values({
|
||||||
authorId: user.id,
|
authorId: user.id,
|
||||||
reblogId: status.id,
|
reblogId: foundStatus.id,
|
||||||
visibility,
|
visibility,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
},
|
updatedAt: new Date().toISOString(),
|
||||||
include: statusAndUserRelations,
|
})
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (!newReblog) {
|
||||||
|
return errorResponse("Failed to reblog", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalNewReblog = await findFirstStatuses({
|
||||||
|
where: (status, { eq }) => eq(status.id, newReblog.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!finalNewReblog) {
|
||||||
|
return errorResponse("Failed to reblog", 500);
|
||||||
|
}
|
||||||
|
|
||||||
// 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 (status.author.instanceId === user.instanceId) {
|
if (foundStatus.author.instanceId === user.instanceId) {
|
||||||
await client.notification.create({
|
await db.insert(notification).values({
|
||||||
data: {
|
|
||||||
accountId: user.id,
|
accountId: user.id,
|
||||||
notifiedId: status.authorId,
|
notifiedId: foundStatus.authorId,
|
||||||
type: "reblog",
|
type: "reblog",
|
||||||
statusId: status.reblogId,
|
statusId: foundStatus.reblogId,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(await statusToAPI(newReblog, user));
|
return jsonResponse(await statusToAPI(finalNewReblog, user));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { fetchTimeline } from "@timelines";
|
import { fetchTimeline } from "@timelines";
|
||||||
import { client } from "~database/datasource";
|
import { findFirstStatuses, isViewableByUser } from "~database/entities/Status";
|
||||||
import { isViewableByUser } from "~database/entities/Status";
|
|
||||||
import { type UserWithRelations, userToAPI } from "~database/entities/User";
|
|
||||||
import {
|
import {
|
||||||
statusAndUserRelations,
|
type UserWithRelations,
|
||||||
userRelations,
|
userToAPI,
|
||||||
} from "~database/entities/relations";
|
findManyUsers,
|
||||||
|
} from "~database/entities/User";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -34,9 +33,8 @@ export default apiRoute<{
|
||||||
|
|
||||||
const { user } = extraData.auth;
|
const { user } = extraData.auth;
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const status = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
|
|
@ -55,33 +53,18 @@ export default apiRoute<{
|
||||||
if (limit < 1) return errorResponse("Invalid limit", 400);
|
if (limit < 1) return errorResponse("Invalid limit", 400);
|
||||||
|
|
||||||
const { objects, link } = await fetchTimeline<UserWithRelations>(
|
const { objects, link } = await fetchTimeline<UserWithRelations>(
|
||||||
client.user,
|
findManyUsers,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-ignore
|
||||||
statuses: {
|
where: (reblogger, { and, lt, gt, gte, eq, sql }) =>
|
||||||
some: {
|
and(
|
||||||
reblogId: status.id,
|
max_id ? lt(reblogger.id, max_id) : undefined,
|
||||||
},
|
since_id ? gte(reblogger.id, since_id) : undefined,
|
||||||
},
|
min_id ? gt(reblogger.id, min_id) : undefined,
|
||||||
id: {
|
sql`EXISTS (SELECT 1 FROM "Status" WHERE "Status"."reblogId" = ${status.id} AND "Status"."authorId" = ${reblogger.id})`,
|
||||||
lt: max_id ?? undefined,
|
),
|
||||||
gte: since_id ?? undefined,
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
gt: min_id ?? undefined,
|
orderBy: (liker, { desc }) => desc(liker.id),
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
...userRelations,
|
|
||||||
statuses: {
|
|
||||||
where: {
|
|
||||||
reblogId: status.id,
|
|
||||||
},
|
|
||||||
include: statusAndUserRelations,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
take: Number(limit),
|
|
||||||
orderBy: {
|
|
||||||
id: "desc",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { deleteLike } from "~database/entities/Like";
|
import { deleteLike } from "~database/entities/Like";
|
||||||
import { isViewableByUser, statusToAPI } from "~database/entities/Status";
|
import {
|
||||||
|
findFirstStatuses,
|
||||||
|
isViewableByUser,
|
||||||
|
statusToAPI,
|
||||||
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { statusAndUserRelations } from "~database/entities/relations";
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import type { APIStatus } from "~types/entities/status";
|
||||||
|
|
||||||
|
|
@ -28,20 +32,19 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const foundStatus = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
if (!status || !isViewableByUser(status, user))
|
if (!foundStatus || !isViewableByUser(foundStatus, user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
await deleteLike(user, status);
|
await deleteLike(user, foundStatus);
|
||||||
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
...(await statusToAPI(status, user)),
|
...(await statusToAPI(foundStatus, user)),
|
||||||
favourited: false,
|
favourited: false,
|
||||||
favourites_count: status._count.likes - 1,
|
favourites_count: foundStatus.likeCount - 1,
|
||||||
} as APIStatus);
|
} as APIStatus);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { eq } from "drizzle-orm";
|
||||||
import { isViewableByUser, statusToAPI } from "~database/entities/Status";
|
import {
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
findFirstStatuses,
|
||||||
|
isViewableByUser,
|
||||||
|
statusToAPI,
|
||||||
|
} from "~database/entities/Status";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { status } from "~drizzle/schema";
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import type { APIStatus } from "~types/entities/status";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -27,33 +32,28 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
const status = await client.status.findUnique({
|
const foundStatus = await findFirstStatuses({
|
||||||
where: { id },
|
where: (status, { eq }) => eq(status.id, id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
if (!status || !isViewableByUser(status, user))
|
if (!foundStatus || !isViewableByUser(foundStatus, user))
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
const existingReblog = await client.status.findFirst({
|
const existingReblog = await findFirstStatuses({
|
||||||
where: {
|
where: (status, { eq }) =>
|
||||||
authorId: user.id,
|
eq(status.authorId, user.id) && eq(status.reblogId, foundStatus.id),
|
||||||
reblogId: status.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingReblog) {
|
if (!existingReblog) {
|
||||||
return errorResponse("Not already reblogged", 422);
|
return errorResponse("Not already reblogged", 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.status.delete({
|
await db.delete(status).where(eq(status.id, existingReblog.id));
|
||||||
where: { id: existingReblog.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
return jsonResponse({
|
return jsonResponse({
|
||||||
...(await statusToAPI(status, user)),
|
...(await statusToAPI(foundStatus, user)),
|
||||||
reblogged: false,
|
reblogged: false,
|
||||||
reblogs_count: status._count.reblogs - 1,
|
reblogs_count: foundStatus.reblogCount - 1,
|
||||||
} as APIStatus);
|
} as APIStatus);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import type { StatusWithRelations } from "~database/entities/Status";
|
||||||
import {
|
import {
|
||||||
createNewStatus,
|
createNewStatus,
|
||||||
federateStatus,
|
federateStatus,
|
||||||
|
findFirstStatuses,
|
||||||
parseTextMentions,
|
parseTextMentions,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
|
|
@ -46,9 +47,9 @@ export default apiRoute<{
|
||||||
scheduled_at?: string;
|
scheduled_at?: string;
|
||||||
local_only?: boolean;
|
local_only?: boolean;
|
||||||
content_type?: string;
|
content_type?: string;
|
||||||
|
federate?: boolean;
|
||||||
}>(async (req, matchedRoute, extraData) => {
|
}>(async (req, matchedRoute, extraData) => {
|
||||||
const { user, token } = extraData.auth;
|
const { user, token } = extraData.auth;
|
||||||
const application = await getFromToken(token);
|
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
|
|
@ -69,6 +70,7 @@ export default apiRoute<{
|
||||||
spoiler_text,
|
spoiler_text,
|
||||||
visibility,
|
visibility,
|
||||||
content_type,
|
content_type,
|
||||||
|
federate = true,
|
||||||
} = extraData.parsedRequest;
|
} = extraData.parsedRequest;
|
||||||
|
|
||||||
// Validate status
|
// Validate status
|
||||||
|
|
@ -173,9 +175,8 @@ export default apiRoute<{
|
||||||
let quote: StatusWithRelations | null = null;
|
let quote: StatusWithRelations | null = null;
|
||||||
|
|
||||||
if (in_reply_to_id) {
|
if (in_reply_to_id) {
|
||||||
replyStatus = await client.status.findUnique({
|
replyStatus = await findFirstStatuses({
|
||||||
where: { id: in_reply_to_id },
|
where: (status, { eq }) => eq(status.id, in_reply_to_id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!replyStatus) {
|
if (!replyStatus) {
|
||||||
|
|
@ -184,9 +185,8 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quote_id) {
|
if (quote_id) {
|
||||||
quote = await client.status.findUnique({
|
quote = await findFirstStatuses({
|
||||||
where: { id: quote_id },
|
where: (status, { eq }) => eq(status.id, quote_id),
|
||||||
include: statusAndUserRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!quote) {
|
if (!quote) {
|
||||||
|
|
@ -233,7 +233,13 @@ export default apiRoute<{
|
||||||
quote ?? undefined,
|
quote ?? undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!newStatus) {
|
||||||
|
return errorResponse("Failed to create status", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (federate) {
|
||||||
await federateStatus(newStatus);
|
await federateStatus(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
return jsonResponse(await statusToAPI(newStatus, user));
|
return jsonResponse(await statusToAPI(newStatus, user));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,10 @@ import { client } from "~database/datasource";
|
||||||
import {
|
import {
|
||||||
type StatusWithRelations,
|
type StatusWithRelations,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
|
findManyStatuses,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { statusAndUserRelations } from "~database/entities/relations";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -39,48 +41,40 @@ export default apiRoute<{
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
|
const followers = await db.query.relationship.findMany({
|
||||||
|
where: (relationship, { eq, and }) =>
|
||||||
|
and(
|
||||||
|
eq(relationship.subjectId, user.id),
|
||||||
|
eq(relationship.following, true),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
||||||
client.status,
|
findManyStatuses,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
id: {
|
where: (status, { lt, gte, gt, and, or, eq, inArray, sql }) =>
|
||||||
lt: max_id ?? undefined,
|
or(
|
||||||
gte: since_id ?? undefined,
|
and(
|
||||||
gt: min_id ?? undefined,
|
max_id ? lt(status.id, max_id) : undefined,
|
||||||
},
|
since_id ? gte(status.id, since_id) : undefined,
|
||||||
OR: [
|
min_id ? gt(status.id, min_id) : undefined,
|
||||||
{
|
),
|
||||||
author: {
|
eq(status.authorId, user.id),
|
||||||
OR: [
|
/* inArray(
|
||||||
{
|
status.authorId,
|
||||||
relationshipSubjects: {
|
followers.map((f) => f.ownerId),
|
||||||
some: {
|
), */
|
||||||
ownerId: user.id,
|
// All statuses where the user is mentioned, using table StatusToUser which has a: status.id and b: user.id
|
||||||
following: true,
|
// WHERE format (... = ...)
|
||||||
},
|
sql`EXISTS (SELECT 1 FROM "StatusToUser" WHERE "StatusToUser"."a" = ${status.id} AND "StatusToUser"."b" = ${user.id})`,
|
||||||
},
|
// All statuses from users that the user is following
|
||||||
},
|
// WHERE format (... = ...)
|
||||||
{
|
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${status.authorId} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."following" = true)`,
|
||||||
id: user.id,
|
),
|
||||||
},
|
limit: Number(limit),
|
||||||
],
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
},
|
orderBy: (status, { desc }) => desc(status.id),
|
||||||
},
|
|
||||||
{
|
|
||||||
// Include posts where the user is mentioned in addition to posts by followed users
|
|
||||||
mentions: {
|
|
||||||
some: {
|
|
||||||
id: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
include: statusAndUserRelations,
|
|
||||||
take: Number(limit),
|
|
||||||
orderBy: {
|
|
||||||
id: "desc",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { errorResponse, jsonResponse } from "@response";
|
||||||
import { fetchTimeline } from "@timelines";
|
import { fetchTimeline } from "@timelines";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import {
|
import {
|
||||||
|
findManyStatuses,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
type StatusWithRelations,
|
type StatusWithRelations,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
|
|
@ -49,27 +50,23 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
const { objects, link } = await fetchTimeline<StatusWithRelations>(
|
||||||
client.status,
|
findManyStatuses,
|
||||||
{
|
{
|
||||||
where: {
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
id: {
|
where: (status, { lt, gte, gt, and, isNull, isNotNull }) =>
|
||||||
lt: max_id ?? undefined,
|
and(
|
||||||
gte: since_id ?? undefined,
|
max_id ? lt(status.id, max_id) : undefined,
|
||||||
gt: min_id ?? undefined,
|
since_id ? gte(status.id, since_id) : undefined,
|
||||||
},
|
min_id ? gt(status.id, min_id) : undefined,
|
||||||
instanceId: remote
|
remote
|
||||||
? {
|
? isNotNull(status.instanceId)
|
||||||
not: null,
|
|
||||||
}
|
|
||||||
: local
|
: local
|
||||||
? null
|
? isNull(status.instanceId)
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
),
|
||||||
include: statusAndUserRelations,
|
limit: Number(limit),
|
||||||
take: Number(limit),
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
orderBy: {
|
orderBy: (status, { desc }) => desc(status.id),
|
||||||
id: "desc",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ import { encode } from "blurhash";
|
||||||
import type { MediaBackend } from "media-manager";
|
import type { MediaBackend } from "media-manager";
|
||||||
import { MediaBackendType } from "media-manager";
|
import { MediaBackendType } from "media-manager";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
|
import { attachmentToAPI, getUrl } from "~database/entities/Attachment";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { attachment } from "~drizzle/schema";
|
||||||
import { LocalMediaBackend, S3MediaBackend } from "~packages/media-manager";
|
import { LocalMediaBackend, S3MediaBackend } from "~packages/media-manager";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -134,19 +135,22 @@ export default apiRoute<{
|
||||||
thumbnailUrl = getUrl(path, config);
|
thumbnailUrl = getUrl(path, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAttachment = await client.attachment.create({
|
const newAttachment = (
|
||||||
data: {
|
await db
|
||||||
|
.insert(attachment)
|
||||||
|
.values({
|
||||||
url,
|
url,
|
||||||
thumbnail_url: thumbnailUrl,
|
thumbnailUrl,
|
||||||
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
||||||
mime_type: file.type,
|
mimeType: file.type,
|
||||||
description: description ?? "",
|
description: description ?? "",
|
||||||
size: file.size,
|
size: file.size,
|
||||||
blurhash: blurhash ?? undefined,
|
blurhash: blurhash ?? undefined,
|
||||||
width: metadata?.width ?? undefined,
|
width: metadata?.width ?? undefined,
|
||||||
height: metadata?.height ?? undefined,
|
height: metadata?.height ?? undefined,
|
||||||
},
|
})
|
||||||
});
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
// TODO: Add job to process videos and other media
|
// TODO: Add job to process videos and other media
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,34 @@ import {
|
||||||
import type { APIEmoji } from "~types/entities/emoji";
|
import type { APIEmoji } from "~types/entities/emoji";
|
||||||
import type { APIInstance } from "~types/entities/instance";
|
import type { APIInstance } from "~types/entities/instance";
|
||||||
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { inArray } from "drizzle-orm";
|
||||||
|
import { application, user } from "~drizzle/schema";
|
||||||
|
|
||||||
const base_url = config.http.base_url;
|
const base_url = config.http.base_url;
|
||||||
|
|
||||||
let token: Token;
|
let token: Token;
|
||||||
let user: UserWithRelations;
|
let dummyUser: UserWithRelations;
|
||||||
|
|
||||||
describe("API Tests", () => {
|
describe("API Tests", () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
await db.delete(user).where(inArray(user.username, ["test", "test2"]));
|
||||||
|
await db
|
||||||
|
.delete(application)
|
||||||
|
.where(inArray(application.clientId, ["test"]));
|
||||||
|
|
||||||
// Initialize test user
|
// Initialize test user
|
||||||
user = await createNewLocalUser({
|
dummyUser = await createNewLocalUser({
|
||||||
email: "test@test.com",
|
email: "test@test.com",
|
||||||
username: "test",
|
username: "test",
|
||||||
password: "test",
|
password: "test",
|
||||||
display_name: "",
|
display_name: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!dummyUser) {
|
||||||
|
throw new Error("Failed to create test user");
|
||||||
|
}
|
||||||
|
|
||||||
token = await client.token.create({
|
token = await client.token.create({
|
||||||
data: {
|
data: {
|
||||||
access_token: "test",
|
access_token: "test",
|
||||||
|
|
@ -45,7 +57,7 @@ describe("API Tests", () => {
|
||||||
token_type: TokenType.BEARER,
|
token_type: TokenType.BEARER,
|
||||||
user: {
|
user: {
|
||||||
connect: {
|
connect: {
|
||||||
id: user.id,
|
id: dummyUser.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -53,19 +65,10 @@ describe("API Tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await client.user.deleteMany({
|
await db.delete(user).where(inArray(user.username, ["test", "test2"]));
|
||||||
where: {
|
await db
|
||||||
username: {
|
.delete(application)
|
||||||
in: ["test", "test2"],
|
.where(inArray(application.clientId, ["test"]));
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.application.deleteMany({
|
|
||||||
where: {
|
|
||||||
client_id: "test",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("GET /api/v1/instance", () => {
|
describe("GET /api/v1/instance", () => {
|
||||||
|
|
@ -89,7 +92,7 @@ describe("API Tests", () => {
|
||||||
|
|
||||||
const instance = (await response.json()) as APIInstance;
|
const instance = (await response.json()) as APIInstance;
|
||||||
|
|
||||||
expect(instance.uri).toBe(new URL(config.http.base_url).hostname);
|
expect(instance.uri).toBe(config.http.base_url);
|
||||||
expect(instance.title).toBeDefined();
|
expect(instance.title).toBeDefined();
|
||||||
expect(instance.description).toBeDefined();
|
expect(instance.description).toBeDefined();
|
||||||
expect(instance.email).toBeDefined();
|
expect(instance.email).toBeDefined();
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ describe("API Tests", () => {
|
||||||
status: "Hello, world!",
|
status: "Hello, world!",
|
||||||
visibility: "public",
|
visibility: "public",
|
||||||
media_ids: [media1?.id],
|
media_ids: [media1?.id],
|
||||||
|
federate: false,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -173,6 +174,7 @@ describe("API Tests", () => {
|
||||||
status: "This is a reply!",
|
status: "This is a reply!",
|
||||||
visibility: "public",
|
visibility: "public",
|
||||||
in_reply_to_id: status?.id,
|
in_reply_to_id: status?.id,
|
||||||
|
federate: false,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { server } from "~index";
|
// import { server } from "~index";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This allows us to send a test request to the server even when it isnt running
|
* This allows us to send a test request to the server even when it isnt running
|
||||||
|
|
@ -7,7 +7,9 @@ import { server } from "~index";
|
||||||
* @returns Response from the server
|
* @returns Response from the server
|
||||||
*/
|
*/
|
||||||
export async function sendTestRequest(req: Request) {
|
export async function sendTestRequest(req: Request) {
|
||||||
return server.fetch(req);
|
console.log(req);
|
||||||
|
return fetch(req);
|
||||||
|
// return server.fetch(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapRelativeUrl(url: string, base_url: string) {
|
export function wrapRelativeUrl(url: string, base_url: string) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import type { Status, User } from "@prisma/client";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { LogLevel, type LogManager, type MultiLogManager } from "log-manager";
|
import { LogLevel, type LogManager, type MultiLogManager } from "log-manager";
|
||||||
import { Meilisearch } from "meilisearch";
|
import { Meilisearch } from "meilisearch";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
|
import type { Status } from "~database/entities/Status";
|
||||||
|
import type { User } from "~database/entities/User";
|
||||||
|
|
||||||
export const meilisearch = new Meilisearch({
|
export const meilisearch = new Meilisearch({
|
||||||
host: `${config.meilisearch.host}:${config.meilisearch.port}`,
|
host: `${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
import type { Status, User, Prisma, Notification } from "@prisma/client";
|
import type { findManyStatuses, Status } from "~database/entities/Status";
|
||||||
|
import type { findManyUsers, User } from "~database/entities/User";
|
||||||
|
import type { Notification } from "~database/entities/Notification";
|
||||||
|
import type { db } from "~drizzle/db";
|
||||||
|
|
||||||
export async function fetchTimeline<T extends User | Status | Notification>(
|
export async function fetchTimeline<T extends User | Status | Notification>(
|
||||||
model:
|
model:
|
||||||
| Prisma.StatusDelegate
|
| typeof findManyStatuses
|
||||||
| Prisma.UserDelegate
|
| typeof findManyUsers
|
||||||
| Prisma.NotificationDelegate,
|
| typeof db.query.notification.findMany,
|
||||||
args:
|
args:
|
||||||
| Prisma.StatusFindManyArgs
|
| Parameters<typeof findManyStatuses>[0]
|
||||||
| Prisma.UserFindManyArgs
|
| Parameters<typeof findManyUsers>[0]
|
||||||
| Prisma.NotificationFindManyArgs,
|
| Parameters<typeof db.query.notification.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
|
||||||
// AFTER: After in a top-to-bottom order, so the oldest posts
|
// AFTER: After in a top-to-bottom order, so the oldest posts
|
||||||
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
|
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
|
||||||
const objects = (await model.findMany(args)) as T[];
|
const objects = (await model(args)) as T[];
|
||||||
|
|
||||||
// Constuct HTTP Link header (next and prev) only if there are more statuses
|
// Constuct HTTP Link header (next and prev) only if there are more statuses
|
||||||
const linkHeader = [];
|
const linkHeader = [];
|
||||||
|
|
@ -22,15 +25,11 @@ export async function fetchTimeline<T extends User | Status | Notification>(
|
||||||
if (objects.length > 0) {
|
if (objects.length > 0) {
|
||||||
// Check if there are statuses before the first one
|
// Check if there are statuses before the first one
|
||||||
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
|
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
|
||||||
const objectsBefore = await model.findMany({
|
const objectsBefore = await model({
|
||||||
...args,
|
...args,
|
||||||
where: {
|
// @ts-expect-error this hack breaks typing :(
|
||||||
...args.where,
|
where: (object, { gt }) => gt(object.id, objects[0].id),
|
||||||
id: {
|
limit: 1,
|
||||||
gt: objects[0].id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (objectsBefore.length > 0) {
|
if (objectsBefore.length > 0) {
|
||||||
|
|
@ -41,18 +40,16 @@ export async function fetchTimeline<T extends User | Status | Notification>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objects.length < (args.take ?? Number.POSITIVE_INFINITY)) {
|
if (
|
||||||
|
objects.length < (Number(args?.limit) ?? Number.POSITIVE_INFINITY)
|
||||||
|
) {
|
||||||
// Check if there are statuses after the last one
|
// Check if there are statuses after the last one
|
||||||
// @ts-expect-error This is a hack to get around the fact that Prisma doesn't have a common base type for all models
|
// @ts-expect-error hack again
|
||||||
const objectsAfter = await model.findMany({
|
const objectsAfter = await model({
|
||||||
...args,
|
...args,
|
||||||
where: {
|
// @ts-expect-error this hack breaks typing :(
|
||||||
...args.where,
|
where: (object, { lt }) => lt(object.id, objects.at(-1).id),
|
||||||
id: {
|
limit: 1,
|
||||||
lt: objects.at(-1)?.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (objectsAfter.length > 0) {
|
if (objectsAfter.length > 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue