template-saas/apps/backend/dist/modules/audit/services/audit.service.js
rckrdmrd 26f0e52ca7 feat: Initial commit - template-saas
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>
2026-01-07 04:41:24 -06:00

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