-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathschema.sql
More file actions
513 lines (434 loc) · 18.1 KB
/
schema.sql
File metadata and controls
513 lines (434 loc) · 18.1 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
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
-- Trigger to automatically update status on insert or update
DROP TRIGGER IF EXISTS trigger_update_license_status ON licenses;
CREATE TRIGGER trigger_update_license_status
BEFORE INSERT OR UPDATE ON licenses
FOR EACH ROW
EXECUTE FUNCTION update_license_status();
-- ============================================================
-- VIEWS for common queries
-- ============================================================
-- View: Active licenses with group information
CREATE OR REPLACE VIEW v_active_licenses AS
SELECT
l.id,
l.key,
l.group_id,
lg.name AS group_name,
l.created_at,
l.expires_at,
l.assigned_to,
EXTRACT(DAY FROM (l.expires_at - NOW())) AS days_remaining
FROM licenses l
JOIN license_groups lg ON l.group_id = lg.id
WHERE l.status = 'active' AND l.expires_at > NOW();
COMMENT ON VIEW v_active_licenses IS 'Shows all active (non-expired) licenses with group information';
-- View: Expired licenses
CREATE OR REPLACE VIEW v_expired_licenses AS
SELECT
l.id,
l.key,
l.group_id,
lg.name AS group_name,
l.created_at,
l.expires_at,
l.assigned_to,
EXTRACT(DAY FROM (NOW() - l.expires_at)) AS days_expired
FROM licenses l
JOIN license_groups lg ON l.group_id = lg.id
WHERE l.expires_at <= NOW();
COMMENT ON VIEW v_expired_licenses IS 'Shows all expired licenses';
-- View: License statistics by group
CREATE OR REPLACE VIEW v_license_statistics AS
SELECT
lg.id AS group_id,
lg.name AS group_name,
COUNT(l.id) AS total_licenses,
COUNT(l.id) FILTER (WHERE l.status = 'active' AND l.expires_at > NOW()) AS active_licenses,
COUNT(l.id) FILTER (WHERE l.expires_at <= NOW()) AS expired_licenses,
COUNT(l.id) FILTER (WHERE l.assigned_to IS NOT NULL) AS assigned_licenses,
MIN(l.expires_at) AS earliest_expiration,
MAX(l.expires_at) AS latest_expiration
FROM license_groups lg
LEFT JOIN licenses l ON lg.id = l.group_id
GROUP BY lg.id, lg.name;
COMMENT ON VIEW v_license_statistics IS 'Shows statistics for each license group';
-- ============================================================
-- FUNCTIONS for license management
-- ============================================================
-- Function: Get license status with detailed information
CREATE OR REPLACE FUNCTION get_license_info(license_key VARCHAR)
RETURNS TABLE (
key VARCHAR,
group_name VARCHAR,
status VARCHAR,
is_expired BOOLEAN,
created_at TIMESTAMP WITH TIME ZONE,
expires_at TIMESTAMP WITH TIME ZONE,
time_remaining INTERVAL,
assigned_to VARCHAR
) AS $
BEGIN
RETURN QUERY
SELECT
l.key,
lg.name AS group_name,
l.status,
(l.expires_at <= NOW()) AS is_expired,
l.created_at,
l.expires_at,
(l.expires_at - NOW()) AS time_remaining,
l.assigned_to
FROM licenses l
JOIN license_groups lg ON l.group_id = lg.id
WHERE l.key = license_key;
END;
$ LANGUAGE plpgsql;
COMMENT ON FUNCTION get_license_info IS 'Returns detailed information about a specific license';
-- Function: Count licenses by status
CREATE OR REPLACE FUNCTION count_licenses_by_status()
RETURNS TABLE (
status VARCHAR,
count BIGINT
) AS $
BEGIN
RETURN QUERY
SELECT
l.status,
COUNT(*)::BIGINT
FROM licenses l
GROUP BY l.status;
END;
$ LANGUAGE plpgsql;
COMMENT ON FUNCTION count_licenses_by_status IS 'Returns count of licenses grouped by status';
-- Function: Get expiring licenses (within specified days)
CREATE OR REPLACE FUNCTION get_expiring_licenses(days_ahead INTEGER DEFAULT 7)
RETURNS TABLE (
key VARCHAR,
group_name VARCHAR,
expires_at TIMESTAMP WITH TIME ZONE,
days_until_expiration NUMERIC,
assigned_to VARCHAR
) AS $
BEGIN
RETURN QUERY
SELECT
l.key,
lg.name AS group_name,
l.expires_at,
EXTRACT(DAY FROM (l.expires_at - NOW()))::NUMERIC AS days_until_expiration,
l.assigned_to
FROM licenses l
JOIN license_groups lg ON l.group_id = lg.id
WHERE l.status = 'active'
AND l.expires_at > NOW()
AND l.expires_at <= NOW() + (days_ahead || ' days')::INTERVAL
ORDER BY l.expires_at ASC;
END;
$ LANGUAGE plpgsql;
COMMENT ON FUNCTION get_expiring_licenses IS 'Returns licenses expiring within specified number of days';
-- ============================================================
-- ROW LEVEL SECURITY (Optional - uncomment if needed)
-- ============================================================
-- Enable RLS on tables
-- ALTER TABLE license_groups ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE licenses ENABLE ROW LEVEL SECURITY;
-- Create policies (example - adjust based on your needs)
-- CREATE POLICY "Allow public read access to license_groups" ON license_groups
-- FOR SELECT USING (true);
-- CREATE POLICY "Allow public read access to licenses" ON licenses
-- FOR SELECT USING (true);
-- CREATE POLICY "Allow authenticated users to insert licenses" ON licenses
-- FOR INSERT WITH CHECK (auth.role() = 'authenticated');
-- CREATE POLICY "Allow authenticated users to update licenses" ON licenses
-- FOR UPDATE USING (auth.role() = 'authenticated');
-- CREATE POLICY "Allow authenticated users to delete licenses" ON licenses
-- FOR DELETE USING (auth.role() = 'authenticated');
-- ============================================================
-- INITIAL DATA (Optional - sample license groups)
-- ============================================================
-- Uncomment to insert sample license groups
-- INSERT INTO license_groups (name) VALUES
-- ('PREMIUM'),
-- ('BASIC'),
-- ('ENTERPRISE')
-- ON CONFLICT (name) DO NOTHING;
-- ============================================================
-- UTILITY FUNCTIONS
-- ============================================================
-- Function: Clean up expired licenses older than X days
CREATE OR REPLACE FUNCTION cleanup_old_expired_licenses(days_old INTEGER DEFAULT 90)
RETURNS INTEGER AS $
DECLARE
deleted_count INTEGER;
BEGIN
DELETE FROM licenses
WHERE status = 'expired'
AND expires_at < NOW() - (days_old || ' days')::INTERVAL;
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RETURN deleted_count;
END;
$ LANGUAGE plpgsql;
COMMENT ON FUNCTION cleanup_old_expired_licenses IS 'Deletes expired licenses older than specified days (default 90)';
-- Function: Bulk extend licenses by group
CREATE OR REPLACE FUNCTION bulk_extend_licenses(
target_group_name VARCHAR,
additional_days INTEGER
)
RETURNS INTEGER AS $
DECLARE
updated_count INTEGER;
target_group_id UUID;
BEGIN
-- Get group ID
SELECT id INTO target_group_id
FROM license_groups
WHERE name = target_group_name;
IF target_group_id IS NULL THEN
RAISE EXCEPTION 'License group not found: %', target_group_name;
END IF;
-- Update all licenses in the group
UPDATE licenses
SET expires_at = expires_at + (additional_days || ' days')::INTERVAL,
status = 'active'
WHERE group_id = target_group_id;
GET DIAGNOSTICS updated_count = ROW_COUNT;
RETURN updated_count;
END;
$ LANGUAGE plpgsql;
COMMENT ON FUNCTION bulk_extend_licenses IS 'Extends all licenses in a group by specified days';
-- ============================================================
-- SCHEDULED JOBS (Using pg_cron extension if available)
-- ============================================================
-- Note: pg_cron might not be available on all Supabase plans
-- Uncomment and adjust if you have pg_cron enabled
-- Enable pg_cron extension
-- CREATE EXTENSION IF NOT EXISTS pg_cron;
-- Schedule automatic status updates (runs every hour)
-- SELECT cron.schedule(
-- 'update-expired-licenses',
-- '0 * * * *', -- Every hour
-- $
-- UPDATE licenses
-- SET status = 'expired'
-- WHERE expires_at <= NOW() AND status = 'active'
-- $
-- );
-- Schedule cleanup of very old expired licenses (runs weekly)
-- SELECT cron.schedule(
-- 'cleanup-old-licenses',
-- '0 0 * * 0', -- Every Sunday at midnight
-- $
-- SELECT cleanup_old_expired_licenses(90)
-- $
-- );
-- ============================================================
-- PERFORMANCE MONITORING
-- ============================================================
-- View: Table sizes
CREATE OR REPLACE VIEW v_table_sizes AS
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size,
pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) AS table_size,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename) - pg_relation_size(schemaname||'.'||tablename)) AS index_size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
COMMENT ON VIEW v_table_sizes IS 'Shows size of all tables and their indexes';
-- ============================================================
-- AUDIT LOG (Optional - for tracking changes)
-- ============================================================
-- Uncomment if you want to track all changes to licenses
-- CREATE TABLE IF NOT EXISTS license_audit_log (
-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- license_id UUID REFERENCES licenses(id) ON DELETE SET NULL,
-- license_key VARCHAR(255),
-- action VARCHAR(50) NOT NULL, -- 'created', 'updated', 'deleted', 'extended', 'assigned'
-- old_values JSONB,
-- new_values JSONB,
-- changed_by VARCHAR(255),
-- changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
-- );
-- CREATE INDEX IF NOT EXISTS idx_audit_log_license_id ON license_audit_log(license_id);
-- CREATE INDEX IF NOT EXISTS idx_audit_log_action ON license_audit_log(action);
-- CREATE INDEX IF NOT EXISTS idx_audit_log_changed_at ON license_audit_log(changed_at);
-- -- Audit trigger function
-- CREATE OR REPLACE FUNCTION audit_license_changes()
-- RETURNS TRIGGER AS $
-- BEGIN
-- IF TG_OP = 'INSERT' THEN
-- INSERT INTO license_audit_log (license_id, license_key, action, new_values)
-- VALUES (NEW.id, NEW.key, 'created', row_to_json(NEW)::JSONB);
-- ELSIF TG_OP = 'UPDATE' THEN
-- INSERT INTO license_audit_log (license_id, license_key, action, old_values, new_values)
-- VALUES (NEW.id, NEW.key, 'updated', row_to_json(OLD)::JSONB, row_to_json(NEW)::JSONB);
-- ELSIF TG_OP = 'DELETE' THEN
-- INSERT INTO license_audit_log (license_id, license_key, action, old_values)
-- VALUES (OLD.id, OLD.key, 'deleted', row_to_json(OLD)::JSONB);
-- END IF;
-- RETURN NULL;
-- END;
-- $ LANGUAGE plpgsql;
-- -- Attach audit trigger
-- DROP TRIGGER IF EXISTS trigger_audit_license_changes ON licenses;
-- CREATE TRIGGER trigger_audit_license_changes
-- AFTER INSERT OR UPDATE OR DELETE ON licenses
-- FOR EACH ROW EXECUTE FUNCTION audit_license_changes();
-- ============================================================
-- VERIFICATION & TESTING
-- ============================================================
-- Query to verify all tables were created
SELECT
table_name,
table_type
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('license_groups', 'licenses')
ORDER BY table_name;
-- Query to verify all indexes were created
SELECT
indexname,
tablename,
indexdef
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename IN ('license_groups', 'licenses')
ORDER BY tablename, indexname;
-- Query to verify all views were created
SELECT
table_name AS view_name
FROM information_schema.views
WHERE table_schema = 'public'
AND table_name LIKE 'v_%'
ORDER BY table_name;
-- Query to verify all functions were created
SELECT
routine_name AS function_name,
routine_type
FROM information_schema.routines
WHERE routine_schema = 'public'
AND routine_type = 'FUNCTION'
ORDER BY routine_name;
-- ============================================================
-- SAMPLE QUERIES for testing
-- ============================================================
-- Test: Create a sample license group and licenses
-- INSERT INTO license_groups (name) VALUES ('TEST') RETURNING *;
-- Test: Create sample licenses
-- INSERT INTO licenses (key, group_id, expires_at, status)
-- SELECT
-- 'OP-TEST-TI-SAMPLE' || i || '-C',
-- (SELECT id FROM license_groups WHERE name = 'TEST'),
-- NOW() + interval '30 days',
-- 'active'
-- FROM generate_series(1, 5) AS i;
-- Test: Query active licenses
-- SELECT * FROM v_active_licenses WHERE group_name = 'TEST';
-- Test: Get license info
-- SELECT * FROM get_license_info('OP-TEST-TI-SAMPLE1-C');
-- Test: Get expiring licenses
-- SELECT * FROM get_expiring_licenses(30);
-- ============================================================
-- SCHEMA COMPLETE
-- ============================================================
-- Display success message
DO $
BEGIN
RAISE NOTICE '============================================================';
RAISE NOTICE 'License Management System schema created successfully!';
RAISE NOTICE '============================================================';
RAISE NOTICE 'Tables created: license_groups, licenses';
RAISE NOTICE 'Indexes created: Multiple indexes for performance';
RAISE NOTICE 'Views created: v_active_licenses, v_expired_licenses, v_license_statistics';
RAISE NOTICE 'Functions created: get_license_info, count_licenses_by_status, and more';
RAISE NOTICE '============================================================';
RAISE NOTICE 'Next steps:';
RAISE NOTICE '1. Configure your .env file with Supabase credentials';
RAISE NOTICE '2. Run: python bot.py';
RAISE NOTICE '3. Run: python api.py (in separate terminal)';
RAISE NOTICE '============================================================';
END $; ============================================================
-- LICENSE MANAGEMENT SYSTEM - SUPABASE DATABASE SCHEMA
-- ============================================================
-- Run this script in your Supabase SQL Editor to create
-- all necessary tables and indexes for the licensing system
-- ============================================================
-- Enable UUID extension (if not already enabled)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ============================================================
-- TABLE: license_groups
-- Stores license groups (e.g., Premium, Basic, Enterprise)
-- ============================================================
CREATE TABLE IF NOT EXISTS license_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- Constraints
CONSTRAINT name_not_empty CHECK (char_length(name) > 0)
);
-- Add comment for documentation
COMMENT ON TABLE license_groups IS 'Stores license groups for organizing licenses by product or tier';
COMMENT ON COLUMN license_groups.id IS 'Unique identifier for the license group';
COMMENT ON COLUMN license_groups.name IS 'Unique name of the license group (e.g., PREMIUM, BASIC)';
COMMENT ON COLUMN license_groups.created_at IS 'Timestamp when the group was created';
-- ============================================================
-- TABLE: licenses
-- Stores individual license keys with expiration and status
-- ============================================================
CREATE TABLE IF NOT EXISTS licenses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key VARCHAR(255) UNIQUE NOT NULL,
group_id UUID NOT NULL REFERENCES license_groups(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
status VARCHAR(50) DEFAULT 'active',
assigned_to VARCHAR(255),
-- Constraints
CONSTRAINT key_not_empty CHECK (char_length(key) > 0),
CONSTRAINT status_valid CHECK (status IN ('active', 'expired', 'revoked', 'suspended')),
CONSTRAINT expires_after_creation CHECK (expires_at > created_at)
);
-- Add comment for documentation
COMMENT ON TABLE licenses IS 'Stores individual license keys with metadata';
COMMENT ON COLUMN licenses.id IS 'Unique identifier for the license';
COMMENT ON COLUMN licenses.key IS 'Unique license key (e.g., OP-PREMIUM-TI-ABC123-C)';
COMMENT ON COLUMN licenses.group_id IS 'Reference to the license group this license belongs to';
COMMENT ON COLUMN licenses.created_at IS 'Timestamp when the license was created';
COMMENT ON COLUMN licenses.expires_at IS 'Timestamp when the license expires';
COMMENT ON COLUMN licenses.status IS 'Current status of the license (active, expired, revoked, suspended)';
COMMENT ON COLUMN licenses.assigned_to IS 'Optional: Username or tool identifier assigned to this license';
-- ============================================================
-- INDEXES for faster queries
-- ============================================================
-- Index on license keys for quick lookups (most common query)
CREATE INDEX IF NOT EXISTS idx_licenses_key ON licenses(key);
-- Index on group_id for filtering licenses by group
CREATE INDEX IF NOT EXISTS idx_licenses_group_id ON licenses(group_id);
-- Index on status for filtering active/expired licenses
CREATE INDEX IF NOT EXISTS idx_licenses_status ON licenses(status);
-- Index on expires_at for finding expiring licenses
CREATE INDEX IF NOT EXISTS idx_licenses_expires_at ON licenses(expires_at);
-- Index on assigned_to for finding user's licenses
CREATE INDEX IF NOT EXISTS idx_licenses_assigned_to ON licenses(assigned_to) WHERE assigned_to IS NOT NULL;
-- Index on license_groups name for quick group lookups
CREATE INDEX IF NOT EXISTS idx_license_groups_name ON license_groups(name);
-- Composite index for common query patterns
CREATE INDEX IF NOT EXISTS idx_licenses_group_status ON licenses(group_id, status);
CREATE INDEX IF NOT EXISTS idx_licenses_status_expires ON licenses(status, expires_at);
-- ============================================================
-- FUNCTIONS for automatic status updates
-- ============================================================
-- Function to automatically update license status based on expiration
CREATE OR REPLACE FUNCTION update_license_status()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.expires_at <= NOW() THEN
NEW.status := 'expired';
ELSIF NEW.status = 'expired' AND NEW.expires_at > NOW() THEN
NEW.status := 'active';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
--