Skip to main content

Item History & Audit

The Ever Works Template includes a comprehensive audit trail system that tracks all changes made to items throughout their lifecycle. Every creation, update, status change, review, deletion, and restoration is logged with detailed change information, performer identity, and timestamps.

Architecture Overview

ComponentPathPurpose
itemAuditServicelib/services/item-audit.service.tsService layer for logging audit actions
item-audit.queries.tslib/db/queries/item-audit.queries.tsDatabase queries for audit log CRUD
useItemHistoryhooks/use-item-history.tsReact Query hook for fetching audit logs
ItemHistoryModalcomponents/admin/items/item-history-modal.tsxModal UI for viewing item history

Audit Actions

The system tracks six types of actions:

ActionConstantDescription
CreatedItemAuditAction.CREATEDItem was created
UpdatedItemAuditAction.UPDATEDItem fields were modified
Status ChangedItemAuditAction.STATUS_CHANGEDItem status was changed
ReviewedItemAuditAction.REVIEWEDItem was reviewed (approved/rejected)
DeletedItemAuditAction.DELETEDItem was deleted (soft or hard)
RestoredItemAuditAction.RESTOREDItem was restored from deletion

Tracked Fields

The audit service monitors the following fields for change detection:

FieldType
nameItem name
descriptionItem description
source_urlSource/product URL
categoryCategory assignment
tagsTag array
collectionsCollection assignments
featuredFeatured status
icon_urlIcon/logo URL
statusItem status

Item Audit Service

The itemAuditService provides high-level logging methods that are called from API routes and services.

Logging Item Creation

import { logCreation } from '@/lib/services/item-audit.service';

await logCreation(item, { id: userId, name: userName });
// Logs: action=CREATED, metadata includes slug, category, tags

Logging Item Updates

import { logUpdate } from '@/lib/services/item-audit.service';

await logUpdate(previousItem, updatedItem, { id: userId, name: userName });
// Automatically detects changes between previous and current state
// Uses STATUS_CHANGED action if status differs, UPDATED otherwise
// Only logs if actual changes are detected

Logging Reviews

import { logReview } from '@/lib/services/item-audit.service';

await logReview(item, 'pending', 'Looks good, approved!', { id: userId, name: userName });
// Logs: action=REVIEWED with previous status, new status, and review notes

Logging Deletion and Restoration

import { logDeletion, logRestoration } from '@/lib/services/item-audit.service';

await logDeletion(item, performer, true); // soft delete
await logRestoration(item, performer);

Non-Blocking Design

All audit logging is wrapped in try-catch blocks and will not throw errors that could block the primary operation:

async function logAction(params: LogActionParams): Promise<void> {
try {
await createItemAuditLog(createParams);
} catch (error) {
// Log error but don't throw - audit logging should not block operations
console.error('[ItemAuditService] Failed to log action:', error);
}
}

Change Detection

The detectChanges function compares two item states and returns a detailed diff:

import { detectChanges } from '@/lib/services/item-audit.service';

const changes = detectChanges(previousItem, updatedItem);
// Returns: { fieldName: { old: previousValue, new: currentValue } } or null

Example output:

{
"name": { "old": "Old Name", "new": "New Name" },
"tags": { "old": ["react", "nextjs"], "new": ["react", "nextjs", "typescript"] },
"status": { "old": "pending", "new": "approved" }
}

The function handles deep equality for arrays (sorted comparison) and returns null if no changes are detected.

Database Layer

Audit Log Schema

Each audit log entry contains:

FieldTypeDescription
idstringUnique ID
itemIdstringItem slug/ID
itemNamestringItem name at time of action
actionItemAuditActionValuesAction type
previousStatusstring | nullStatus before action
newStatusstring | nullStatus after action
changesJSON | nullField-level change details
performedBystring | nullUser ID who performed the action
performedByNamestring | nullUser display name
notesstring | nullAdditional notes (e.g., review comments)
metadataJSON | nullExtra context data
createdAttimestampWhen the action occurred

Query Functions

FunctionDescription
createItemAuditLog(data)Create a new audit log entry
getItemHistory(params)Get paginated history with performer info
getLatestItemAuditLog(itemId)Get most recent log entry
getAuditLogsByAction(action, limit)Filter logs by action type
getAuditLogsByPerformer(userId, limit)Filter logs by performer
getItemAuditStats(itemId)Get count breakdown by action type

Paginated History Query

import { getItemHistory } from '@/lib/db/queries/item-audit.queries';

const result = await getItemHistory({
itemId: 'my-item-slug',
page: 1,
limit: 20,
actionFilter: ['updated', 'status_changed']
});

// Returns: { logs, total, page, limit, totalPages }

The query joins with the users table to include performer email alongside each log entry.

The useItemHistory Hook

import { useItemHistory } from '@/hooks/use-item-history';

function ItemHistoryPanel({ itemId }) {
const { data, isLoading, isError } = useItemHistory({
itemId,
page: 1,
limit: 20,
actionFilter: ['updated', 'reviewed'],
enabled: true
});

if (isLoading) return <Spinner />;
if (!data) return null;

return (
<div>
<p>Total entries: {data.total}</p>
{data.logs.map(entry => (
<div key={entry.id}>
<span>{entry.action}</span>
<span>{entry.performedByName}</span>
<span>{entry.createdAt}</span>
</div>
))}
</div>
);
}

Hook Configuration

OptionDefaultDescription
itemIdrequiredItem ID/slug to fetch history for
page1Page number
limit20Items per page
actionFilterundefinedArray of action types to filter by
enabledtrueWhether the query is active
staleTime30 secondsCache freshness duration

Item History Modal

The ItemHistoryModal component provides a complete UI for viewing item audit history:

import { ItemHistoryModal } from '@/components/admin/items/item-history-modal';

<ItemHistoryModal
isOpen={showHistory}
itemId="my-item-slug"
itemName="My Item Name"
onClose={() => setShowHistory(false)}
/>
FeatureDescription
Action filteringDropdown to filter by action type (Created, Updated, etc.)
Color-coded entriesEach action type has a distinct icon and color scheme
Expandable changesClick to expand field-level change details
Relative timestamps"2h ago", "3d ago" with full date on hover
Performer displayShows user name, email, or "System" for automated actions
Review contextShows "Approved"/"Rejected" labels and rejection reasons
PaginationBuilt-in pagination for long histories
Keyboard supportEscape key closes the modal

Action Color Scheme

ActionColorIcon
CreatedGreenPlus
UpdatedBlueEdit2
Status ChangedYellowRefreshCw
ReviewedPurpleCheckCircle
DeletedRedTrash2
RestoredTealRotateCcw

Key Files

FilePath
Audit Servicelib/services/item-audit.service.ts
Audit Querieslib/db/queries/item-audit.queries.ts
History Hookhooks/use-item-history.ts
History Modalcomponents/admin/items/item-history-modal.tsx