kstools-license-manager / supabase-schema.sql
KyrosDev's picture
重新實施機器名稱綁定功能
b37d8d7
-- =====================================================
-- 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;