diff --git a/lib/audit-logger.ts b/lib/audit-logger.ts new file mode 100644 index 0000000..bcd17ef --- /dev/null +++ b/lib/audit-logger.ts @@ -0,0 +1,61 @@ +import sqlite3 from 'sqlite3' +import path from 'path' + +export interface AuditLogEntry { + user_id?: number + action: string + resource_type: string + resource_id?: number + details?: any + ip_address?: string +} + +export async function logAuditEvent(entry: AuditLogEntry): Promise { + const dbPath = path.join(process.cwd(), 'database', 'antihoax.db') + const db = new sqlite3.Database(dbPath) + + try { + await new Promise((resolve, reject) => { + db.run( + `INSERT INTO audit_logs (user_id, action, resource_type, resource_id, details, ip_address, created_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`, + [ + entry.user_id || null, + entry.action, + entry.resource_type, + entry.resource_id || null, + entry.details ? JSON.stringify(entry.details) : null, + entry.ip_address || null + ], + (err) => { + if (err) reject(err) + else resolve() + } + ) + }) + } catch (error) { + console.error('Audit logging failed:', error) + } finally { + db.close() + } +} + +export const AuditActions = { + CREATE: 'CREATE', + UPDATE: 'UPDATE', + DELETE: 'DELETE', + APPROVE: 'APPROVE', + REJECT: 'REJECT', + LOGIN: 'LOGIN', + LOGOUT: 'LOGOUT', + BULK_IMPORT: 'BULK_IMPORT', + EXPORT: 'EXPORT' +} as const + +export const ResourceTypes = { + SOURCE: 'source', + REPORT: 'report', + USER: 'user', + CATEGORY: 'category', + API_KEY: 'api_key' +} as const \ No newline at end of file diff --git a/pages/api/admin/audit-logs.ts b/pages/api/admin/audit-logs.ts new file mode 100644 index 0000000..a4c206d --- /dev/null +++ b/pages/api/admin/audit-logs.ts @@ -0,0 +1,85 @@ +import type { NextApiRequest, NextApiResponse } from "next" +import sqlite3 from "sqlite3" +import path from "path" + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== "GET") return res.status(405).json({ error: "Method not allowed" }) + + const { page = '1', limit = '50', action, resource_type, user_id } = req.query + + const dbPath = path.join(process.cwd(), "database", "antihoax.db") + const db = new sqlite3.Database(dbPath) + + try { + let whereConditions: string[] = [] + let params: any[] = [] + + if (action) { + whereConditions.push("a.action = ?") + params.push(action) + } + + if (resource_type) { + whereConditions.push("a.resource_type = ?") + params.push(resource_type) + } + + if (user_id) { + whereConditions.push("a.user_id = ?") + params.push(parseInt(user_id as string)) + } + + const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '' + const offset = (parseInt(page as string) - 1) * parseInt(limit as string) + + const query = ` + SELECT + a.*, + u.email as user_email, + COUNT(*) OVER() as total_count + FROM audit_logs a + LEFT JOIN users u ON a.user_id = u.id + ${whereClause} + ORDER BY a.created_at DESC + LIMIT ? OFFSET ? + ` + + params.push(parseInt(limit as string), offset) + + const logs = await new Promise((resolve, reject) => { + db.all(query, params, (err, rows) => { + if (err) reject(err) + else resolve(rows) + }) + }) + + const total = logs.length > 0 ? logs[0].total_count : 0 + const totalPages = Math.ceil(total / parseInt(limit as string)) + + res.json({ + logs: logs.map(log => ({ + id: log.id, + user_id: log.user_id, + user_email: log.user_email, + action: log.action, + resource_type: log.resource_type, + resource_id: log.resource_id, + details: log.details ? JSON.parse(log.details) : null, + ip_address: log.ip_address, + created_at: log.created_at + })), + pagination: { + page: parseInt(page as string), + limit: parseInt(limit as string), + total, + totalPages + } + }) + + } catch (error) { + console.error('Audit logs error:', error) + res.status(500).json({ error: "Failed to fetch audit logs" }) + } finally { + db.close() + } +} \ No newline at end of file