strapi-plugin-audit-logs
Version:
Comprehensive audit logging plugin for Strapi v5 that tracks all user interactions and system events with a clean admin interface and automatic cleanup
235 lines (199 loc) • 6.72 kB
JavaScript
module.exports = ({ strapi }) => ({
async find(ctx) {
try {
const { query } = ctx.request;
// Parse and validate pagination parameters
const page = Math.max(parseInt(query.page) || 1, 1);
const pageSize = Math.min(
Math.max(parseInt(query.pageSize) || 25, 1),
100
);
const start = (page - 1) * pageSize;
const limit = pageSize;
// Parse and validate sorting - always prioritize date
let sort = { date: "desc", id: "desc" }; // Secondary sort by ID for consistency
if (query.sort) {
const [field, order] = query.sort.split(":");
const allowedFields = [
"action",
"date",
"method",
"statusCode",
"userDisplayName",
"endpoint",
"ipAddress",
];
const allowedOrders = ["asc", "desc"];
if (allowedFields.includes(field) && allowedOrders.includes(order)) {
// Always include date as primary sort, then the requested field
if (field === "date") {
sort = { date: order, id: "desc" };
} else {
sort = { date: "desc", [field]: order, id: "desc" };
}
}
}
// Parse and validate filters using Strapi v5 Document Service API syntax
const filters = {};
// Handle action filter (the frontend sends 'action' parameter)
if (query.action && typeof query.action === "string") {
filters.action = { $eq: query.action.trim() };
}
if (query.user && typeof query.user === "string") {
filters.userDisplayName = { $containsi: query.user.trim() };
}
if (query.dateFrom) {
const dateFrom = new Date(query.dateFrom);
if (!isNaN(dateFrom.getTime())) {
filters.date = filters.date || {};
filters.date.$gte = dateFrom.toISOString();
}
}
if (query.dateTo) {
const dateTo = new Date(query.dateTo);
if (!isNaN(dateTo.getTime())) {
filters.date = filters.date || {};
filters.date.$lte = dateTo.toISOString();
}
}
const auditLogService = strapi.plugin("audit-logs").service("log");
// Debug log to see what filters are being applied
strapi.log.debug("Audit logs query filters:", {
filters,
sort,
start,
limit,
originalQuery: query,
});
// Check if the service and content type exist
if (!auditLogService) {
strapi.log.error("Audit log service not found");
ctx.body = {
data: [],
meta: { pagination: { page, pageSize, pageCount: 0, total: 0 } },
};
return;
}
let logs = [];
let total = 0;
try {
[logs, total] = await Promise.all([
auditLogService.findMany({
filters,
sort,
start,
limit,
}),
auditLogService.count({ filters }),
]);
} catch (serviceError) {
strapi.log.error("Service call error:", serviceError);
// If content type doesn't exist yet or no data, return empty result
logs = [];
total = 0;
}
const pagination = {
page,
pageSize,
pageCount: Math.ceil(total / pageSize),
total,
};
ctx.body = {
data: logs,
meta: { pagination },
};
} catch (error) {
strapi.log.error("Failed to fetch audit logs:", error);
strapi.log.error("Error details:", {
message: error.message,
stack: error.stack,
name: error.name,
});
ctx.throw(500, "Failed to fetch audit logs");
}
},
async findOne(ctx) {
try {
const { id } = ctx.params;
if (!id) {
ctx.throw(400, "Invalid log ID");
}
const auditLogService = strapi.plugin("audit-logs").service("log");
const log = await auditLogService.findOne(id);
if (!log) {
ctx.throw(404, "Audit log not found");
}
ctx.body = { data: log };
} catch (error) {
if (error.status) {
throw error;
}
strapi.log.error("Failed to fetch audit log:", error);
ctx.throw(500, "Failed to fetch audit log");
}
},
async count(ctx) {
try {
const { query } = ctx.request;
// Reuse the same filter parsing logic from find method
const filters = {};
// Handle action filter (exact match, same as find method)
if (query.action && typeof query.action === "string") {
filters.action = { $eq: query.action.trim() };
}
if (query.user && typeof query.user === "string") {
filters.userDisplayName = { $containsi: query.user.trim() };
}
if (query.method && typeof query.method === "string") {
const allowedMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
if (allowedMethods.includes(query.method.toUpperCase())) {
filters.method = { $eq: query.method.toUpperCase() };
}
}
if (query.dateFrom) {
const dateFrom = new Date(query.dateFrom);
if (!isNaN(dateFrom.getTime())) {
filters.date = filters.date || {};
filters.date.$gte = dateFrom.toISOString();
}
}
if (query.dateTo) {
const dateTo = new Date(query.dateTo);
if (!isNaN(dateTo.getTime())) {
filters.date = filters.date || {};
filters.date.$lte = dateTo.toISOString();
}
}
const auditLogService = strapi.plugin("audit-logs").service("log");
const count = await auditLogService.count({ filters });
ctx.body = { data: count };
} catch (error) {
strapi.log.error("Failed to count audit logs:", error);
ctx.throw(500, "Failed to count audit logs");
}
},
async cleanup(ctx) {
try {
// Check if user is super admin
const { user } = ctx.state;
if (!user || !user.roles || !Array.isArray(user.roles)) {
ctx.throw(403, "Access denied: Super admin role required");
}
const isSuperAdmin = user.roles.some(role =>
role.code === "strapi-super-admin" || role.name === "Super Admin"
);
if (!isSuperAdmin) {
ctx.throw(403, "Access denied: Super admin role required");
}
const auditLogService = strapi.plugin("audit-logs").service("log");
const result = await auditLogService.cleanupOldLogs();
ctx.body = {
message: "Cleanup completed successfully",
deleted: result.count || 0,
};
} catch (error) {
strapi.log.error("Failed to cleanup audit logs:", error);
ctx.throw(500, "Failed to cleanup audit logs");
}
},
});