|
| 1 | +-- ============================================ |
| 2 | +-- SUPABASE SQL COMMANDS - Chạy trong SQL Editor |
| 3 | +-- ============================================ |
| 4 | +-- Vào: Supabase Dashboard → SQL Editor → New Query |
| 5 | +-- Copy và chạy từng phần dưới đây |
| 6 | + |
| 7 | +-- ============================================ |
| 8 | +-- BƯỚC 1: Enable Extensions (Nếu chưa có) |
| 9 | +-- ============================================ |
| 10 | + |
| 11 | +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; |
| 12 | +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; |
| 13 | + |
| 14 | + |
| 15 | +-- ============================================ |
| 16 | +-- BƯỚC 2: KHÔNG CẦN TẠO TABLES THỦ CÔNG |
| 17 | +-- ============================================ |
| 18 | +-- Prisma sẽ tự tạo tất cả tables khi bạn chạy: |
| 19 | +-- npx prisma db push |
| 20 | + |
| 21 | + |
| 22 | +-- ============================================ |
| 23 | +-- BƯỚC 3: (OPTIONAL) Tạo Admin User đầu tiên |
| 24 | +-- ============================================ |
| 25 | +-- Chạy SAU KHI đã push schema với Prisma |
| 26 | + |
| 27 | +-- Tạo password hash (thay 'YourPassword123' bằng password bạn muốn) |
| 28 | +-- Hoặc generate hash tại: https://bcrypt-generator.com (cost: 12) |
| 29 | + |
| 30 | +INSERT INTO "User" ( |
| 31 | + id, |
| 32 | + email, |
| 33 | + name, |
| 34 | + password, |
| 35 | + role, |
| 36 | + tier, |
| 37 | + status, |
| 38 | + "emailVerified", |
| 39 | + "createdAt", |
| 40 | + "updatedAt" |
| 41 | +) VALUES ( |
| 42 | + gen_random_uuid(), |
| 43 | + 'admin@antiscam.vn', |
| 44 | + 'Admin', |
| 45 | + -- Password hash của 'admin123' (THAY ĐỔI NÀY TRONG PRODUCTION!) |
| 46 | + '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYIwNoRKWMK', |
| 47 | + 'ADMIN', |
| 48 | + 'ENTERPRISE', |
| 49 | + 'ACTIVE', |
| 50 | + NOW(), |
| 51 | + NOW(), |
| 52 | + NOW() |
| 53 | +); |
| 54 | + |
| 55 | +-- Verify admin user đã tạo |
| 56 | +SELECT id, email, name, role, tier FROM "User" WHERE role = 'ADMIN'; |
| 57 | + |
| 58 | + |
| 59 | +-- ============================================ |
| 60 | +-- BƯỚC 4: (OPTIONAL) Create Indexes cho Performance |
| 61 | +-- ============================================ |
| 62 | +-- Prisma đã tự động tạo indexes từ schema.prisma |
| 63 | +-- Nhưng có thể thêm custom indexes nếu cần: |
| 64 | + |
| 65 | +-- Index cho search scans by domain |
| 66 | +CREATE INDEX IF NOT EXISTS idx_scan_history_domain_search |
| 67 | +ON "ScanHistory" USING gin(to_tsvector('english', domain)); |
| 68 | + |
| 69 | +-- Index cho User lookup by email (case-insensitive) |
| 70 | +CREATE INDEX IF NOT EXISTS idx_user_email_lower |
| 71 | +ON "User" (LOWER(email)); |
| 72 | + |
| 73 | + |
| 74 | +-- ============================================ |
| 75 | +-- BƯỚC 5: (OPTIONAL) Row Level Security (RLS) |
| 76 | +-- ============================================ |
| 77 | +-- Nếu muốn enable RLS cho bảo mật extra |
| 78 | + |
| 79 | +-- Enable RLS for ScanHistory |
| 80 | +ALTER TABLE "ScanHistory" ENABLE ROW LEVEL SECURITY; |
| 81 | + |
| 82 | +-- Policy: Users chỉ xem được scan của mình |
| 83 | +CREATE POLICY "Users can view own scans" ON "ScanHistory" |
| 84 | + FOR SELECT |
| 85 | + USING (auth.uid()::text = "userId"); |
| 86 | + |
| 87 | +-- Policy: Users chỉ tạo được scan với userId của mình |
| 88 | +CREATE POLICY "Users can create own scans" ON "ScanHistory" |
| 89 | + FOR INSERT |
| 90 | + WITH CHECK (auth.uid()::text = "userId"); |
| 91 | + |
| 92 | + |
| 93 | +-- ============================================ |
| 94 | +-- BƯỚC 6: Verify Database Setup |
| 95 | +-- ============================================ |
| 96 | + |
| 97 | +-- Check tất cả tables đã được tạo |
| 98 | +SELECT |
| 99 | + table_name, |
| 100 | + (SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name) as column_count |
| 101 | +FROM information_schema.tables t |
| 102 | +WHERE table_schema = 'public' |
| 103 | + AND table_type = 'BASE TABLE' |
| 104 | +ORDER BY table_name; |
| 105 | + |
| 106 | +-- Check total records trong các bảng chính |
| 107 | +SELECT |
| 108 | + 'User' as table_name, COUNT(*) as count FROM "User" |
| 109 | +UNION ALL |
| 110 | +SELECT 'ScanHistory', COUNT(*) FROM "ScanHistory" |
| 111 | +UNION ALL |
| 112 | +SELECT 'Subscription', COUNT(*) FROM "Subscription" |
| 113 | +UNION ALL |
| 114 | +SELECT 'ApiKey', COUNT(*) FROM "ApiKey" |
| 115 | +UNION ALL |
| 116 | +SELECT 'Watchlist', COUNT(*) FROM "Watchlist"; |
| 117 | + |
| 118 | + |
| 119 | +-- ============================================ |
| 120 | +-- BƯỚC 7: Useful Queries cho Monitoring |
| 121 | +-- ============================================ |
| 122 | + |
| 123 | +-- Top users by scan count |
| 124 | +SELECT |
| 125 | + u.email, |
| 126 | + u.tier, |
| 127 | + COUNT(sh.id) as total_scans |
| 128 | +FROM "User" u |
| 129 | +LEFT JOIN "ScanHistory" sh ON u.id = sh."userId" |
| 130 | +GROUP BY u.id, u.email, u.tier |
| 131 | +ORDER BY total_scans DESC |
| 132 | +LIMIT 10; |
| 133 | + |
| 134 | +-- Active subscriptions |
| 135 | +SELECT |
| 136 | + u.email, |
| 137 | + s.tier, |
| 138 | + s.status, |
| 139 | + s."currentPeriodEnd" |
| 140 | +FROM "Subscription" s |
| 141 | +JOIN "User" u ON s."userId" = u.id |
| 142 | +WHERE s.status = 'ACTIVE' |
| 143 | +ORDER BY s."createdAt" DESC; |
| 144 | + |
| 145 | +-- API usage by key |
| 146 | +SELECT |
| 147 | + u.email, |
| 148 | + ak.name as api_key_name, |
| 149 | + ak.prefix, |
| 150 | + COUNT(au.id) as total_requests, |
| 151 | + MAX(au."timestamp") as last_used |
| 152 | +FROM "ApiKey" ak |
| 153 | +JOIN "User" u ON ak."userId" = u.id |
| 154 | +LEFT JOIN "ApiUsage" au ON ak.id = au."apiKeyId" |
| 155 | +GROUP BY ak.id, u.email, ak.name, ak.prefix |
| 156 | +ORDER BY total_requests DESC; |
| 157 | + |
| 158 | + |
| 159 | +-- ============================================ |
| 160 | +-- BƯỚC 8: Cleanup (CHỈ DÙNG CHO DEV/TESTING!) |
| 161 | +-- ============================================ |
| 162 | +-- CẢNH BÁO: Các lệnh này XÓA DATA! |
| 163 | + |
| 164 | +-- Reset scan history |
| 165 | +-- TRUNCATE TABLE "ScanHistory" CASCADE; |
| 166 | + |
| 167 | +-- Reset all data (NGUY HIỂM!) |
| 168 | +-- DO $$ |
| 169 | +-- DECLARE |
| 170 | +-- r RECORD; |
| 171 | +-- BEGIN |
| 172 | +-- FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP |
| 173 | +-- EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE'; |
| 174 | +-- END LOOP; |
| 175 | +-- END $$; |
0 commit comments