Barazo AppView backend
barazo.forum
1import {
2 pgTable,
3 pgPolicy,
4 text,
5 integer,
6 timestamp,
7 jsonb,
8 boolean,
9 index,
10} from 'drizzle-orm/pg-core'
11import { sql } from 'drizzle-orm'
12import { appRole } from './roles.js'
13
14export const topics = pgTable(
15 'topics',
16 {
17 uri: text('uri').primaryKey(),
18 rkey: text('rkey').notNull(),
19 authorDid: text('author_did').notNull(),
20 title: text('title').notNull(),
21 content: text('content').notNull(),
22 category: text('category').notNull(),
23 site: text('site'),
24 tags: jsonb('tags').$type<string[]>(),
25 communityDid: text('community_did').notNull(),
26 cid: text('cid').notNull(),
27 labels: jsonb('labels').$type<{ values: { val: string }[] }>(),
28 replyCount: integer('reply_count').notNull().default(0),
29 reactionCount: integer('reaction_count').notNull().default(0),
30 voteCount: integer('vote_count').notNull().default(0),
31 lastActivityAt: timestamp('last_activity_at', { withTimezone: true }).notNull().defaultNow(),
32 publishedAt: timestamp('published_at', { withTimezone: true }).notNull(),
33 indexedAt: timestamp('indexed_at', { withTimezone: true }).notNull().defaultNow(),
34 isLocked: boolean('is_locked').notNull().default(false),
35 isPinned: boolean('is_pinned').notNull().default(false),
36 pinnedAt: timestamp('pinned_at', { withTimezone: true }),
37 pinnedScope: text('pinned_scope', { enum: ['category', 'forum'] }),
38 isModDeleted: boolean('is_mod_deleted').notNull().default(false),
39 isAuthorDeleted: boolean('is_author_deleted').notNull().default(false),
40 moderationStatus: text('moderation_status', {
41 enum: ['approved', 'held', 'rejected'],
42 })
43 .notNull()
44 .default('approved'),
45 /** Trust status based on account age at indexing time. 'new' for accounts < 24h old. */
46 trustStatus: text('trust_status', {
47 enum: ['trusted', 'new'],
48 })
49 .notNull()
50 .default('trusted'),
51 // Note: search_vector (tsvector) and embedding (vector) columns exist in the
52 // database but are managed outside Drizzle schema (see migration 0010).
53 // search_vector is maintained by a database trigger.
54 // embedding is nullable vector(768) for optional semantic search.
55 },
56 (table) => [
57 index('topics_author_did_idx').on(table.authorDid),
58 index('topics_category_idx').on(table.category),
59 index('topics_published_at_idx').on(table.publishedAt),
60 index('topics_last_activity_at_idx').on(table.lastActivityAt),
61 index('topics_community_did_idx').on(table.communityDid),
62 index('topics_moderation_status_idx').on(table.moderationStatus),
63 index('topics_trust_status_idx').on(table.trustStatus),
64 index('topics_community_category_activity_idx').on(
65 table.communityDid,
66 table.category,
67 table.lastActivityAt
68 ),
69 index('topics_pinned_scope_idx').on(table.pinnedScope),
70 index('topics_author_did_rkey_idx').on(table.authorDid, table.rkey),
71 pgPolicy('tenant_isolation', {
72 as: 'permissive',
73 to: appRole,
74 for: 'all',
75 using: sql`community_did = current_setting('app.current_community_did', true)`,
76 withCheck: sql`community_did = current_setting('app.current_community_did', true)`,
77 }),
78 ]
79).enableRLS()