KyrosDev Claude commited on
Commit
9f7505e
·
1 Parent(s): 7c68544

Fix license creation and API connectivity issues

Browse files

- Fix API endpoint paths to include /api prefix for unified server
- Ensure field name consistency across frontend, backend, and database
- Add comprehensive debugging and logging to track API requests/responses
- Improve Supabase connection error handling and user feedback
- Add debugging to license creation and data loading processes
- Fix new license button functionality with proper error handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

app/models/database.py CHANGED
@@ -14,18 +14,32 @@ class Database:
14
  self.key: str = os.environ.get("SUPABASE_SERVICE_KEY", "")
15
  self.client: Optional[Client] = None
16
 
17
- if self.url and self.key:
18
- self.client = create_client(self.url, self.key)
 
 
 
 
 
 
 
 
 
19
  else:
20
- print("⚠️ Supabase 環境變數未設定")
 
21
 
22
  def get_client(self) -> Optional[Client]:
23
  """取得 Supabase 客戶端"""
 
 
24
  return self.client
25
 
26
  def is_connected(self) -> bool:
27
  """檢查是否連接成功"""
28
- return self.client is not None
 
 
29
 
30
  # 全域資料庫實例
31
  db = Database()
 
14
  self.key: str = os.environ.get("SUPABASE_SERVICE_KEY", "")
15
  self.client: Optional[Client] = None
16
 
17
+ print(f"🔗 Initializing Supabase connection...")
18
+ print(f" URL: {self.url[:50]}{'...' if len(self.url) > 50 else ''}")
19
+ print(f" Key: {'*' * 20 if self.key else 'NOT SET'}")
20
+
21
+ if self.url and self.key and not self.url.startswith('your-') and not self.key.startswith('your-'):
22
+ try:
23
+ self.client = create_client(self.url, self.key)
24
+ print("✅ Supabase connection initialized successfully")
25
+ except Exception as e:
26
+ print(f"❌ Supabase connection failed: {e}")
27
+ self.client = None
28
  else:
29
+ print("⚠️ Supabase 環境變數未設定或使用預設值")
30
+ print(" 請在 Hugging Face Spaces Settings 或 .env 檔案中設定正確的 SUPABASE_URL 和 SUPABASE_SERVICE_KEY")
31
 
32
  def get_client(self) -> Optional[Client]:
33
  """取得 Supabase 客戶端"""
34
+ if not self.client:
35
+ print("❌ Supabase client 未初始化")
36
  return self.client
37
 
38
  def is_connected(self) -> bool:
39
  """檢查是否連接成功"""
40
+ connected = self.client is not None
41
+ print(f"🔍 Supabase connection status: {'Connected' if connected else 'Not connected'}")
42
+ return connected
43
 
44
  # 全域資料庫實例
45
  db = Database()
app/services/license_service.py CHANGED
@@ -18,6 +18,8 @@ class LicenseService:
18
 
19
  def __init__(self):
20
  self.supabase = db.get_client()
 
 
21
 
22
  def generate_license_code(self) -> str:
23
  """生成唯一授權碼"""
@@ -30,11 +32,14 @@ class LicenseService:
30
  async def create_license(self, license_data: LicenseCreate) -> Dict[str, Any]:
31
  """建立新授權"""
32
  try:
 
 
 
 
33
  license_code = self.generate_license_code()
34
  expires_at = datetime.now(timezone.utc) + timedelta(days=license_data.expires_days)
35
 
36
- # 插入授權記錄
37
- result = self.supabase.table("licenses").insert({
38
  "license_code": license_code,
39
  "user_name": license_data.user_name,
40
  "user_email": license_data.user_email,
@@ -42,7 +47,14 @@ class LicenseService:
42
  "expires_at": expires_at.isoformat(),
43
  "is_active": True,
44
  "activated_at": datetime.now(timezone.utc).isoformat() if license_data.hardware_id else None
45
- }).execute()
 
 
 
 
 
 
 
46
 
47
  if result.data:
48
  return {
@@ -56,7 +68,7 @@ class LicenseService:
56
  return {"success": False, "message": "授權建立失敗"}
57
 
58
  except Exception as e:
59
- print(f"建立授權錯誤: {e}")
60
  return {"success": False, "message": f"系統錯誤: {str(e)}"}
61
 
62
  async def activate_license(self, activation: LicenseActivation, client_ip: str = None) -> ActivationResponse:
@@ -260,10 +272,16 @@ class LicenseService:
260
  async def get_all_licenses(self) -> List[Dict[str, Any]]:
261
  """取得所有授權列表"""
262
  try:
 
 
 
 
 
263
  result = self.supabase.table("licenses").select("*").order("created_at", desc=True).execute()
 
264
  return result.data or []
265
  except Exception as e:
266
- print(f"取得授權列表錯誤: {e}")
267
  return []
268
 
269
  async def get_usage_logs(self, limit: int = 100) -> List[Dict[str, Any]]:
 
18
 
19
  def __init__(self):
20
  self.supabase = db.get_client()
21
+ if not self.supabase:
22
+ print("❌ LicenseService: Supabase client is None - all operations will fail")
23
 
24
  def generate_license_code(self) -> str:
25
  """生成唯一授權碼"""
 
32
  async def create_license(self, license_data: LicenseCreate) -> Dict[str, Any]:
33
  """建立新授權"""
34
  try:
35
+ if not self.supabase:
36
+ print("❌ create_license: Supabase client not available")
37
+ return {"success": False, "message": "資料庫連接失敗"}
38
+
39
  license_code = self.generate_license_code()
40
  expires_at = datetime.now(timezone.utc) + timedelta(days=license_data.expires_days)
41
 
42
+ license_record = {
 
43
  "license_code": license_code,
44
  "user_name": license_data.user_name,
45
  "user_email": license_data.user_email,
 
47
  "expires_at": expires_at.isoformat(),
48
  "is_active": True,
49
  "activated_at": datetime.now(timezone.utc).isoformat() if license_data.hardware_id else None
50
+ }
51
+
52
+ print(f"🔄 Creating license with data: {license_record}")
53
+
54
+ # 插入授權記錄
55
+ result = self.supabase.table("licenses").insert(license_record).execute()
56
+
57
+ print(f"✅ License creation result: {result}")
58
 
59
  if result.data:
60
  return {
 
68
  return {"success": False, "message": "授權建立失敗"}
69
 
70
  except Exception as e:
71
+ print(f"建立授權錯誤: {e}")
72
  return {"success": False, "message": f"系統錯誤: {str(e)}"}
73
 
74
  async def activate_license(self, activation: LicenseActivation, client_ip: str = None) -> ActivationResponse:
 
272
  async def get_all_licenses(self) -> List[Dict[str, Any]]:
273
  """取得所有授權列表"""
274
  try:
275
+ if not self.supabase:
276
+ print("❌ get_all_licenses: Supabase client not available")
277
+ return []
278
+
279
+ print("🔄 Fetching all licenses from Supabase...")
280
  result = self.supabase.table("licenses").select("*").order("created_at", desc=True).execute()
281
+ print(f"✅ Retrieved {len(result.data or [])} licenses from database")
282
  return result.data or []
283
  except Exception as e:
284
+ print(f"取得授權列表錯誤: {e}")
285
  return []
286
 
287
  async def get_usage_logs(self, limit: int = 100) -> List[Dict[str, Any]]:
frontend/js/api.js CHANGED
@@ -36,22 +36,32 @@ class ApiClient {
36
  config.body = JSON.stringify(config.body);
37
  }
38
 
 
 
39
  try {
40
  const response = await fetch(url, config);
41
 
 
 
42
  if (!response.ok) {
 
 
43
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
44
  }
45
 
46
  const contentType = response.headers.get('content-type');
47
  if (contentType?.includes('application/json')) {
48
- return await response.json();
 
 
49
  }
50
 
51
- return await response.text();
 
 
52
 
53
  } catch (error) {
54
- console.error(`API Error [${config.method} ${endpoint}]:`, error);
55
  throw error;
56
  }
57
  }
@@ -59,7 +69,7 @@ class ApiClient {
59
  // Health check
60
  async checkHealth() {
61
  try {
62
- await this.request('/health');
63
  return { success: true };
64
  } catch (error) {
65
  return { success: false, error: error.message };
@@ -68,42 +78,41 @@ class ApiClient {
68
 
69
  // License endpoints
70
  async getLicenses() {
71
- return this.request('/licenses');
72
  }
73
 
74
  async createLicense(licenseData) {
75
- return this.request('/licenses', {
76
  method: 'POST',
77
  body: licenseData
78
  });
79
  }
80
 
81
- async updateLicense(licenseId, licenseData) {
82
- return this.request(`/licenses/${licenseId}`, {
83
- method: 'PUT',
84
- body: licenseData
85
  });
86
  }
87
 
88
  async deleteLicense(licenseId) {
89
- return this.request(`/licenses/${licenseId}`, {
90
  method: 'DELETE'
91
  });
92
  }
93
 
94
  // Stats endpoints
95
  async getLicenseStats() {
96
- return this.request('/licenses/stats');
97
  }
98
 
99
  // Logs endpoints
100
  async getLicenseLogs(limit = 50) {
101
- return this.request(`/licenses/logs?limit=${limit}`);
102
  }
103
 
104
  // System info
105
  async getSystemInfo() {
106
- return this.request('/system/info');
107
  }
108
  }
109
 
 
36
  config.body = JSON.stringify(config.body);
37
  }
38
 
39
+ console.log(`🌐 API Request [${config.method}] ${url}`, config.body ? JSON.parse(config.body) : '(no body)');
40
+
41
  try {
42
  const response = await fetch(url, config);
43
 
44
+ console.log(`📡 API Response [${config.method}] ${url}: ${response.status} ${response.statusText}`);
45
+
46
  if (!response.ok) {
47
+ const errorText = await response.text();
48
+ console.error(`❌ API Error Response:`, errorText);
49
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
50
  }
51
 
52
  const contentType = response.headers.get('content-type');
53
  if (contentType?.includes('application/json')) {
54
+ const result = await response.json();
55
+ console.log(`✅ API Success Response:`, result);
56
+ return result;
57
  }
58
 
59
+ const textResult = await response.text();
60
+ console.log(`✅ API Text Response:`, textResult);
61
+ return textResult;
62
 
63
  } catch (error) {
64
+ console.error(`API Error [${config.method} ${endpoint}]:`, error);
65
  throw error;
66
  }
67
  }
 
69
  // Health check
70
  async checkHealth() {
71
  try {
72
+ await this.request('/api/health');
73
  return { success: true };
74
  } catch (error) {
75
  return { success: false, error: error.message };
 
78
 
79
  // License endpoints
80
  async getLicenses() {
81
+ return this.request('/api/licenses');
82
  }
83
 
84
  async createLicense(licenseData) {
85
+ return this.request('/api/license/create', {
86
  method: 'POST',
87
  body: licenseData
88
  });
89
  }
90
 
91
+ async toggleLicense(licenseId) {
92
+ return this.request(`/api/license/${licenseId}/toggle`, {
93
+ method: 'PATCH'
 
94
  });
95
  }
96
 
97
  async deleteLicense(licenseId) {
98
+ return this.request(`/api/license/${licenseId}`, {
99
  method: 'DELETE'
100
  });
101
  }
102
 
103
  // Stats endpoints
104
  async getLicenseStats() {
105
+ return this.request('/api/licenses/stats');
106
  }
107
 
108
  // Logs endpoints
109
  async getLicenseLogs(limit = 50) {
110
+ return this.request(`/api/licenses/logs?limit=${limit}`);
111
  }
112
 
113
  // System info
114
  async getSystemInfo() {
115
+ return this.request('/api/system/info');
116
  }
117
  }
118
 
frontend/js/components.js CHANGED
@@ -107,11 +107,13 @@ class Components {
107
  const formData = new FormData(form);
108
  const licenseData = {
109
  user_name: formData.get('userName'),
110
- email: formData.get('email'),
111
- valid_days: parseInt(formData.get('validDays')),
112
- notes: formData.get('notes')
113
  };
114
 
 
 
115
  const submitBtn = form.querySelector('button[type="submit"]');
116
  const originalContent = submitBtn.innerHTML;
117
 
@@ -119,17 +121,24 @@ class Components {
119
  submitBtn.disabled = true;
120
  submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 建立中...';
121
 
122
- await api.createLicense(licenseData);
123
-
124
- Utils.showSuccess('授權建立成功');
125
- this.closeModal(modal);
126
 
127
- // Refresh current page if it has a refresh method
128
- if (window.currentPage && window.currentPage.refresh) {
129
- window.currentPage.refresh();
 
 
 
 
 
 
 
130
  }
131
 
132
  } catch (error) {
 
133
  Utils.handleError(error, '建立授權時');
134
  submitBtn.disabled = false;
135
  submitBtn.innerHTML = originalContent;
 
107
  const formData = new FormData(form);
108
  const licenseData = {
109
  user_name: formData.get('userName'),
110
+ user_email: formData.get('email'),
111
+ expires_days: parseInt(formData.get('validDays')),
112
+ hardware_id: null // 可選,管理界面可以留空
113
  };
114
 
115
+ console.log('📝 License Creation Data:', licenseData);
116
+
117
  const submitBtn = form.querySelector('button[type="submit"]');
118
  const originalContent = submitBtn.innerHTML;
119
 
 
121
  submitBtn.disabled = true;
122
  submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 建立中...';
123
 
124
+ console.log('🚀 Sending create license request...');
125
+ const response = await api.createLicense(licenseData);
126
+ console.log('✅ Create license response:', response);
 
127
 
128
+ if (response && response.success) {
129
+ Utils.showSuccess('授權建立成功');
130
+ this.closeModal(modal);
131
+
132
+ // Refresh current page if it has a refresh method
133
+ if (window.currentPage && window.currentPage.refresh) {
134
+ window.currentPage.refresh();
135
+ }
136
+ } else {
137
+ throw new Error(response?.message || '建立授權失敗');
138
  }
139
 
140
  } catch (error) {
141
+ console.error('❌ Create license error:', error);
142
  Utils.handleError(error, '建立授權時');
143
  submitBtn.disabled = false;
144
  submitBtn.innerHTML = originalContent;
frontend/js/pages/users.js CHANGED
@@ -156,26 +156,33 @@ class UsersPage {
156
 
157
  async loadLicenses() {
158
  try {
 
159
  const response = await api.getLicenses();
 
160
 
161
  // 確保 licenses 是陣列
162
  if (Array.isArray(response)) {
163
  this.licenses = response;
 
164
  } else if (response && Array.isArray(response.data)) {
165
  this.licenses = response.data;
 
166
  } else if (response && typeof response === 'object') {
167
  // 如果是對象,嘗試提取可能的陣列屬性
168
  const possibleArrays = ['licenses', 'data', 'items', 'records'];
169
  for (const key of possibleArrays) {
170
  if (Array.isArray(response[key])) {
171
  this.licenses = response[key];
 
172
  break;
173
  }
174
  }
175
  if (!this.licenses) {
 
176
  this.licenses = [];
177
  }
178
  } else {
 
179
  this.licenses = [];
180
  }
181
 
@@ -184,6 +191,7 @@ class UsersPage {
184
  Utils.showSuccess('授權列表載入完成');
185
  }
186
  } catch (error) {
 
187
  Utils.handleError(error, '載入授權列表時');
188
  this.licenses = [];
189
  this.renderError();
 
156
 
157
  async loadLicenses() {
158
  try {
159
+ console.log('🔄 Loading licenses...');
160
  const response = await api.getLicenses();
161
+ console.log('📄 Licenses response:', response);
162
 
163
  // 確保 licenses 是陣列
164
  if (Array.isArray(response)) {
165
  this.licenses = response;
166
+ console.log(`✅ Loaded ${response.length} licenses (direct array)`);
167
  } else if (response && Array.isArray(response.data)) {
168
  this.licenses = response.data;
169
+ console.log(`✅ Loaded ${response.data.length} licenses (from .data property)`);
170
  } else if (response && typeof response === 'object') {
171
  // 如果是對象,嘗試提取可能的陣列屬性
172
  const possibleArrays = ['licenses', 'data', 'items', 'records'];
173
  for (const key of possibleArrays) {
174
  if (Array.isArray(response[key])) {
175
  this.licenses = response[key];
176
+ console.log(`✅ Loaded ${response[key].length} licenses (from .${key} property)`);
177
  break;
178
  }
179
  }
180
  if (!this.licenses) {
181
+ console.warn('⚠️ No valid license data found in response object, using empty array');
182
  this.licenses = [];
183
  }
184
  } else {
185
+ console.warn('⚠️ Response is not an array or object with license data, using empty array');
186
  this.licenses = [];
187
  }
188
 
 
191
  Utils.showSuccess('授權列表載入完成');
192
  }
193
  } catch (error) {
194
+ console.error('❌ Error loading licenses:', error);
195
  Utils.handleError(error, '載入授權列表時');
196
  this.licenses = [];
197
  this.renderError();