File size: 17,432 Bytes
ad67bb9 b37d8d7 ad67bb9 e0ffce0 ad67bb9 b37d8d7 ad67bb9 b37d8d7 ad67bb9 b37d8d7 ad67bb9 b37d8d7 ad67bb9 ebe14fd ad67bb9 b37d8d7 ad67bb9 ebe14fd a6a49d1 ebe14fd a6a49d1 ebe14fd b37d8d7 ebe14fd a6a49d1 5265c10 e184b24 a6a49d1 e184b24 a6a49d1 e184b24 ebe14fd ad67bb9 a6a49d1 b37d8d7 a6a49d1 b37d8d7 79daae2 a6a49d1 b37d8d7 79daae2 a6a49d1 b37d8d7 79daae2 b1569ac b37d8d7 79daae2 b37d8d7 79daae2 b1569ac b37d8d7 79daae2 b1569ac b37d8d7 ebe14fd ad67bb9 b37d8d7 79daae2 b37d8d7 79daae2 b37d8d7 79daae2 b1569ac b37d8d7 b1569ac b37d8d7 b1569ac 79daae2 b37d8d7 79daae2 b37d8d7 79daae2 b37d8d7 79daae2 b37d8d7 ad67bb9 |
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 |
-- =====================================================
-- KSTools License Manager - Supabase 資料庫架構
-- =====================================================
-- 清理現有資料表 (如果存在)
DROP TABLE IF EXISTS usage_logs CASCADE;
DROP TABLE IF EXISTS licenses CASCADE;
-- =====================================================
-- 授權表 (licenses)
-- =====================================================
CREATE TABLE licenses (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
license_code VARCHAR(50) UNIQUE NOT NULL,
hardware_id VARCHAR(100),
machine_name VARCHAR(255),
user_name VARCHAR(255) NOT NULL,
user_email VARCHAR(255),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
is_active BOOLEAN DEFAULT true,
activated_at TIMESTAMP WITH TIME ZONE,
last_used_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 授權表註解
COMMENT ON TABLE licenses IS 'KSTools 授權管理表';
COMMENT ON COLUMN licenses.id IS '唯一識別碼';
COMMENT ON COLUMN licenses.license_code IS '授權碼 (格式: 32位隨機字符,支援XXXX-XXXX-XXXX-XXXX分段顯示)';
COMMENT ON COLUMN licenses.hardware_id IS '硬體指紋ID';
COMMENT ON COLUMN licenses.machine_name IS '機器名稱';
COMMENT ON COLUMN licenses.user_name IS '授權用戶名稱';
COMMENT ON COLUMN licenses.user_email IS '用戶電子郵件';
COMMENT ON COLUMN licenses.expires_at IS '授權到期時間';
COMMENT ON COLUMN licenses.is_active IS '是否啟用';
COMMENT ON COLUMN licenses.activated_at IS '首次啟用時間';
COMMENT ON COLUMN licenses.last_used_at IS '最後使用時間';
-- =====================================================
-- 使用記錄表 (usage_logs)
-- =====================================================
CREATE TABLE usage_logs (
id BIGSERIAL PRIMARY KEY,
license_id UUID REFERENCES licenses(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL, -- 'activate', 'validate', 'error'
ip_address INET,
user_agent TEXT,
hardware_info TEXT,
machine_name VARCHAR(255),
error_message TEXT, -- 錯誤訊息 (如果有)
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 使用記錄表註解
COMMENT ON TABLE usage_logs IS 'KSTools 授權使用記錄表';
COMMENT ON COLUMN usage_logs.license_id IS '關聯的授權ID';
COMMENT ON COLUMN usage_logs.action IS '動作類型: activate/validate/error';
COMMENT ON COLUMN usage_logs.ip_address IS '客戶端IP地址';
COMMENT ON COLUMN usage_logs.user_agent IS '用戶代理字符串';
COMMENT ON COLUMN usage_logs.hardware_info IS '硬體資訊';
COMMENT ON COLUMN usage_logs.machine_name IS '機器名稱';
COMMENT ON COLUMN usage_logs.error_message IS '錯誤訊息 (發生錯誤時)';
-- =====================================================
-- 索引優化
-- =====================================================
-- 授權表索引
CREATE INDEX idx_licenses_code ON licenses(license_code);
CREATE INDEX idx_licenses_hardware ON licenses(hardware_id);
CREATE INDEX idx_licenses_machine_name ON licenses(machine_name);
CREATE INDEX idx_licenses_active ON licenses(is_active, expires_at);
CREATE INDEX idx_licenses_user_email ON licenses(user_email);
CREATE INDEX idx_licenses_created_at ON licenses(created_at);
-- 使用記錄表索引
CREATE INDEX idx_usage_logs_license ON usage_logs(license_id);
CREATE INDEX idx_usage_logs_created ON usage_logs(created_at);
CREATE INDEX idx_usage_logs_action ON usage_logs(action);
CREATE INDEX idx_usage_logs_ip ON usage_logs(ip_address);
-- =====================================================
-- 觸發器 - 自動更新 updated_at
-- =====================================================
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_licenses_updated_at
BEFORE UPDATE ON licenses
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- =====================================================
-- RLS (Row Level Security) 政策
-- =====================================================
-- 啟用 RLS
ALTER TABLE licenses ENABLE ROW LEVEL SECURITY;
ALTER TABLE usage_logs ENABLE ROW LEVEL SECURITY;
-- 授權表政策 (允許所有操作 - 由應用程式層控制權限)
CREATE POLICY "Allow all operations on licenses" ON licenses
FOR ALL USING (true);
-- 使用記錄表政策 (允許插入和查詢)
CREATE POLICY "Allow insert and select on usage_logs" ON usage_logs
FOR ALL USING (true);
-- =====================================================
-- 視圖 - 授權統計
-- =====================================================
CREATE OR REPLACE VIEW license_stats AS
SELECT
COUNT(*) as total_licenses,
COUNT(*) FILTER (WHERE is_active = true AND expires_at > NOW()) as active_licenses,
COUNT(*) FILTER (WHERE is_active = false) as inactive_licenses,
COUNT(*) FILTER (WHERE expires_at <= NOW()) as expired_licenses,
COUNT(*) FILTER (WHERE is_active = true AND expires_at > NOW()) as valid_licenses,
COUNT(*) FILTER (WHERE activated_at >= CURRENT_DATE) as today_activations,
COUNT(*) FILTER (WHERE activated_at >= CURRENT_DATE - INTERVAL '1 day' AND activated_at < CURRENT_DATE) as yesterday_activations,
COUNT(*) FILTER (WHERE activated_at >= CURRENT_DATE - INTERVAL '7 days') as this_week_activations,
COUNT(*) FILTER (WHERE activated_at >= CURRENT_DATE - INTERVAL '30 days') as this_month_activations
FROM licenses;
COMMENT ON VIEW license_stats IS '授權統計視圖';
-- =====================================================
-- 視圖 - 授權詳細資訊 (含使用記錄)
-- =====================================================
CREATE OR REPLACE VIEW license_details AS
SELECT
l.*,
(SELECT COUNT(*) FROM usage_logs ul WHERE ul.license_id = l.id) as usage_count,
(SELECT MAX(created_at) FROM usage_logs ul WHERE ul.license_id = l.id AND action = 'validate') as last_validation,
CASE
WHEN l.expires_at <= NOW() THEN 'expired'
WHEN l.is_active = false THEN 'inactive'
WHEN l.activated_at IS NULL THEN 'not_activated'
ELSE 'active'
END as status,
l.machine_name as device_name
FROM licenses l;
COMMENT ON VIEW license_details IS '授權詳細資訊視圖 (含統計)';
-- =====================================================
-- 視圖 - 活動記錄 (前端儀表板使用)
-- =====================================================
-- 重新創建 activity_logs 視圖
DROP VIEW IF EXISTS activity_logs;
CREATE VIEW activity_logs AS
SELECT
ul.id,
ul.license_id,
ul.action,
ul.ip_address,
ul.user_agent,
ul.hardware_info,
ul.machine_name,
ul.error_message,
ul.created_at,
l.license_code,
l.user_name,
l.user_email,
CASE
-- 管理員操作:直接顯示為成功,不判斷 error_message
WHEN ul.action IN ('create', 'extend', 'activate_license', 'deactivate_license', 'delete') THEN ul.action
-- Plugin 操作:有 error_message 就是錯誤
WHEN ul.action IN ('activate', 'validate') THEN
CASE
WHEN ul.error_message IS NOT NULL THEN 'error'
ELSE ul.action
END
-- 其他操作:保持原邏輯
ELSE
CASE
WHEN ul.error_message IS NOT NULL THEN 'error'
ELSE ul.action
END
END as activity_type
FROM usage_logs ul
LEFT JOIN licenses l ON ul.license_id = l.id
ORDER BY ul.created_at DESC;
COMMENT ON VIEW activity_logs IS '活動記錄視圖 (前端儀表板使用)';
-- =====================================================
-- 函數 - 清理過期記錄
-- =====================================================
CREATE OR REPLACE FUNCTION cleanup_old_logs(days_to_keep INTEGER DEFAULT 90)
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
DELETE FROM usage_logs
WHERE created_at < NOW() - (days_to_keep || ' days')::INTERVAL;
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION cleanup_old_logs IS '清理指定天數前的使用記錄';
-- =====================================================
-- 函數 - 生成授權統計報告
-- =====================================================
CREATE OR REPLACE FUNCTION generate_license_report(
start_date DATE DEFAULT CURRENT_DATE - INTERVAL '30 days',
end_date DATE DEFAULT CURRENT_DATE
)
RETURNS TABLE (
date DATE,
new_licenses INTEGER,
activations INTEGER,
validations INTEGER,
errors INTEGER
) AS $$
BEGIN
RETURN QUERY
SELECT
d.date,
COALESCE(l.new_licenses, 0) as new_licenses,
COALESCE(ul_activate.activations, 0) as activations,
COALESCE(ul_validate.validations, 0) as validations,
COALESCE(ul_error.errors, 0) as errors
FROM (
SELECT generate_series(start_date, end_date, '1 day'::interval)::date as date
) d
LEFT JOIN (
SELECT created_at::date as date, COUNT(*) as new_licenses
FROM licenses
WHERE created_at::date BETWEEN start_date AND end_date
GROUP BY created_at::date
) l ON d.date = l.date
LEFT JOIN (
SELECT created_at::date as date, COUNT(*) as activations
FROM usage_logs
WHERE action = 'activate' AND created_at::date BETWEEN start_date AND end_date
GROUP BY created_at::date
) ul_activate ON d.date = ul_activate.date
LEFT JOIN (
SELECT created_at::date as date, COUNT(*) as validations
FROM usage_logs
WHERE action = 'validate' AND created_at::date BETWEEN start_date AND end_date
GROUP BY created_at::date
) ul_validate ON d.date = ul_validate.date
LEFT JOIN (
SELECT created_at::date as date, COUNT(*) as errors
FROM usage_logs
WHERE error_message IS NOT NULL AND created_at::date BETWEEN start_date AND end_date
GROUP BY created_at::date
) ul_error ON d.date = ul_error.date
ORDER BY d.date;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION generate_license_report IS '生成指定日期範圍的授權統計報告';
-- =====================================================
-- 示例資料 (可選)
-- =====================================================
-- 插入測試用授權 (啟用來測試系統) - 統一使用台灣時間00:00計算(-8hr)
INSERT INTO licenses (license_code, user_name, user_email, expires_at, hardware_id, machine_name, is_active, activated_at) VALUES
-- 已啟用用戶 (正常使用中)
('A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6', '王小明', 'ming@company.com.tw', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '90 days' - INTERVAL '1 second', 'CPU123ABC456DEF789GHI012JKL345MN', 'DESKTOP-MING-PC', true, NOW() - INTERVAL '5 days'),
('X7Y8Z9W0Q1R2S3T4U5V6A7B8C9D0E1F2', '李美華', 'li.meihua@design.studio', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '365 days' - INTERVAL '1 second', 'CPU789XYZ012ABC345DEF678GHI901JK', 'DESIGN-WORKSTATION-02', true, NOW() - INTERVAL '30 days'),
-- 試用用戶 (7天試用)
('B9C8D7E6F5G4H3I2J1K0L9M8N7O6P5Q4', '張工程師', 'engineer.zhang@tech.com', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '7 days' - INTERVAL '1 second', 'CPUABC123XYZ456QWE789RTY012UIU34', 'ENGINEER-LAPTOP-01', true, NOW() - INTERVAL '2 days'),
-- 更多正常用戶
('M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8A9B0', '陳建築師', 'chen@architect.firm', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '30 days' - INTERVAL '1 second', 'CPUARCH456BUILD789DESIGN012PROJ34', 'ARCHITECT-STUDIO-PC', true, NOW() - INTERVAL '1 day'),
('F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8', '劉設計師', 'liu.designer@studio.tw', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '60 days' - INTERVAL '1 second', 'CPUDESIGN123CREATIVE456ART789STUD', 'CREATIVE-WORKSTATION', true, NOW() - INTERVAL '7 days'),
-- 過期授權 (已過期15天)
('Z0Y9X8W7V6U5T4S3R2Q1P0O9N8M7L6K5', '周老闆', 'boss.zhou@construction.co', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' - INTERVAL '15 days' - INTERVAL '1 second', 'CPU456DEF789ABC012GHI345JKL678MN', 'BOSS-OFFICE-PC', true, NOW() - INTERVAL '45 days'),
-- 已停用授權 (管理員手動停用)
('Q2W3E4R5T6Y7U8I9O0P1A2S3D4F5G6H7', '黃違規用戶', 'huang@suspended.user', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '20 days' - INTERVAL '1 second', 'CPUXYZ789ABC012DEF345GHI678JKL90', 'SUSPENDED-PC', false, NOW() - INTERVAL '10 days'),
-- 永久授權 (企業用戶) - 50年
('K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6', 'KSTools管理員', 'admin@kstools.com.tw', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '18250 days' - INTERVAL '1 second', 'CPUADMIN123MASTER456CONTROL789AB', 'ADMIN-MASTER-SERVER', true, NOW() - INTERVAL '1 day'),
-- 即將過期 (今天結束就過期)
('V8W9X0Y1Z2A3B4C5D6E7F8G9H0I1J2K3', '趙用戶', 'zhao@expires.soon', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '1 day' - INTERVAL '1 second', 'CPU999END888SOON777EXPIRE666ABC', 'EXPIRING-PC-TODAY', true, NOW() - INTERVAL '28 days')
ON CONFLICT (license_code) DO NOTHING;
-- 插入測試使用記錄
INSERT INTO usage_logs (license_id, action, ip_address, hardware_info, machine_name) VALUES
-- 正常用戶的使用記錄
((SELECT id FROM licenses WHERE license_code = 'A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6'), 'activate', '192.168.1.100', 'CPU123ABC456DEF789GHI012JKL345MN', 'DESKTOP-MING-PC'),
((SELECT id FROM licenses WHERE license_code = 'A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6'), 'validate', '192.168.1.100', 'CPU123ABC456DEF789GHI012JKL345MN', 'DESKTOP-MING-PC'),
((SELECT id FROM licenses WHERE license_code = 'A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6'), 'validate', '192.168.1.100', 'CPU123ABC456DEF789GHI012JKL345MN', 'DESKTOP-MING-PC'),
-- 企業用戶的使用記錄
((SELECT id FROM licenses WHERE license_code = 'X7Y8Z9W0Q1R2S3T4U5V6A7B8C9D0E1F2'), 'activate', '10.0.1.50', 'CPU789XYZ012ABC345DEF678GHI901JK', 'DESIGN-WORKSTATION-02'),
((SELECT id FROM licenses WHERE license_code = 'X7Y8Z9W0Q1R2S3T4U5V6A7B8C9D0E1F2'), 'validate', '10.0.1.50', 'CPU789XYZ012ABC345DEF678GHI901JK', 'DESIGN-WORKSTATION-02'),
-- 其他正常用戶記錄
((SELECT id FROM licenses WHERE license_code = 'M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8A9B0'), 'activate', '192.168.50.20', 'CPUARCH456BUILD789DESIGN012PROJ34', 'ARCHITECT-STUDIO-PC'),
((SELECT id FROM licenses WHERE license_code = 'M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8A9B0'), 'validate', '192.168.50.20', 'CPUARCH456BUILD789DESIGN012PROJ34', 'ARCHITECT-STUDIO-PC'),
((SELECT id FROM licenses WHERE license_code = 'F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8'), 'activate', '172.20.10.15', 'CPUDESIGN123CREATIVE456ART789STUD', 'CREATIVE-WORKSTATION'),
((SELECT id FROM licenses WHERE license_code = 'F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8'), 'validate', '172.20.10.15', 'CPUDESIGN123CREATIVE456ART789STUD', 'CREATIVE-WORKSTATION'),
-- 試用用戶的記錄
((SELECT id FROM licenses WHERE license_code = 'B9C8D7E6F5G4H3I2J1K0L9M8N7O6P5Q4'), 'activate', '203.74.123.45', 'CPUABC123XYZ456QWE789RTY012UIU34', 'ENGINEER-LAPTOP-01'),
((SELECT id FROM licenses WHERE license_code = 'B9C8D7E6F5G4H3I2J1K0L9M8N7O6P5Q4'), 'validate', '203.74.123.45', 'CPUABC123XYZ456QWE789RTY012UIU34', 'ENGINEER-LAPTOP-01'),
-- 過期用戶的歷史記錄
((SELECT id FROM licenses WHERE license_code = 'Z0Y9X8W7V6U5T4S3R2Q1P0O9N8M7L6K5'), 'activate', '172.16.0.10', 'CPU456DEF789ABC012GHI345JKL678MN', 'BOSS-OFFICE-PC'),
((SELECT id FROM licenses WHERE license_code = 'Z0Y9X8W7V6U5T4S3R2Q1P0O9N8M7L6K5'), 'validate', '172.16.0.10', 'CPU456DEF789ABC012GHI345JKL678MN', 'BOSS-OFFICE-PC'),
-- 管理員測試記錄
((SELECT id FROM licenses WHERE license_code = 'K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6'), 'activate', '127.0.0.1', 'CPUADMIN123MASTER456CONTROL789AB', 'ADMIN-MASTER-SERVER'),
((SELECT id FROM licenses WHERE license_code = 'K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6'), 'validate', '127.0.0.1', 'CPUADMIN123MASTER456CONTROL789AB', 'ADMIN-MASTER-SERVER'),
-- 即將過期用戶的記錄
((SELECT id FROM licenses WHERE license_code = 'V8W9X0Y1Z2A3B4C5D6E7F8G9H0I1J2K3'), 'activate', '118.163.88.99', 'CPU999END888SOON777EXPIRE666ABC', 'EXPIRING-PC-TODAY'),
((SELECT id FROM licenses WHERE license_code = 'V8W9X0Y1Z2A3B4C5D6E7F8G9H0I1J2K3'), 'validate', '118.163.88.99', 'CPU999END888SOON777EXPIRE666ABC', 'EXPIRING-PC-TODAY');
-- =====================================================
-- 權限設定 (建議)
-- =====================================================
-- 如果需要建立專用的應用程式角色
-- CREATE ROLE kstools_app_role;
-- GRANT SELECT, INSERT, UPDATE, DELETE ON licenses TO kstools_app_role;
-- GRANT SELECT, INSERT ON usage_logs TO kstools_app_role;
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO kstools_app_role;
-- =====================================================
-- 完成設定
-- =====================================================
-- 驗證資料表建立
SELECT
schemaname,
tablename,
tableowner
FROM pg_tables
WHERE tablename IN ('licenses', 'usage_logs')
ORDER BY tablename;
-- 驗證索引建立
SELECT
indexname,
tablename,
indexdef
FROM pg_indexes
WHERE tablename IN ('licenses', 'usage_logs')
ORDER BY tablename, indexname; |