A community based topic aggregation platform built on atproto
1-- +goose Up
2-- Enable pg_trgm extension for fuzzy text search
3CREATE EXTENSION IF NOT EXISTS pg_trgm;
4
5-- Communities table: stores community metadata indexed from firehose
6CREATE TABLE communities (
7 id SERIAL PRIMARY KEY,
8 did TEXT UNIQUE NOT NULL, -- Community DID (did:coves:xxx or did:plc:xxx)
9 handle TEXT UNIQUE NOT NULL, -- Scoped handle (!gaming@coves.social)
10 name TEXT NOT NULL, -- Short community name (local part of handle)
11 display_name TEXT, -- Display name for UI
12 description TEXT, -- Community description
13 description_facets JSONB, -- Rich text annotations
14 avatar_cid TEXT, -- CID of avatar image blob
15 banner_cid TEXT, -- CID of banner image blob
16
17 -- Ownership & hosting
18 owner_did TEXT NOT NULL, -- DID of community owner (instance in V1)
19 created_by_did TEXT NOT NULL, -- DID of user who created community
20 hosted_by_did TEXT NOT NULL, -- DID of hosting instance
21
22 -- Visibility & federation
23 visibility TEXT NOT NULL DEFAULT 'public' CHECK (visibility IN ('public', 'unlisted', 'private')),
24 allow_external_discovery BOOLEAN NOT NULL DEFAULT true,
25
26 -- Moderation
27 moderation_type TEXT CHECK (moderation_type IN ('moderator', 'sortition')),
28 content_warnings TEXT[], -- Array of content warning types
29
30 -- Statistics (cached counts)
31 member_count INTEGER DEFAULT 0,
32 subscriber_count INTEGER DEFAULT 0,
33 post_count INTEGER DEFAULT 0,
34
35 -- Federation metadata (for future cross-platform support)
36 federated_from TEXT, -- 'lemmy', 'coves', etc.
37 federated_id TEXT, -- Original ID on federated platform
38
39 -- Timestamps
40 created_at TIMESTAMP WITH TIME ZONE NOT NULL,
41 updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
42
43 -- AT-Proto metadata
44 record_uri TEXT, -- AT-URI of the community profile record
45 record_cid TEXT -- CID of the community profile record
46);
47
48-- Indexes for efficient queries
49CREATE INDEX idx_communities_handle ON communities(handle);
50CREATE INDEX idx_communities_visibility ON communities(visibility);
51CREATE INDEX idx_communities_hosted_by ON communities(hosted_by_did);
52CREATE INDEX idx_communities_created_by ON communities(created_by_did);
53CREATE INDEX idx_communities_created_at ON communities(created_at);
54CREATE INDEX idx_communities_name_trgm ON communities USING gin(name gin_trgm_ops); -- For fuzzy search
55CREATE INDEX idx_communities_description_trgm ON communities USING gin(description gin_trgm_ops);
56
57-- Subscriptions table: lightweight feed following
58CREATE TABLE community_subscriptions (
59 id SERIAL PRIMARY KEY,
60 user_did TEXT NOT NULL,
61 community_did TEXT NOT NULL REFERENCES communities(did) ON DELETE CASCADE,
62 subscribed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
63
64 -- AT-Proto metadata (subscription is a record in user's repo)
65 record_uri TEXT, -- AT-URI of the subscription record
66 record_cid TEXT, -- CID of the subscription record
67
68 UNIQUE(user_did, community_did)
69);
70
71-- Indexes for subscription queries
72CREATE INDEX idx_subscriptions_user ON community_subscriptions(user_did);
73CREATE INDEX idx_subscriptions_community ON community_subscriptions(community_did);
74CREATE INDEX idx_subscriptions_user_community ON community_subscriptions(user_did, community_did); -- Composite index for GetSubscription
75CREATE INDEX idx_subscriptions_subscribed_at ON community_subscriptions(subscribed_at);
76
77-- Memberships table: active participation & reputation tracking
78CREATE TABLE community_memberships (
79 id SERIAL PRIMARY KEY,
80 user_did TEXT NOT NULL,
81 community_did TEXT NOT NULL REFERENCES communities(did) ON DELETE CASCADE,
82
83 -- Reputation system
84 reputation_score INTEGER DEFAULT 0, -- Gained through participation
85 contribution_count INTEGER DEFAULT 0, -- Total posts + comments + actions
86
87 -- Activity tracking
88 joined_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
89 last_active_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
90
91 -- Moderation status
92 is_banned BOOLEAN DEFAULT false,
93 is_moderator BOOLEAN DEFAULT false,
94
95 UNIQUE(user_did, community_did)
96);
97
98-- Indexes for membership queries
99CREATE INDEX idx_memberships_user ON community_memberships(user_did);
100CREATE INDEX idx_memberships_community ON community_memberships(community_did);
101CREATE INDEX idx_memberships_reputation ON community_memberships(community_did, reputation_score DESC);
102CREATE INDEX idx_memberships_last_active ON community_memberships(last_active_at);
103
104-- Community moderation actions table (V2 feature, schema prepared)
105CREATE TABLE community_moderation (
106 id SERIAL PRIMARY KEY,
107 community_did TEXT NOT NULL REFERENCES communities(did) ON DELETE CASCADE,
108 action TEXT NOT NULL CHECK (action IN ('delist', 'quarantine', 'remove')),
109 reason TEXT,
110 instance_did TEXT NOT NULL, -- Which instance took this action
111 broadcast BOOLEAN DEFAULT false, -- Share moderation signal with network?
112 created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
113 expires_at TIMESTAMP WITH TIME ZONE -- Optional: temporary moderation
114);
115
116-- Indexes for moderation queries
117CREATE INDEX idx_moderation_community ON community_moderation(community_did);
118CREATE INDEX idx_moderation_instance ON community_moderation(instance_did);
119CREATE INDEX idx_moderation_action ON community_moderation(action);
120CREATE INDEX idx_moderation_created_at ON community_moderation(created_at);
121
122-- +goose Down
123DROP INDEX IF EXISTS idx_moderation_created_at;
124DROP INDEX IF EXISTS idx_moderation_action;
125DROP INDEX IF EXISTS idx_moderation_instance;
126DROP INDEX IF EXISTS idx_moderation_community;
127DROP TABLE IF EXISTS community_moderation;
128
129DROP INDEX IF EXISTS idx_memberships_last_active;
130DROP INDEX IF EXISTS idx_memberships_reputation;
131DROP INDEX IF EXISTS idx_memberships_community;
132DROP INDEX IF EXISTS idx_memberships_user;
133DROP TABLE IF EXISTS community_memberships;
134
135DROP INDEX IF EXISTS idx_subscriptions_subscribed_at;
136DROP INDEX IF EXISTS idx_subscriptions_user_community;
137DROP INDEX IF EXISTS idx_subscriptions_community;
138DROP INDEX IF EXISTS idx_subscriptions_user;
139DROP TABLE IF EXISTS community_subscriptions;
140
141DROP INDEX IF EXISTS idx_communities_description_trgm;
142DROP INDEX IF EXISTS idx_communities_name_trgm;
143DROP INDEX IF EXISTS idx_communities_created_at;
144DROP INDEX IF EXISTS idx_communities_created_by;
145DROP INDEX IF EXISTS idx_communities_hosted_by;
146DROP INDEX IF EXISTS idx_communities_visibility;
147DROP INDEX IF EXISTS idx_communities_handle;
148DROP TABLE IF EXISTS communities;