/**
* 審計日誌服務
* 記錄所有 API 調用到 Firestore
* 只有 admin 權限可以查詢
*/
import { Firestore } from '@google-cloud/firestore'
let firestore: Firestore | null = null
function getFirestore(): Firestore {
if (!firestore) {
firestore = new Firestore({
projectId: process.env.GCP_PROJECT_ID,
})
}
return firestore
}
export interface AuditLogEntry {
id?: string
timestamp: Date
fsuid: string
userName: string
permission: string
tool: string
arguments: Record<string, unknown>
success: boolean
errorCode?: string
errorMessage?: string
executionTimeMs?: number
ipAddress?: string
}
const COLLECTION_NAME = 'audit_logs'
/**
* 記錄 API 調用
*/
export async function logApiCall(entry: Omit<AuditLogEntry, 'id'>): Promise<string> {
try {
const db = getFirestore()
const docRef = await db.collection(COLLECTION_NAME).add({
...entry,
timestamp: entry.timestamp.toISOString(),
})
return docRef.id
} catch (error) {
console.error('Failed to write audit log:', error)
return ''
}
}
/**
* 查詢日誌(僅 admin 可用)
*/
export async function queryLogs(options: {
fsuid?: string
tool?: string
startDate?: Date
endDate?: Date
limit?: number
offset?: number
}): Promise<AuditLogEntry[]> {
const db = getFirestore()
let query = db.collection(COLLECTION_NAME).orderBy('timestamp', 'desc')
if (options.fsuid) {
query = query.where('fsuid', '==', options.fsuid)
}
if (options.tool) {
query = query.where('tool', '==', options.tool)
}
if (options.startDate) {
query = query.where('timestamp', '>=', options.startDate.toISOString())
}
if (options.endDate) {
query = query.where('timestamp', '<=', options.endDate.toISOString())
}
if (options.offset) {
query = query.offset(options.offset)
}
query = query.limit(options.limit || 50)
const snapshot = await query.get()
return snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
timestamp: new Date(doc.data().timestamp),
})) as AuditLogEntry[]
}
/**
* 取得日誌統計
*/
export async function getLogStats(fsuid?: string): Promise<{
totalCalls: number
successCount: number
errorCount: number
toolUsage: Record<string, number>
}> {
const db = getFirestore()
let query = db.collection(COLLECTION_NAME)
if (fsuid) {
query = query.where('fsuid', '==', fsuid) as any
}
const snapshot = await query.get()
const stats = {
totalCalls: 0,
successCount: 0,
errorCount: 0,
toolUsage: {} as Record<string, number>,
}
snapshot.docs.forEach(doc => {
const data = doc.data()
stats.totalCalls++
if (data.success) {
stats.successCount++
} else {
stats.errorCount++
}
stats.toolUsage[data.tool] = (stats.toolUsage[data.tool] || 0) + 1
})
return stats
}