Template base para proyectos SaaS multi-tenant. Estructura inicial: - apps/backend (NestJS API) - apps/frontend (React/Vite) - apps/database (PostgreSQL DDL) - docs/ (Documentación) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
221 lines
9.0 KiB
JavaScript
221 lines
9.0 KiB
JavaScript
"use strict";
|
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
};
|
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.AuditService = void 0;
|
|
const common_1 = require("@nestjs/common");
|
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
const typeorm_2 = require("typeorm");
|
|
const audit_log_entity_1 = require("../entities/audit-log.entity");
|
|
const activity_log_entity_1 = require("../entities/activity-log.entity");
|
|
let AuditService = class AuditService {
|
|
constructor(auditLogRepository, activityLogRepository) {
|
|
this.auditLogRepository = auditLogRepository;
|
|
this.activityLogRepository = activityLogRepository;
|
|
}
|
|
async createAuditLog(params) {
|
|
const changedFields = this.detectChangedFields(params.old_values, params.new_values);
|
|
const auditLog = this.auditLogRepository.create({
|
|
...params,
|
|
changed_fields: changedFields,
|
|
});
|
|
return this.auditLogRepository.save(auditLog);
|
|
}
|
|
async queryAuditLogs(tenantId, query) {
|
|
const { user_id, action, entity_type, entity_id, from_date, to_date, page = 1, limit = 20 } = query;
|
|
const queryBuilder = this.auditLogRepository
|
|
.createQueryBuilder('audit')
|
|
.where('audit.tenant_id = :tenantId', { tenantId });
|
|
if (user_id) {
|
|
queryBuilder.andWhere('audit.user_id = :user_id', { user_id });
|
|
}
|
|
if (action) {
|
|
queryBuilder.andWhere('audit.action = :action', { action });
|
|
}
|
|
if (entity_type) {
|
|
queryBuilder.andWhere('audit.entity_type = :entity_type', { entity_type });
|
|
}
|
|
if (entity_id) {
|
|
queryBuilder.andWhere('audit.entity_id = :entity_id', { entity_id });
|
|
}
|
|
if (from_date && to_date) {
|
|
queryBuilder.andWhere('audit.created_at BETWEEN :from_date AND :to_date', {
|
|
from_date,
|
|
to_date,
|
|
});
|
|
}
|
|
else if (from_date) {
|
|
queryBuilder.andWhere('audit.created_at >= :from_date', { from_date });
|
|
}
|
|
else if (to_date) {
|
|
queryBuilder.andWhere('audit.created_at <= :to_date', { to_date });
|
|
}
|
|
queryBuilder
|
|
.orderBy('audit.created_at', 'DESC')
|
|
.skip((page - 1) * limit)
|
|
.take(limit);
|
|
const [data, total] = await queryBuilder.getManyAndCount();
|
|
return {
|
|
data,
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages: Math.ceil(total / limit),
|
|
};
|
|
}
|
|
async getAuditLogById(tenantId, id) {
|
|
return this.auditLogRepository.findOne({
|
|
where: { id, tenant_id: tenantId },
|
|
});
|
|
}
|
|
async getEntityAuditHistory(tenantId, entityType, entityId) {
|
|
return this.auditLogRepository.find({
|
|
where: {
|
|
tenant_id: tenantId,
|
|
entity_type: entityType,
|
|
entity_id: entityId,
|
|
},
|
|
order: { created_at: 'DESC' },
|
|
});
|
|
}
|
|
async createActivityLog(tenantId, userId, dto, context) {
|
|
const activityLog = this.activityLogRepository.create({
|
|
tenant_id: tenantId,
|
|
user_id: userId,
|
|
...dto,
|
|
ip_address: context?.ip_address,
|
|
user_agent: context?.user_agent,
|
|
session_id: context?.session_id,
|
|
});
|
|
return this.activityLogRepository.save(activityLog);
|
|
}
|
|
async queryActivityLogs(tenantId, query) {
|
|
const { user_id, activity_type, resource_type, from_date, to_date, page = 1, limit = 20 } = query;
|
|
const queryBuilder = this.activityLogRepository
|
|
.createQueryBuilder('activity')
|
|
.where('activity.tenant_id = :tenantId', { tenantId });
|
|
if (user_id) {
|
|
queryBuilder.andWhere('activity.user_id = :user_id', { user_id });
|
|
}
|
|
if (activity_type) {
|
|
queryBuilder.andWhere('activity.activity_type = :activity_type', { activity_type });
|
|
}
|
|
if (resource_type) {
|
|
queryBuilder.andWhere('activity.resource_type = :resource_type', { resource_type });
|
|
}
|
|
if (from_date && to_date) {
|
|
queryBuilder.andWhere('activity.created_at BETWEEN :from_date AND :to_date', {
|
|
from_date,
|
|
to_date,
|
|
});
|
|
}
|
|
else if (from_date) {
|
|
queryBuilder.andWhere('activity.created_at >= :from_date', { from_date });
|
|
}
|
|
else if (to_date) {
|
|
queryBuilder.andWhere('activity.created_at <= :to_date', { to_date });
|
|
}
|
|
queryBuilder
|
|
.orderBy('activity.created_at', 'DESC')
|
|
.skip((page - 1) * limit)
|
|
.take(limit);
|
|
const [data, total] = await queryBuilder.getManyAndCount();
|
|
return {
|
|
data,
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages: Math.ceil(total / limit),
|
|
};
|
|
}
|
|
async getUserActivitySummary(tenantId, userId, days = 30) {
|
|
const fromDate = new Date();
|
|
fromDate.setDate(fromDate.getDate() - days);
|
|
const result = await this.activityLogRepository
|
|
.createQueryBuilder('activity')
|
|
.select('activity.activity_type', 'activity_type')
|
|
.addSelect('COUNT(*)', 'count')
|
|
.where('activity.tenant_id = :tenantId', { tenantId })
|
|
.andWhere('activity.user_id = :userId', { userId })
|
|
.andWhere('activity.created_at >= :fromDate', { fromDate })
|
|
.groupBy('activity.activity_type')
|
|
.getRawMany();
|
|
return result.map((r) => ({
|
|
activity_type: r.activity_type,
|
|
count: parseInt(r.count, 10),
|
|
}));
|
|
}
|
|
async getAuditStats(tenantId, days = 7) {
|
|
const fromDate = new Date();
|
|
fromDate.setDate(fromDate.getDate() - days);
|
|
const [totalActions, actionsByType, topUsers] = await Promise.all([
|
|
this.auditLogRepository.count({
|
|
where: {
|
|
tenant_id: tenantId,
|
|
created_at: (0, typeorm_2.MoreThanOrEqual)(fromDate),
|
|
},
|
|
}),
|
|
this.auditLogRepository
|
|
.createQueryBuilder('audit')
|
|
.select('audit.action', 'action')
|
|
.addSelect('COUNT(*)', 'count')
|
|
.where('audit.tenant_id = :tenantId', { tenantId })
|
|
.andWhere('audit.created_at >= :fromDate', { fromDate })
|
|
.groupBy('audit.action')
|
|
.getRawMany(),
|
|
this.auditLogRepository
|
|
.createQueryBuilder('audit')
|
|
.select('audit.user_id', 'user_id')
|
|
.addSelect('COUNT(*)', 'count')
|
|
.where('audit.tenant_id = :tenantId', { tenantId })
|
|
.andWhere('audit.created_at >= :fromDate', { fromDate })
|
|
.andWhere('audit.user_id IS NOT NULL')
|
|
.groupBy('audit.user_id')
|
|
.orderBy('count', 'DESC')
|
|
.limit(10)
|
|
.getRawMany(),
|
|
]);
|
|
return {
|
|
total_actions: totalActions,
|
|
actions_by_type: actionsByType.map((r) => ({
|
|
action: r.action,
|
|
count: parseInt(r.count, 10),
|
|
})),
|
|
top_users: topUsers.map((r) => ({
|
|
user_id: r.user_id,
|
|
count: parseInt(r.count, 10),
|
|
})),
|
|
};
|
|
}
|
|
detectChangedFields(oldValues, newValues) {
|
|
if (!oldValues || !newValues)
|
|
return [];
|
|
const changedFields = [];
|
|
const allKeys = new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
|
|
for (const key of allKeys) {
|
|
if (JSON.stringify(oldValues[key]) !== JSON.stringify(newValues[key])) {
|
|
changedFields.push(key);
|
|
}
|
|
}
|
|
return changedFields;
|
|
}
|
|
};
|
|
exports.AuditService = AuditService;
|
|
exports.AuditService = AuditService = __decorate([
|
|
(0, common_1.Injectable)(),
|
|
__param(0, (0, typeorm_1.InjectRepository)(audit_log_entity_1.AuditLog)),
|
|
__param(1, (0, typeorm_1.InjectRepository)(activity_log_entity_1.ActivityLog)),
|
|
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
typeorm_2.Repository])
|
|
], AuditService);
|
|
//# sourceMappingURL=audit.service.js.map
|