2026-06-09 19:46:08 +07:00
|
|
|
import {
|
|
|
|
|
bigint as pgBigint,
|
2026-06-13 00:55:42 +07:00
|
|
|
boolean as pgBoolean,
|
2026-06-09 19:46:08 +07:00
|
|
|
foreignKey as pgForeignKey,
|
|
|
|
|
index as pgIndex,
|
|
|
|
|
integer as pgInteger,
|
|
|
|
|
real as pgReal,
|
|
|
|
|
pgTable,
|
|
|
|
|
text as pgText,
|
|
|
|
|
} from "drizzle-orm/pg-core";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Messages Table (PostgreSQL)
|
|
|
|
|
* Stores text messages with AI moderation analysis
|
|
|
|
|
*/
|
|
|
|
|
export const pgMessagesTable = pgTable(
|
|
|
|
|
"messages",
|
|
|
|
|
{
|
|
|
|
|
id: pgText("id").primaryKey(),
|
|
|
|
|
guild_id: pgText("guild_id").notNull(),
|
|
|
|
|
channel_id: pgText("channel_id").notNull(),
|
|
|
|
|
thread_id: pgText("thread_id"),
|
|
|
|
|
user_id: pgText("user_id").notNull(),
|
|
|
|
|
username: pgText("username").notNull(),
|
|
|
|
|
avatar_url: pgText("avatar_url"),
|
|
|
|
|
content: pgText("content").notNull(),
|
|
|
|
|
edited_content: pgText("edited_content"),
|
|
|
|
|
created_at: pgBigint("created_at", { mode: "number" }).notNull(),
|
|
|
|
|
edited_at: pgBigint("edited_at", { mode: "number" }),
|
|
|
|
|
deleted_at: pgBigint("deleted_at", { mode: "number" }),
|
|
|
|
|
type: pgText("type", { enum: ["text", "edited", "deleted"] })
|
|
|
|
|
.notNull()
|
|
|
|
|
.default("text"),
|
2026-06-13 00:55:42 +07:00
|
|
|
is_reply: pgBoolean("is_reply"),
|
|
|
|
|
is_forward: pgBoolean("is_forward"),
|
|
|
|
|
is_crosspost: pgBoolean("is_crosspost"),
|
|
|
|
|
reference_message_id: pgText("reference_message_id"),
|
|
|
|
|
reference_channel_id: pgText("reference_channel_id"),
|
|
|
|
|
reference_guild_id: pgText("reference_guild_id"),
|
2026-06-09 19:46:08 +07:00
|
|
|
metadata: pgText("metadata"),
|
|
|
|
|
ai_status: pgText("ai_status", {
|
|
|
|
|
enum: ["pending", "processing", "clean", "warn", "flagged", "error"],
|
|
|
|
|
})
|
|
|
|
|
.notNull()
|
|
|
|
|
.default("pending"),
|
|
|
|
|
ai_moderation_flags: pgText("ai_moderation_flags"),
|
|
|
|
|
ai_moderation_score: pgReal("ai_moderation_score"),
|
|
|
|
|
ai_analysis: pgText("ai_analysis"),
|
|
|
|
|
ai_categories: pgText("ai_categories"),
|
|
|
|
|
ai_severity: pgText("ai_severity", {
|
|
|
|
|
enum: ["none", "low", "medium", "high", "critical"],
|
|
|
|
|
}),
|
|
|
|
|
ai_confidence: pgReal("ai_confidence"),
|
|
|
|
|
ai_recommended_action: pgText("ai_recommended_action", {
|
|
|
|
|
enum: ["none", "monitor", "warn", "review", "delete", "escalate"],
|
|
|
|
|
}),
|
|
|
|
|
ai_analyzed_at: pgBigint("ai_analyzed_at", { mode: "number" }),
|
|
|
|
|
ai_error: pgText("ai_error"),
|
|
|
|
|
},
|
|
|
|
|
(table) => ({
|
|
|
|
|
channelIdx: pgIndex("idx_messages_channel").on(table.channel_id),
|
|
|
|
|
userIdx: pgIndex("idx_messages_user").on(table.user_id),
|
|
|
|
|
createdIdx: pgIndex("idx_messages_created").on(table.created_at),
|
|
|
|
|
threadIdx: pgIndex("idx_messages_thread").on(table.thread_id),
|
|
|
|
|
channelCreatedIdx: pgIndex("idx_messages_channel_created").on(
|
|
|
|
|
table.channel_id,
|
|
|
|
|
table.created_at,
|
|
|
|
|
table.id,
|
|
|
|
|
),
|
|
|
|
|
threadCreatedIdx: pgIndex("idx_messages_thread_created").on(
|
|
|
|
|
table.thread_id,
|
|
|
|
|
table.created_at,
|
|
|
|
|
table.id,
|
|
|
|
|
),
|
|
|
|
|
aiStatusCreatedIdx: pgIndex("idx_messages_ai_status_created").on(
|
|
|
|
|
table.ai_status,
|
|
|
|
|
table.created_at,
|
|
|
|
|
table.id,
|
|
|
|
|
),
|
|
|
|
|
guildAiStatusCreatedIdx: pgIndex("idx_messages_guild_ai_status_created").on(
|
|
|
|
|
table.guild_id,
|
|
|
|
|
table.ai_status,
|
|
|
|
|
table.created_at,
|
|
|
|
|
table.id,
|
|
|
|
|
),
|
2026-07-04 03:03:36 +07:00
|
|
|
guildAiStatusAnalyzedIdx: pgIndex(
|
|
|
|
|
"idx_messages_guild_ai_status_analyzed",
|
|
|
|
|
).on(table.guild_id, table.ai_status, table.ai_analyzed_at, table.id),
|
2026-06-09 19:46:08 +07:00
|
|
|
guildCreatedDeletedIdx: pgIndex("idx_messages_guild_created_deleted").on(
|
|
|
|
|
table.guild_id,
|
|
|
|
|
table.created_at,
|
|
|
|
|
table.deleted_at,
|
|
|
|
|
table.id,
|
|
|
|
|
),
|
|
|
|
|
channelAiStatusCreatedIdx: pgIndex(
|
|
|
|
|
"idx_messages_channel_ai_status_created",
|
|
|
|
|
).on(table.channel_id, table.ai_status, table.created_at, table.id),
|
|
|
|
|
threadAiStatusCreatedIdx: pgIndex(
|
|
|
|
|
"idx_messages_thread_ai_status_created",
|
|
|
|
|
).on(table.thread_id, table.ai_status, table.created_at, table.id),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
2026-06-12 21:28:05 +07:00
|
|
|
/**
|
|
|
|
|
* Corrected Moderations Table (PostgreSQL)
|
|
|
|
|
* Stores manual corrections of AI moderation false positives
|
|
|
|
|
* for few-shot injection into LLM moderation prompts.
|
|
|
|
|
*/
|
|
|
|
|
export const pgCorrectedModerationsTable = pgTable(
|
|
|
|
|
"corrected_moderations",
|
|
|
|
|
{
|
|
|
|
|
id: pgText("id").primaryKey(),
|
|
|
|
|
message_id: pgText("message_id").notNull(),
|
|
|
|
|
original_flags: pgText("original_flags").notNull(),
|
|
|
|
|
corrected_flags: pgText("corrected_flags").notNull(),
|
|
|
|
|
correction_notes: pgText("correction_notes"),
|
|
|
|
|
content_snippet: pgText("content_snippet").notNull(),
|
|
|
|
|
created_at: pgBigint("created_at", { mode: "number" }).notNull(),
|
|
|
|
|
},
|
|
|
|
|
(table) => ({
|
|
|
|
|
createdAtIdx: pgIndex("idx_corrected_moderations_created_at").on(
|
|
|
|
|
table.created_at,
|
|
|
|
|
),
|
|
|
|
|
messageIdx: pgIndex("idx_corrected_moderations_message_id").on(
|
|
|
|
|
table.message_id,
|
|
|
|
|
),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
export type CorrectedModeration =
|
|
|
|
|
typeof pgCorrectedModerationsTable.$inferSelect;
|
|
|
|
|
export type CorrectedModerationInsert =
|
|
|
|
|
typeof pgCorrectedModerationsTable.$inferInsert;
|
|
|
|
|
|
2026-06-09 19:46:08 +07:00
|
|
|
/**
|
|
|
|
|
* Attachments Table (PostgreSQL)
|
|
|
|
|
* Stores attachment metadata with upload status tracking
|
|
|
|
|
*/
|
|
|
|
|
export const pgAttachmentsTable = pgTable(
|
|
|
|
|
"attachments",
|
|
|
|
|
{
|
|
|
|
|
id: pgText("id").primaryKey(),
|
|
|
|
|
message_id: pgText("message_id").notNull(),
|
|
|
|
|
guild_id: pgText("guild_id").notNull(),
|
|
|
|
|
channel_id: pgText("channel_id").notNull(),
|
|
|
|
|
thread_id: pgText("thread_id"),
|
|
|
|
|
user_id: pgText("user_id").notNull(),
|
|
|
|
|
filename: pgText("filename").notNull(),
|
|
|
|
|
size: pgInteger("size").notNull(),
|
|
|
|
|
type: pgText("type").notNull(),
|
|
|
|
|
discord_url: pgText("discord_url").notNull(),
|
|
|
|
|
uploaded_url: pgText("uploaded_url"),
|
|
|
|
|
upload_status: pgText("upload_status", {
|
|
|
|
|
enum: ["pending", "uploaded", "failed"],
|
|
|
|
|
})
|
|
|
|
|
.notNull()
|
|
|
|
|
.default("pending"),
|
|
|
|
|
upload_error: pgText("upload_error"),
|
|
|
|
|
created_at: pgBigint("created_at", { mode: "number" }).notNull(),
|
|
|
|
|
uploaded_at: pgBigint("uploaded_at", { mode: "number" }),
|
|
|
|
|
},
|
|
|
|
|
(table) => ({
|
|
|
|
|
channelIdx: pgIndex("idx_attachments_channel").on(table.channel_id),
|
|
|
|
|
messageIdx: pgIndex("idx_attachments_message").on(table.message_id),
|
|
|
|
|
statusIdx: pgIndex("idx_attachments_status").on(table.upload_status),
|
|
|
|
|
channelCreatedIdx: pgIndex("idx_attachments_channel_created").on(
|
|
|
|
|
table.channel_id,
|
|
|
|
|
table.created_at,
|
|
|
|
|
table.id,
|
|
|
|
|
),
|
|
|
|
|
threadCreatedIdx: pgIndex("idx_attachments_thread_created").on(
|
|
|
|
|
table.thread_id,
|
|
|
|
|
table.created_at,
|
|
|
|
|
table.id,
|
|
|
|
|
),
|
|
|
|
|
messageFk: pgForeignKey({
|
|
|
|
|
columns: [table.message_id],
|
|
|
|
|
foreignColumns: [pgMessagesTable.id],
|
|
|
|
|
name: "fk_attachments_message_id",
|
|
|
|
|
}).onDelete("cascade"),
|
|
|
|
|
}),
|
|
|
|
|
);
|