Schema additions for issues #37-41: New models: - Domain (#37): Life domains (work, marriage, homelab, etc.) - Idea (#38): Brain dumps with pgvector embeddings - Relationship (#39): Generic entity linking (blocks, depends_on) - Agent (#40): ClawdBot agent tracking with metrics - AgentSession (#40): Conversation session tracking - WidgetDefinition (#41): HUD widget registry - UserLayout (#41): Per-user dashboard configuration Updated models: - Task, Event, Project: Added domainId foreign key - User, Workspace: Added new relations New enums: - IdeaStatus: CAPTURED, PROCESSING, ACTIONABLE, ARCHIVED, DISCARDED - RelationshipType: BLOCKS, BLOCKED_BY, DEPENDS_ON, etc. - AgentStatus: IDLE, WORKING, WAITING, ERROR, TERMINATED - EntityType: Added IDEA, DOMAIN Migration: 20260129182803_add_domains_ideas_agents_widgets
Activity Logging API
The Activity Logging API provides comprehensive audit trail and activity tracking functionality for the Mosaic Stack platform. It logs user actions, workspace changes, task/event modifications, and authentication events.
Overview
Activity logs are automatically created for:
- CRUD Operations: Task, event, project, and workspace modifications
- Authentication Events: Login, logout, password resets
- User Actions: Task assignments, workspace member changes
- System Events: Configuration updates, permission changes
All activity logs are workspace-scoped and support multi-tenant isolation through Row-Level Security (RLS).
Endpoints
List Activity Logs
GET /api/activity
Get a paginated list of activity logs with optional filters.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId |
UUID | Yes | Workspace to filter by |
userId |
UUID | No | Filter by user who performed the action |
action |
ActivityAction | No | Filter by action type (CREATED, UPDATED, etc.) |
entityType |
EntityType | No | Filter by entity type (TASK, EVENT, etc.) |
entityId |
UUID | No | Filter by specific entity |
startDate |
ISO 8601 | No | Filter activities after this date |
endDate |
ISO 8601 | No | Filter activities before this date |
page |
Number | No | Page number (default: 1) |
limit |
Number | No | Items per page (default: 50, max: 100) |
Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
"userId": "770e8400-e29b-41d4-a716-446655440002",
"action": "CREATED",
"entityType": "TASK",
"entityId": "880e8400-e29b-41d4-a716-446655440003",
"details": {
"title": "New Task",
"status": "NOT_STARTED"
},
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"createdAt": "2024-01-28T12:00:00Z",
"user": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "John Doe",
"email": "john@example.com"
}
}
],
"meta": {
"total": 150,
"page": 1,
"limit": 50,
"totalPages": 3
}
}
Example Requests:
# Get all activities in a workspace
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001
# Get activities for a specific user
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&userId=770e8400-e29b-41d4-a716-446655440002
# Get task creation events
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&action=CREATED&entityType=TASK
# Get activities in date range
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&startDate=2024-01-01&endDate=2024-01-31
# Paginate results
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&page=2&limit=25
Get Single Activity Log
GET /api/activity/:id
Retrieve a single activity log entry by ID.
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Activity log ID |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId |
UUID | Yes | Workspace ID (for multi-tenant isolation) |
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
"userId": "770e8400-e29b-41d4-a716-446655440002",
"action": "UPDATED",
"entityType": "TASK",
"entityId": "880e8400-e29b-41d4-a716-446655440003",
"details": {
"changes": {
"status": "IN_PROGRESS"
}
},
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"createdAt": "2024-01-28T12:00:00Z",
"user": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "John Doe",
"email": "john@example.com"
}
}
Example Request:
GET /api/activity/550e8400-e29b-41d4-a716-446655440000?workspaceId=660e8400-e29b-41d4-a716-446655440001
Get Entity Audit Trail
GET /api/activity/audit/:entityType/:entityId
Retrieve complete audit trail for a specific entity (task, event, project, etc.).
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
entityType |
EntityType | Yes | Type of entity (TASK, EVENT, PROJECT, WORKSPACE, USER) |
entityId |
UUID | Yes | Entity ID |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId |
UUID | Yes | Workspace ID (for multi-tenant isolation) |
Response:
Returns array of activity logs in chronological order (oldest first).
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
"userId": "770e8400-e29b-41d4-a716-446655440002",
"action": "CREATED",
"entityType": "TASK",
"entityId": "880e8400-e29b-41d4-a716-446655440003",
"details": {
"title": "New Task"
},
"createdAt": "2024-01-28T10:00:00Z",
"user": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "John Doe",
"email": "john@example.com"
}
},
{
"id": "660e8400-e29b-41d4-a716-446655440004",
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
"userId": "770e8400-e29b-41d4-a716-446655440005",
"action": "UPDATED",
"entityType": "TASK",
"entityId": "880e8400-e29b-41d4-a716-446655440003",
"details": {
"changes": {
"status": "IN_PROGRESS"
}
},
"createdAt": "2024-01-28T12:00:00Z",
"user": {
"id": "770e8400-e29b-41d4-a716-446655440005",
"name": "Jane Smith",
"email": "jane@example.com"
}
}
]
Example Requests:
# Get audit trail for a task
GET /api/activity/audit/TASK/880e8400-e29b-41d4-a716-446655440003?workspaceId=660e8400-e29b-41d4-a716-446655440001
# Get audit trail for a project
GET /api/activity/audit/PROJECT/990e8400-e29b-41d4-a716-446655440006?workspaceId=660e8400-e29b-41d4-a716-446655440001
# Get audit trail for a workspace
GET /api/activity/audit/WORKSPACE/660e8400-e29b-41d4-a716-446655440001?workspaceId=660e8400-e29b-41d4-a716-446655440001
Enums
ActivityAction
Actions that can be logged:
CREATED- Entity was createdUPDATED- Entity was updatedDELETED- Entity was deletedCOMPLETED- Task or project was completedASSIGNED- Task was assigned to a userCOMMENTED- Comment was added (future use)LOGIN- User logged inLOGOUT- User logged outPASSWORD_RESET- Password was resetEMAIL_VERIFIED- Email was verified
EntityType
Types of entities that can be tracked:
TASK- Task entityEVENT- Calendar eventPROJECT- ProjectWORKSPACE- WorkspaceUSER- User profile
Automatic Logging
The Activity Logging system includes an interceptor that automatically logs:
- POST requests →
CREATEDaction - PATCH/PUT requests →
UPDATEDaction - DELETE requests →
DELETEDaction
The interceptor extracts:
- User information from the authenticated session
- Workspace context from request
- IP address and user agent from HTTP headers
- Entity ID from route parameters or response
Manual Logging
For custom logging scenarios, use the ActivityService helper methods:
import { ActivityService } from '@/activity/activity.service';
@Injectable()
export class TaskService {
constructor(private activityService: ActivityService) {}
async createTask(data, userId, workspaceId) {
const task = await this.prisma.task.create({ data });
// Log task creation
await this.activityService.logTaskCreated(
workspaceId,
userId,
task.id,
{ title: task.title }
);
return task;
}
}
Available Helper Methods
Task Activities
logTaskCreated(workspaceId, userId, taskId, details?)logTaskUpdated(workspaceId, userId, taskId, details?)logTaskDeleted(workspaceId, userId, taskId, details?)logTaskCompleted(workspaceId, userId, taskId, details?)logTaskAssigned(workspaceId, userId, taskId, assigneeId)
Event Activities
logEventCreated(workspaceId, userId, eventId, details?)logEventUpdated(workspaceId, userId, eventId, details?)logEventDeleted(workspaceId, userId, eventId, details?)
Project Activities
logProjectCreated(workspaceId, userId, projectId, details?)logProjectUpdated(workspaceId, userId, projectId, details?)logProjectDeleted(workspaceId, userId, projectId, details?)
Workspace Activities
logWorkspaceCreated(workspaceId, userId, details?)logWorkspaceUpdated(workspaceId, userId, details?)logWorkspaceMemberAdded(workspaceId, userId, memberId, role)logWorkspaceMemberRemoved(workspaceId, userId, memberId)
User Activities
logUserUpdated(workspaceId, userId, details?)
Security & Privacy
Multi-Tenant Isolation
All activity logs are scoped to workspaces using Row-Level Security (RLS). Users can only access activity logs for workspaces they belong to.
Data Retention
Activity logs are retained indefinitely by default. Consider implementing a retention policy based on:
- Compliance requirements
- Storage constraints
- Business needs
Sensitive Data
Activity logs should NOT contain:
- Passwords or authentication tokens
- Credit card information
- Personal health information
- Other sensitive PII
Store only metadata needed for audit purposes. Use the details field for non-sensitive context.
Best Practices
1. Use Descriptive Details
Include enough context to understand what changed:
// Good
await activityService.logTaskUpdated(workspaceId, userId, taskId, {
changes: {
status: { from: 'NOT_STARTED', to: 'IN_PROGRESS' },
assignee: { from: null, to: 'user-456' }
}
});
// Less useful
await activityService.logTaskUpdated(workspaceId, userId, taskId);
2. Log Business-Critical Actions
Prioritize logging actions that:
- Change permissions or access control
- Delete data
- Modify billing or subscription
- Export data
- Change security settings
3. Query Efficiently
Use appropriate filters to reduce data transfer:
// Efficient - filters at database level
const activities = await fetch('/api/activity?workspaceId=xxx&entityType=TASK&page=1&limit=50');
// Inefficient - transfers all data then filters
const activities = await fetch('/api/activity?workspaceId=xxx');
const taskActivities = activities.filter(a => a.entityType === 'TASK');
4. Display User-Friendly Activity Feeds
Transform activity logs into human-readable messages:
function formatActivityMessage(activity: ActivityLog) {
const { user, action, entityType, details } = activity;
switch (action) {
case 'CREATED':
return `${user.name} created ${entityType.toLowerCase()} "${details.title}"`;
case 'UPDATED':
return `${user.name} updated ${entityType.toLowerCase()}`;
case 'DELETED':
return `${user.name} deleted ${entityType.toLowerCase()}`;
default:
return `${user.name} performed ${action}`;
}
}
Error Handling
Activity logging failures should NOT block the primary operation. The interceptor and service methods handle errors gracefully:
try {
await activityService.logActivity(data);
} catch (error) {
// Log error but don't throw
logger.error('Failed to log activity', error);
}
If activity logging is critical for compliance, implement synchronous validation before the operation completes.
Performance Considerations
Indexing
The following indexes optimize common queries:
workspaceId- Filter by workspaceworkspaceId + createdAt- Recent activities per workspaceentityType + entityId- Audit trail queriesuserId- User activity historyaction- Filter by action type
Pagination
Always use pagination for activity queries. Default limit is 50 items, maximum is 100.
Background Processing
For high-volume systems, consider:
- Async activity logging with message queues
- Batch inserts for multiple activities
- Separate read replicas for reporting