企業內部新聞彙整與分析系統 - 自動新聞抓取 (Digitimes, 經濟日報, 工商時報) - AI 智慧摘要 (OpenAI/Claude/Ollama) - 群組管理與訂閱通知 - 已清理 Python 快取檔案 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
431 lines
8.8 KiB
JavaScript
431 lines
8.8 KiB
JavaScript
/**
|
|
* API 服務層 - 處理所有後端 API 呼叫
|
|
*/
|
|
|
|
const API_BASE_URL = '/api/v1';
|
|
|
|
class ApiService {
|
|
constructor() {
|
|
this.baseUrl = API_BASE_URL;
|
|
}
|
|
|
|
/**
|
|
* 取得認證 Token
|
|
*/
|
|
getToken() {
|
|
return localStorage.getItem('token');
|
|
}
|
|
|
|
/**
|
|
* 設定認證 Token
|
|
*/
|
|
setToken(token) {
|
|
localStorage.setItem('token', token);
|
|
}
|
|
|
|
/**
|
|
* 清除認證 Token
|
|
*/
|
|
clearToken() {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('user');
|
|
}
|
|
|
|
/**
|
|
* 建立請求標頭
|
|
*/
|
|
getHeaders(includeAuth = true) {
|
|
const headers = {
|
|
'Content-Type': 'application/json'
|
|
};
|
|
if (includeAuth) {
|
|
const token = this.getToken();
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* 發送 API 請求
|
|
*/
|
|
async request(endpoint, options = {}) {
|
|
const url = `${this.baseUrl}${endpoint}`;
|
|
const config = {
|
|
headers: this.getHeaders(options.auth !== false),
|
|
...options
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
|
|
// 處理 401 未授權
|
|
if (response.status === 401) {
|
|
this.clearToken();
|
|
window.location.reload();
|
|
throw new Error('登入已過期,請重新登入');
|
|
}
|
|
|
|
// 處理 204 無內容
|
|
if (response.status === 204) {
|
|
return { success: true };
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.detail || '請求失敗');
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('API Error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET 請求
|
|
*/
|
|
async get(endpoint, params = {}) {
|
|
const queryString = new URLSearchParams(params).toString();
|
|
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
|
|
return this.request(url, { method: 'GET' });
|
|
}
|
|
|
|
/**
|
|
* POST 請求
|
|
*/
|
|
async post(endpoint, data = {}, options = {}) {
|
|
return this.request(endpoint, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
...options
|
|
});
|
|
}
|
|
|
|
/**
|
|
* PUT 請求
|
|
*/
|
|
async put(endpoint, data = {}) {
|
|
return this.request(endpoint, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* DELETE 請求
|
|
*/
|
|
async delete(endpoint) {
|
|
return this.request(endpoint, { method: 'DELETE' });
|
|
}
|
|
|
|
/**
|
|
* 上傳檔案
|
|
*/
|
|
async upload(endpoint, formData) {
|
|
const token = this.getToken();
|
|
const headers = {};
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
throw new Error(data.detail || '上傳失敗');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
}
|
|
|
|
// 建立全域 API 實例 (必須在其他 API 模組之前)
|
|
const api = new ApiService();
|
|
|
|
// ============ 認證 API ============
|
|
const authApi = {
|
|
/**
|
|
* 登入
|
|
*/
|
|
async login(username, password, authType = 'local') {
|
|
const response = await api.post('/auth/login', {
|
|
username,
|
|
password,
|
|
auth_type: authType
|
|
}, { auth: false });
|
|
|
|
if (response.token) {
|
|
api.setToken(response.token);
|
|
localStorage.setItem('user', JSON.stringify(response.user));
|
|
}
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* 登出
|
|
*/
|
|
async logout() {
|
|
try {
|
|
await api.post('/auth/logout');
|
|
} finally {
|
|
api.clearToken();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 取得當前用戶
|
|
*/
|
|
async getMe() {
|
|
return api.get('/auth/me');
|
|
},
|
|
|
|
/**
|
|
* 檢查是否已登入
|
|
*/
|
|
isLoggedIn() {
|
|
return !!api.getToken();
|
|
},
|
|
|
|
/**
|
|
* 取得本地儲存的用戶資訊
|
|
*/
|
|
getUser() {
|
|
const user = localStorage.getItem('user');
|
|
return user ? JSON.parse(user) : null;
|
|
}
|
|
};
|
|
|
|
// ============ 用戶管理 API ============
|
|
const usersApi = {
|
|
/**
|
|
* 取得用戶列表
|
|
*/
|
|
async getList(params = {}) {
|
|
return api.get('/users', params);
|
|
},
|
|
|
|
/**
|
|
* 取得單一用戶
|
|
*/
|
|
async getById(userId) {
|
|
return api.get(`/users/${userId}`);
|
|
},
|
|
|
|
/**
|
|
* 建立用戶
|
|
*/
|
|
async create(userData) {
|
|
return api.post('/users', userData);
|
|
},
|
|
|
|
/**
|
|
* 更新用戶
|
|
*/
|
|
async update(userId, userData) {
|
|
return api.put(`/users/${userId}`, userData);
|
|
},
|
|
|
|
/**
|
|
* 刪除用戶
|
|
*/
|
|
async delete(userId) {
|
|
return api.delete(`/users/${userId}`);
|
|
}
|
|
};
|
|
|
|
// ============ 群組管理 API ============
|
|
const groupsApi = {
|
|
/**
|
|
* 取得群組列表
|
|
*/
|
|
async getList(params = {}) {
|
|
return api.get('/groups', params);
|
|
},
|
|
|
|
/**
|
|
* 取得群組詳情
|
|
*/
|
|
async getById(groupId) {
|
|
return api.get(`/groups/${groupId}`);
|
|
},
|
|
|
|
/**
|
|
* 建立群組
|
|
*/
|
|
async create(groupData) {
|
|
return api.post('/groups', groupData);
|
|
},
|
|
|
|
/**
|
|
* 更新群組
|
|
*/
|
|
async update(groupId, groupData) {
|
|
return api.put(`/groups/${groupId}`, groupData);
|
|
},
|
|
|
|
/**
|
|
* 刪除群組
|
|
*/
|
|
async delete(groupId) {
|
|
return api.delete(`/groups/${groupId}`);
|
|
},
|
|
|
|
/**
|
|
* 取得群組關鍵字
|
|
*/
|
|
async getKeywords(groupId) {
|
|
return api.get(`/groups/${groupId}/keywords`);
|
|
},
|
|
|
|
/**
|
|
* 新增關鍵字
|
|
*/
|
|
async addKeyword(groupId, keyword) {
|
|
return api.post(`/groups/${groupId}/keywords`, { keyword });
|
|
},
|
|
|
|
/**
|
|
* 刪除關鍵字
|
|
*/
|
|
async deleteKeyword(groupId, keywordId) {
|
|
return api.delete(`/groups/${groupId}/keywords/${keywordId}`);
|
|
}
|
|
};
|
|
|
|
// ============ 訂閱管理 API ============
|
|
const subscriptionsApi = {
|
|
/**
|
|
* 取得我的訂閱
|
|
*/
|
|
async getMySubscriptions() {
|
|
return api.get('/subscriptions');
|
|
},
|
|
|
|
/**
|
|
* 更新訂閱
|
|
*/
|
|
async update(subscriptions) {
|
|
return api.put('/subscriptions', { subscriptions });
|
|
}
|
|
};
|
|
|
|
// ============ 報告管理 API ============
|
|
const reportsApi = {
|
|
/**
|
|
* 取得報告列表
|
|
*/
|
|
async getList(params = {}) {
|
|
return api.get('/reports', params);
|
|
},
|
|
|
|
/**
|
|
* 取得今日報告
|
|
*/
|
|
async getToday() {
|
|
return api.get('/reports/today');
|
|
},
|
|
|
|
/**
|
|
* 取得報告詳情
|
|
*/
|
|
async getById(reportId) {
|
|
return api.get(`/reports/${reportId}`);
|
|
},
|
|
|
|
/**
|
|
* 更新報告
|
|
*/
|
|
async update(reportId, reportData) {
|
|
return api.put(`/reports/${reportId}`, reportData);
|
|
},
|
|
|
|
/**
|
|
* 發布報告
|
|
*/
|
|
async publish(reportId) {
|
|
return api.post(`/reports/${reportId}/publish`);
|
|
},
|
|
|
|
/**
|
|
* 重新產生 AI 摘要
|
|
*/
|
|
async regenerateSummary(reportId) {
|
|
return api.post(`/reports/${reportId}/regenerate-summary`);
|
|
},
|
|
|
|
/**
|
|
* 匯出報告 PDF
|
|
*/
|
|
async exportPdf(reportId) {
|
|
const token = api.getToken();
|
|
const response = await fetch(`${API_BASE_URL}/reports/${reportId}/export`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('匯出失敗');
|
|
}
|
|
|
|
return response.blob();
|
|
}
|
|
};
|
|
|
|
// ============ 系統設定 API ============
|
|
const settingsApi = {
|
|
/**
|
|
* 取得系統設定
|
|
*/
|
|
async get() {
|
|
return api.get('/settings');
|
|
},
|
|
|
|
/**
|
|
* 更新系統設定
|
|
*/
|
|
async update(settings) {
|
|
return api.put('/settings', settings);
|
|
},
|
|
|
|
/**
|
|
* 測試 LLM 連線
|
|
*/
|
|
async testLlm() {
|
|
return api.post('/settings/llm/test');
|
|
},
|
|
|
|
/**
|
|
* 上傳 PDF Logo
|
|
*/
|
|
async uploadLogo(file) {
|
|
const formData = new FormData();
|
|
formData.append('logo', file);
|
|
return api.upload('/settings/pdf/logo', formData);
|
|
},
|
|
|
|
/**
|
|
* 取得管理員儀表板數據
|
|
*/
|
|
async getAdminDashboard() {
|
|
return api.get('/settings/dashboard/admin');
|
|
}
|
|
};
|
|
|
|
// 匯出所有 API 到 window
|
|
window.api = api;
|
|
window.authApi = authApi;
|
|
window.usersApi = usersApi;
|
|
window.groupsApi = groupsApi;
|
|
window.subscriptionsApi = subscriptionsApi;
|
|
window.reportsApi = reportsApi;
|
|
window.settingsApi = settingsApi;
|