feat : comment feature
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
check,
|
||||
boolean,
|
||||
foreignKey,
|
||||
index,
|
||||
@@ -35,6 +37,7 @@ export type AuditLogValue =
|
||||
const authSchema = pgSchema("auth");
|
||||
const catalogSchema = pgSchema("catalog");
|
||||
const guidesSchema = pgSchema("guides");
|
||||
const commentsSchema = pgSchema("comments");
|
||||
const commissionSchema = pgSchema("commission");
|
||||
|
||||
const timestamps = {
|
||||
@@ -697,3 +700,96 @@ export const extraSectionRows = guidesSchema.table(
|
||||
},
|
||||
(table) => [index("extra_section_row_order_idx").on(table.sectionId, table.sortOrder)],
|
||||
);
|
||||
|
||||
// Discussion data is separate from guide versions and cached guide content.
|
||||
export const commentThreads = commentsSchema.table("thread", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
guideId: uuid("guide_id").references(() => guides.id, { onDelete: "cascade" }),
|
||||
scheduleId: integer("schedule_id").references(() => stygianSchedules.scheduleId, { onDelete: "cascade" }),
|
||||
}, (table) => [
|
||||
check("comment_thread_one_target", sql`(${table.guideId} is null) <> (${table.scheduleId} is null)`),
|
||||
uniqueIndex("comment_thread_guide_unique").on(table.guideId),
|
||||
uniqueIndex("comment_thread_schedule_unique").on(table.scheduleId),
|
||||
]);
|
||||
|
||||
export const comments = commentsSchema.table("comment", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
threadId: uuid("thread_id").notNull().references(() => commentThreads.id, { onDelete: "cascade" }),
|
||||
authorId: text("author_id").notNull().references(() => users.id),
|
||||
rootId: uuid("root_id"),
|
||||
replyToId: uuid("reply_to_id"),
|
||||
version: integer("version").default(1).notNull(),
|
||||
hidden: boolean("hidden").default(false).notNull(),
|
||||
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
||||
heartedById: text("hearted_by_id").references(() => users.id, { onDelete: "set null" }),
|
||||
...timestamps,
|
||||
}, (table) => [
|
||||
check("comment_reply_shape", sql`(${table.rootId} is null) = (${table.replyToId} is null)`),
|
||||
check("comment_version_positive", sql`${table.version} > 0`),
|
||||
uniqueIndex("comment_thread_id_unique").on(table.threadId, table.id),
|
||||
foreignKey({ columns: [table.threadId, table.rootId], foreignColumns: [table.threadId, table.id] }),
|
||||
foreignKey({ columns: [table.threadId, table.replyToId], foreignColumns: [table.threadId, table.id] }),
|
||||
index("comment_thread_root_time_idx").on(table.threadId, table.rootId, table.createdAt, table.id),
|
||||
index("comment_root_time_idx").on(table.rootId, table.createdAt, table.id),
|
||||
index("comment_inbox_time_idx").on(table.createdAt, table.id),
|
||||
]);
|
||||
|
||||
export const commentRevisions = commentsSchema.table("revision", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
commentId: uuid("comment_id").notNull().references(() => comments.id, { onDelete: "cascade" }),
|
||||
version: integer("version").notNull(),
|
||||
text: text("text").notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
}, (table) => [
|
||||
uniqueIndex("comment_revision_version_unique").on(table.commentId, table.version),
|
||||
check("comment_text_length", sql`length(${table.text}) <= 4000`),
|
||||
]);
|
||||
|
||||
// Attachment metadata references Bun S3 objects; image bytes stay in the bucket.
|
||||
export const commentAttachments = commentsSchema.table("attachment", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
commentId: uuid("comment_id").notNull().references(() => comments.id, { onDelete: "cascade" }),
|
||||
objectKey: text("object_key").notNull(),
|
||||
mimeType: text("mime_type").notNull(),
|
||||
byteSize: integer("byte_size").notNull(),
|
||||
}, (table) => [
|
||||
uniqueIndex("comment_attachment_key_unique").on(table.objectKey),
|
||||
index("comment_attachment_comment_idx").on(table.commentId),
|
||||
check("comment_image_size", sql`${table.byteSize} > 0 and ${table.byteSize} <= 10485760`),
|
||||
check("comment_image_type", sql`${table.mimeType} in ('image/jpeg', 'image/png', 'image/webp')`),
|
||||
]);
|
||||
|
||||
export const commentRevisionAttachments = commentsSchema.table("revision_attachment", {
|
||||
revisionId: uuid("revision_id").notNull().references(() => commentRevisions.id, { onDelete: "cascade" }),
|
||||
attachmentId: uuid("attachment_id").notNull().references(() => commentAttachments.id, { onDelete: "cascade" }),
|
||||
position: integer("position").notNull(),
|
||||
}, (table) => [
|
||||
primaryKey({ columns: [table.revisionId, table.attachmentId] }),
|
||||
uniqueIndex("comment_revision_attachment_position_unique").on(table.revisionId, table.position),
|
||||
check("comment_attachment_position", sql`${table.position} >= 0 and ${table.position} < 5`),
|
||||
]);
|
||||
|
||||
export const commentReactions = commentsSchema.table("reaction", {
|
||||
commentId: uuid("comment_id").notNull().references(() => comments.id, { onDelete: "cascade" }),
|
||||
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
value: integer("value").notNull(),
|
||||
}, (table) => [
|
||||
primaryKey({ columns: [table.commentId, table.userId] }),
|
||||
check("comment_reaction_value", sql`${table.value} in (-1, 1)`),
|
||||
]);
|
||||
|
||||
|
||||
export const commentPushSubscriptions = commentsSchema.table("push_subscription", {
|
||||
endpoint: text("endpoint").primaryKey(),
|
||||
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
p256dh: text("p256dh").notNull(),
|
||||
auth: text("auth").notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
}, (table) => [index("comment_push_subscription_user_idx").on(table.userId)]);
|
||||
|
||||
|
||||
export const commentReadState = commentsSchema.table("read_state", {
|
||||
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
target: text("target").notNull(),
|
||||
seenAt: timestamp("seen_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
}, (table) => [primaryKey({ columns: [table.userId, table.target] })]);
|
||||
|
||||
Reference in New Issue
Block a user