/ opencodehub / src / db / schema / discussions.ts
swadhinbiswas feat: push-mirror, webhook queue, auth-cache, gists/discussions · 8/28/2026
96005fc
90 lines · 2.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * Discussions Schema - Drizzle ORM
 * GitHub-style repository discussions and threaded comments
 */

import {
  boolean,
  index,
  integer,
  pgTable,
  text,
  timestamp,
} from "drizzle-orm/pg-core";
import { repositories } from "./repositories";
import { users } from "./users";

export const DISCUSSION_CATEGORIES = [
  "General",
  "Ideas",
  "Q&A",
  "Show and tell",
] as const;

export type DiscussionCategory = (typeof DISCUSSION_CATEGORIES)[number];

export const discussions = pgTable(
  "discussions",
  {
    id: text("id").primaryKey(),
    repositoryId: text("repository_id")
      .notNull()
      .references(() => repositories.id, { onDelete: "cascade" }),
    authorId: text("author_id")
      .notNull()
      .references(() => users.id),
    title: text("title").notNull(),
    body: text("body").notNull(),
    category: text("category").notNull().default("General"),
    pinned: boolean("pinned").default(false),
    closed: boolean("closed").default(false),
    commentCount: integer("comment_count").default(0),
    lastActivityAt: timestamp("last_activity_at"),
    createdAt: timestamp("created_at").notNull().defaultNow(),
    updatedAt: timestamp("updated_at").notNull().defaultNow(),
  },
  (t) => ({
    repoClosedIdx: index("discussions_repo_closed_idx").on(
      t.repositoryId,
      t.closed,
    ),
    repoActivityIdx: index("discussions_repo_activity_idx").on(
      t.repositoryId,
      t.lastActivityAt,
    ),
    authorIdx: index("discussions_author_idx").on(t.authorId),
  }),
);

export const discussionComments = pgTable(
  "discussion_comments",
  {
    id: text("id").primaryKey(),
    discussionId: text("discussion_id")
      .notNull()
      .references(() => discussions.id, { onDelete: "cascade" }),
    // Self-reference for one-level threading; stored but treated flat in UI v1.
    // Plain text column (no FK) mirrors the issues.parent_id convention to avoid
    // circular type references in Drizzle.
    parentId: text("parent_id"),
    authorId: text("author_id")
      .notNull()
      .references(() => users.id),
    body: text("body").notNull(),
    createdAt: timestamp("created_at").notNull().defaultNow(),
    updatedAt: timestamp("updated_at").notNull().defaultNow(),
  },
  (t) => ({
    discussionIdx: index("discussion_comments_discussion_idx").on(
      t.discussionId,
    ),
    parentIdx: index("discussion_comments_parent_idx").on(t.parentId),
  }),
);

// Types
export type Discussion = typeof discussions.$inferSelect;
export type NewDiscussion = typeof discussions.$inferInsert;
export type DiscussionComment = typeof discussionComments.$inferSelect;
export type NewDiscussionComment = typeof discussionComments.$inferInsert;