feat(#37-41): Add domains, ideas, relationships, agents, widgets schema
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
This commit is contained in:
462
apps/api/src/activity/activity.service.ts
Normal file
462
apps/api/src/activity/activity.service.ts
Normal file
@@ -0,0 +1,462 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { ActivityAction, EntityType } from "@prisma/client";
|
||||
import type {
|
||||
CreateActivityLogInput,
|
||||
PaginatedActivityLogs,
|
||||
ActivityLogResult,
|
||||
} from "./interfaces/activity.interface";
|
||||
import type { QueryActivityLogDto } from "./dto";
|
||||
|
||||
/**
|
||||
* Service for managing activity logs and audit trails
|
||||
*/
|
||||
@Injectable()
|
||||
export class ActivityService {
|
||||
private readonly logger = new Logger(ActivityService.name);
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/**
|
||||
* Create a new activity log entry
|
||||
*/
|
||||
async logActivity(input: CreateActivityLogInput) {
|
||||
try {
|
||||
return await this.prisma.activityLog.create({
|
||||
data: input,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to log activity", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated activity logs with filters
|
||||
*/
|
||||
async findAll(query: QueryActivityLogDto): Promise<PaginatedActivityLogs> {
|
||||
const page = query.page || 1;
|
||||
const limit = query.limit || 50;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Build where clause
|
||||
const where: any = {
|
||||
workspaceId: query.workspaceId,
|
||||
};
|
||||
|
||||
if (query.userId) {
|
||||
where.userId = query.userId;
|
||||
}
|
||||
|
||||
if (query.action) {
|
||||
where.action = query.action;
|
||||
}
|
||||
|
||||
if (query.entityType) {
|
||||
where.entityType = query.entityType;
|
||||
}
|
||||
|
||||
if (query.entityId) {
|
||||
where.entityId = query.entityId;
|
||||
}
|
||||
|
||||
if (query.startDate || query.endDate) {
|
||||
where.createdAt = {};
|
||||
if (query.startDate) {
|
||||
where.createdAt.gte = query.startDate;
|
||||
}
|
||||
if (query.endDate) {
|
||||
where.createdAt.lte = query.endDate;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute queries in parallel
|
||||
const [data, total] = await Promise.all([
|
||||
this.prisma.activityLog.findMany({
|
||||
where,
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
this.prisma.activityLog.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single activity log by ID
|
||||
*/
|
||||
async findOne(
|
||||
id: string,
|
||||
workspaceId: string
|
||||
): Promise<ActivityLogResult | null> {
|
||||
return await this.prisma.activityLog.findUnique({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get audit trail for a specific entity
|
||||
*/
|
||||
async getAuditTrail(
|
||||
workspaceId: string,
|
||||
entityType: EntityType,
|
||||
entityId: string
|
||||
): Promise<ActivityLogResult[]> {
|
||||
return await this.prisma.activityLog.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
entityType,
|
||||
entityId,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// HELPER METHODS FOR COMMON ACTIVITY TYPES
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Log task creation
|
||||
*/
|
||||
async logTaskCreated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
taskId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: taskId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log task update
|
||||
*/
|
||||
async logTaskUpdated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
taskId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.UPDATED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: taskId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log task deletion
|
||||
*/
|
||||
async logTaskDeleted(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
taskId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.DELETED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: taskId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log task completion
|
||||
*/
|
||||
async logTaskCompleted(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
taskId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.COMPLETED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: taskId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log task assignment
|
||||
*/
|
||||
async logTaskAssigned(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
taskId: string,
|
||||
assigneeId: string
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.ASSIGNED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: taskId,
|
||||
details: { assigneeId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log event creation
|
||||
*/
|
||||
async logEventCreated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
eventId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.EVENT,
|
||||
entityId: eventId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log event update
|
||||
*/
|
||||
async logEventUpdated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
eventId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.UPDATED,
|
||||
entityType: EntityType.EVENT,
|
||||
entityId: eventId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log event deletion
|
||||
*/
|
||||
async logEventDeleted(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
eventId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.DELETED,
|
||||
entityType: EntityType.EVENT,
|
||||
entityId: eventId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log project creation
|
||||
*/
|
||||
async logProjectCreated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
projectId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.PROJECT,
|
||||
entityId: projectId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log project update
|
||||
*/
|
||||
async logProjectUpdated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
projectId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.UPDATED,
|
||||
entityType: EntityType.PROJECT,
|
||||
entityId: projectId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log project deletion
|
||||
*/
|
||||
async logProjectDeleted(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
projectId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.DELETED,
|
||||
entityType: EntityType.PROJECT,
|
||||
entityId: projectId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log workspace creation
|
||||
*/
|
||||
async logWorkspaceCreated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.WORKSPACE,
|
||||
entityId: workspaceId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log workspace update
|
||||
*/
|
||||
async logWorkspaceUpdated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.UPDATED,
|
||||
entityType: EntityType.WORKSPACE,
|
||||
entityId: workspaceId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log workspace member added
|
||||
*/
|
||||
async logWorkspaceMemberAdded(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
memberId: string,
|
||||
role: string
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.WORKSPACE,
|
||||
entityId: workspaceId,
|
||||
details: { memberId, role },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log workspace member removed
|
||||
*/
|
||||
async logWorkspaceMemberRemoved(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
memberId: string
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.DELETED,
|
||||
entityType: EntityType.WORKSPACE,
|
||||
entityId: workspaceId,
|
||||
details: { memberId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Log user profile update
|
||||
*/
|
||||
async logUserUpdated(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
details?: Record<string, any>
|
||||
) {
|
||||
return this.logActivity({
|
||||
workspaceId,
|
||||
userId,
|
||||
action: ActivityAction.UPDATED,
|
||||
entityType: EntityType.USER,
|
||||
entityId: userId,
|
||||
...(details && { details }),
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user