-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase-schema.sql
More file actions
386 lines (325 loc) Β· 14 KB
/
Copy pathdatabase-schema.sql
File metadata and controls
386 lines (325 loc) Β· 14 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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
-- BitSage Discord Bot - Advanced Features Database Schema
-- PostgreSQL Schema for Gamification, Onboarding, and User Management
-- ============================================================================
-- USERS & PROFILES
-- ============================================================================
CREATE TABLE IF NOT EXISTS discord_users (
user_id TEXT PRIMARY KEY,
username TEXT NOT NULL,
discriminator TEXT,
wallet_address TEXT UNIQUE,
language TEXT DEFAULT 'en',
-- Gamification
xp INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
total_messages INTEGER DEFAULT 0,
-- Streaks & Engagement
daily_streak INTEGER DEFAULT 0,
longest_streak INTEGER DEFAULT 0,
last_daily_claim TIMESTAMPTZ,
last_message_at TIMESTAMPTZ,
-- Reputation
reputation INTEGER DEFAULT 0,
helpful_votes INTEGER DEFAULT 0,
-- Status
verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMPTZ,
onboarding_completed BOOLEAN DEFAULT FALSE,
onboarding_step INTEGER DEFAULT 0,
-- Metadata
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_discord_users_wallet ON discord_users(wallet_address);
CREATE INDEX idx_discord_users_xp ON discord_users(xp DESC);
CREATE INDEX idx_discord_users_level ON discord_users(level DESC);
-- ============================================================================
-- ACHIEVEMENTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS achievements (
id SERIAL PRIMARY KEY,
key TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
description TEXT,
emoji TEXT,
category TEXT, -- first_steps, network, social, special
xp_reward INTEGER DEFAULT 0,
rarity TEXT, -- common, rare, epic, legendary
hidden BOOLEAN DEFAULT FALSE,
requirement_type TEXT, -- messages, jobs, streak, etc
requirement_value INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS user_achievements (
user_id TEXT REFERENCES discord_users(user_id) ON DELETE CASCADE,
achievement_id INTEGER REFERENCES achievements(id) ON DELETE CASCADE,
earned_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (user_id, achievement_id)
);
CREATE INDEX idx_user_achievements_user ON user_achievements(user_id);
CREATE INDEX idx_user_achievements_earned ON user_achievements(earned_at DESC);
-- ============================================================================
-- QUESTS & CHALLENGES
-- ============================================================================
CREATE TABLE IF NOT EXISTS quests (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
emoji TEXT,
quest_type TEXT, -- daily, weekly, one_time, tutorial
xp_reward INTEGER DEFAULT 0,
-- Requirements
requirement_type TEXT, -- messages, jobs, verify, invite
requirement_value INTEGER,
requirement_data JSONB, -- additional requirements
-- Availability
active BOOLEAN DEFAULT TRUE,
start_date TIMESTAMPTZ,
end_date TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS user_quests (
user_id TEXT REFERENCES discord_users(user_id) ON DELETE CASCADE,
quest_id INTEGER REFERENCES quests(id) ON DELETE CASCADE,
progress INTEGER DEFAULT 0,
completed BOOLEAN DEFAULT FALSE,
started_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ,
PRIMARY KEY (user_id, quest_id)
);
CREATE INDEX idx_user_quests_user ON user_quests(user_id);
CREATE INDEX idx_user_quests_active ON user_quests(user_id, completed) WHERE completed = FALSE;
-- ============================================================================
-- MESSAGE XP & ACTIVITY
-- ============================================================================
CREATE TABLE IF NOT EXISTS message_xp (
user_id TEXT REFERENCES discord_users(user_id) ON DELETE CASCADE,
channel_id TEXT NOT NULL,
message_count INTEGER DEFAULT 0,
xp_earned INTEGER DEFAULT 0,
last_xp_at TIMESTAMPTZ,
PRIMARY KEY (user_id, channel_id)
);
CREATE INDEX idx_message_xp_user ON message_xp(user_id);
-- ============================================================================
-- REPUTATION & VOTING
-- ============================================================================
CREATE TABLE IF NOT EXISTS message_votes (
message_id TEXT PRIMARY KEY,
author_id TEXT REFERENCES discord_users(user_id) ON DELETE CASCADE,
channel_id TEXT NOT NULL,
upvotes INTEGER DEFAULT 0,
downvotes INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS user_votes (
voter_id TEXT NOT NULL,
message_id TEXT REFERENCES message_votes(message_id) ON DELETE CASCADE,
vote_type TEXT NOT NULL, -- upvote, downvote
voted_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (voter_id, message_id)
);
-- ============================================================================
-- ONBOARDING
-- ============================================================================
CREATE TABLE IF NOT EXISTS onboarding_progress (
user_id TEXT REFERENCES discord_users(user_id) ON DELETE CASCADE PRIMARY KEY,
current_step INTEGER DEFAULT 0,
steps_completed INTEGER[] DEFAULT '{}',
role_selections TEXT[] DEFAULT '{}',
skipped BOOLEAN DEFAULT FALSE,
started_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
-- ============================================================================
-- TRANSLATIONS & LANGUAGES
-- ============================================================================
CREATE TABLE IF NOT EXISTS user_language_preferences (
user_id TEXT REFERENCES discord_users(user_id) ON DELETE CASCADE PRIMARY KEY,
language_code TEXT NOT NULL,
auto_translate BOOLEAN DEFAULT FALSE,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================================
-- INTERACTIVE FEATURES
-- ============================================================================
CREATE TABLE IF NOT EXISTS polls (
id SERIAL PRIMARY KEY,
message_id TEXT UNIQUE,
channel_id TEXT NOT NULL,
creator_id TEXT REFERENCES discord_users(user_id),
question TEXT NOT NULL,
options JSONB NOT NULL, -- [{id: 1, text: "...", votes: 0}]
active BOOLEAN DEFAULT TRUE,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS poll_votes (
poll_id INTEGER REFERENCES polls(id) ON DELETE CASCADE,
user_id TEXT NOT NULL,
option_id INTEGER NOT NULL,
voted_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (poll_id, user_id)
);
CREATE TABLE IF NOT EXISTS trivia_sessions (
id SERIAL PRIMARY KEY,
channel_id TEXT NOT NULL,
host_id TEXT REFERENCES discord_users(user_id),
category TEXT,
active BOOLEAN DEFAULT TRUE,
current_question INTEGER DEFAULT 0,
total_questions INTEGER DEFAULT 10,
started_at TIMESTAMPTZ DEFAULT NOW(),
ended_at TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS trivia_scores (
session_id INTEGER REFERENCES trivia_sessions(id) ON DELETE CASCADE,
user_id TEXT REFERENCES discord_users(user_id),
correct_answers INTEGER DEFAULT 0,
total_answered INTEGER DEFAULT 0,
score INTEGER DEFAULT 0,
PRIMARY KEY (session_id, user_id)
);
-- ============================================================================
-- DAILY REWARDS & STREAKS
-- ============================================================================
CREATE TABLE IF NOT EXISTS daily_claims (
user_id TEXT REFERENCES discord_users(user_id) ON DELETE CASCADE,
claim_date DATE NOT NULL,
xp_earned INTEGER,
streak_day INTEGER,
bonus_applied BOOLEAN DEFAULT FALSE,
claimed_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (user_id, claim_date)
);
CREATE INDEX idx_daily_claims_user ON daily_claims(user_id, claim_date DESC);
-- ============================================================================
-- SERVER CONFIGURATION
-- ============================================================================
CREATE TABLE IF NOT EXISTS server_config (
guild_id TEXT PRIMARY KEY,
welcome_channel_id TEXT,
verify_channel_id TEXT,
rules_channel_id TEXT,
stats_channel_id TEXT,
announcement_channel_id TEXT,
-- Role IDs
verified_role_id TEXT,
worker_role_id TEXT,
moderator_role_id TEXT,
-- Features
gamification_enabled BOOLEAN DEFAULT TRUE,
auto_onboarding BOOLEAN DEFAULT TRUE,
ai_responses BOOLEAN DEFAULT TRUE,
setup_completed BOOLEAN DEFAULT FALSE,
setup_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================================
-- FUNCTIONS & TRIGGERS
-- ============================================================================
-- Auto-update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_discord_users_updated_at BEFORE UPDATE
ON discord_users FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Calculate level from XP
CREATE OR REPLACE FUNCTION calculate_level(xp INTEGER)
RETURNS INTEGER AS $$
BEGIN
-- Level formula: sqrt(XP / 100)
-- Level 1 = 0 XP, Level 2 = 100 XP, Level 10 = 10,000 XP, etc.
RETURN FLOOR(SQRT(xp / 100.0)) + 1;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- ============================================================================
-- INITIAL DATA - ACHIEVEMENTS
-- ============================================================================
INSERT INTO achievements (key, name, description, emoji, category, xp_reward, rarity, requirement_type, requirement_value)
VALUES
-- First Steps
('first_message', 'First Message', 'Send your first message', 'π¬', 'first_steps', 10, 'common', 'messages', 1),
('getting_started', 'Getting Started', 'Complete the onboarding', 'π¬', 'first_steps', 50, 'common', 'onboarding', 1),
('verified', 'Verified', 'Link your Starknet wallet', 'β
', 'first_steps', 100, 'common', 'verify', 1),
('first_job', 'First Job', 'Complete your first proof job', 'π', 'network', 200, 'rare', 'jobs', 1),
-- Social
('chatterbox', 'Chatterbox', 'Send 100 messages', 'π', 'social', 100, 'common', 'messages', 100),
('conversationalist', 'Conversationalist', 'Send 1000 messages', 'π£οΈ', 'social', 500, 'rare', 'messages', 1000),
('community_star', 'Community Star', 'Send 5000 messages', 'π', 'social', 2000, 'epic', 'messages', 5000),
('helper', 'Helper', 'Get 50 helpful votes', 'π€', 'social', 300, 'rare', 'helpful_votes', 50),
-- Network
('worker', 'Active Worker', 'Register as a worker', 'π€', 'network', 150, 'common', 'worker_status', 1),
('productive', 'Productive', 'Complete 10 jobs', 'β‘', 'network', 300, 'rare', 'jobs', 10),
('powerhouse', 'Powerhouse', 'Complete 100 jobs', 'πͺ', 'network', 1500, 'epic', 'jobs', 100),
('legend', 'Legend', 'Complete 1000 jobs', 'π', 'network', 10000, 'legendary', 'jobs', 1000),
-- Staking
('bronze_hand', 'Bronze Hand', 'Stake 100+ SAGE', 'π₯', 'network', 50, 'common', 'stake', 100),
('silver_hand', 'Silver Hand', 'Stake 1,000+ SAGE', 'π₯', 'network', 200, 'rare', 'stake', 1000),
('gold_hand', 'Gold Hand', 'Stake 5,000+ SAGE', 'π₯', 'network', 1000, 'epic', 'stake', 5000),
('diamond_hand', 'Diamond Hand', 'Stake 10,000+ SAGE', 'π', 'network', 5000, 'legendary', 'stake', 10000),
-- Streaks
('consistent', 'Consistent', '7 day streak', 'π₯', 'special', 200, 'common', 'streak', 7),
('dedicated', 'Dedicated', '30 day streak', 'π₯', 'special', 1000, 'rare', 'streak', 30),
('unstoppable', 'Unstoppable', '100 day streak', 'π₯', 'special', 5000, 'epic', 'streak', 100),
-- Special
('early_adopter', 'Early Adopter', 'Joined in the first month', 'π
', 'special', 500, 'rare', 'early', 1),
('recruiter', 'Recruiter', 'Invite 10 verified users', 'π₯', 'special', 1000, 'epic', 'referrals', 10)
ON CONFLICT (key) DO NOTHING;
-- ============================================================================
-- INITIAL DATA - QUESTS
-- ============================================================================
INSERT INTO quests (title, description, emoji, quest_type, xp_reward, requirement_type, requirement_value, active)
VALUES
-- Tutorial Quests
('Introduce Yourself', 'Post an introduction in #general', 'π', 'tutorial', 100, 'message_in_channel', 1, TRUE),
('Read the Rules', 'Read and react to the rules', 'π', 'tutorial', 50, 'read_rules', 1, TRUE),
('Verify Your Wallet', 'Link your Starknet wallet', 'π', 'tutorial', 200, 'verify', 1, TRUE),
-- Daily Quests
('Daily Login', 'Claim your daily reward', 'π
', 'daily', 50, 'daily_claim', 1, TRUE),
('Daily Chat', 'Send 10 messages today', 'π¬', 'daily', 25, 'messages', 10, TRUE),
-- Weekly Quests
('Weekly Contributor', 'Send 100 messages this week', 'π', 'weekly', 200, 'messages', 100, TRUE),
('Weekly Helper', 'Get 5 helpful votes this week', 'β', 'weekly', 150, 'helpful_votes', 5, TRUE)
ON CONFLICT DO NOTHING;
-- ============================================================================
-- VIEWS FOR LEADERBOARDS
-- ============================================================================
CREATE OR REPLACE VIEW leaderboard_xp AS
SELECT
user_id,
username,
xp,
level,
ROW_NUMBER() OVER (ORDER BY xp DESC) as rank
FROM discord_users
WHERE xp > 0
ORDER BY xp DESC
LIMIT 100;
CREATE OR REPLACE VIEW leaderboard_streak AS
SELECT
user_id,
username,
daily_streak,
longest_streak,
ROW_NUMBER() OVER (ORDER BY daily_streak DESC, longest_streak DESC) as rank
FROM discord_users
WHERE daily_streak > 0
ORDER BY daily_streak DESC, longest_streak DESC
LIMIT 100;
CREATE OR REPLACE VIEW leaderboard_reputation AS
SELECT
user_id,
username,
reputation,
helpful_votes,
ROW_NUMBER() OVER (ORDER BY reputation DESC) as rank
FROM discord_users
WHERE reputation > 0
ORDER BY reputation DESC
LIMIT 100;