workspace-v1/shared/libs/session-management/_reference/session-management.service.reference.ts
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

168 lines
4.5 KiB
TypeScript

/**
* SESSION MANAGEMENT SERVICE - REFERENCE IMPLEMENTATION
*
* @description Servicio para gestión de sesiones de usuario.
* Mantiene registro de sesiones activas, dispositivos y metadata.
*
* @usage Copiar y adaptar según necesidades del proyecto.
* @origin gamilit/apps/backend/src/modules/auth/services/session-management.service.ts
*/
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, MoreThan } from 'typeorm';
import * as crypto from 'crypto';
// Adaptar imports según proyecto
// import { UserSession } from '../entities';
@Injectable()
export class SessionManagementService {
constructor(
@InjectRepository(UserSession, 'auth')
private readonly sessionRepository: Repository<UserSession>,
) {}
/**
* Crear nueva sesión
*
* @param userId - ID del usuario
* @param metadata - Información del dispositivo/cliente
* @returns Sesión creada
*/
async createSession(
userId: string,
metadata: SessionMetadata,
): Promise<UserSession> {
const session = this.sessionRepository.create({
user_id: userId,
refresh_token: this.generateTokenHash(),
ip_address: metadata.ip,
user_agent: metadata.userAgent,
device_type: this.detectDeviceType(metadata.userAgent),
expires_at: this.calculateExpiry(7, 'days'),
is_revoked: false,
});
return this.sessionRepository.save(session);
}
/**
* Obtener sesiones activas de un usuario
*/
async getActiveSessions(userId: string): Promise<UserSession[]> {
return this.sessionRepository.find({
where: {
user_id: userId,
is_revoked: false,
expires_at: MoreThan(new Date()),
},
order: { last_activity_at: 'DESC' },
});
}
/**
* Revocar una sesión específica
*/
async revokeSession(sessionId: string, userId: string): Promise<void> {
const result = await this.sessionRepository.update(
{ id: sessionId, user_id: userId },
{ is_revoked: true },
);
if (result.affected === 0) {
throw new NotFoundException('Sesión no encontrada');
}
}
/**
* Revocar todas las sesiones de un usuario (excepto la actual)
*/
async revokeAllOtherSessions(userId: string, currentSessionId: string): Promise<number> {
const result = await this.sessionRepository
.createQueryBuilder()
.update()
.set({ is_revoked: true })
.where('user_id = :userId', { userId })
.andWhere('id != :currentSessionId', { currentSessionId })
.andWhere('is_revoked = false')
.execute();
return result.affected || 0;
}
/**
* Actualizar última actividad de sesión
*/
async updateLastActivity(sessionId: string): Promise<void> {
await this.sessionRepository.update(sessionId, {
last_activity_at: new Date(),
});
}
/**
* Validar sesión por refresh token
*/
async validateSession(refreshTokenHash: string): Promise<UserSession | null> {
return this.sessionRepository.findOne({
where: {
refresh_token: refreshTokenHash,
is_revoked: false,
expires_at: MoreThan(new Date()),
},
relations: ['user'],
});
}
/**
* Limpiar sesiones expiradas (para CRON job)
*/
async cleanupExpiredSessions(): Promise<number> {
const result = await this.sessionRepository
.createQueryBuilder()
.delete()
.where('expires_at < :now', { now: new Date() })
.orWhere('is_revoked = true')
.execute();
return result.affected || 0;
}
// ============ HELPERS PRIVADOS ============
private generateTokenHash(): string {
return crypto.randomBytes(32).toString('hex');
}
private detectDeviceType(userAgent: string): string {
if (/mobile/i.test(userAgent)) return 'mobile';
if (/tablet/i.test(userAgent)) return 'tablet';
return 'desktop';
}
private calculateExpiry(value: number, unit: 'hours' | 'days'): Date {
const ms = unit === 'hours' ? value * 3600000 : value * 86400000;
return new Date(Date.now() + ms);
}
}
// ============ TIPOS ============
interface SessionMetadata {
ip?: string;
userAgent?: string;
}
interface UserSession {
id: string;
user_id: string;
refresh_token: string;
ip_address?: string;
user_agent?: string;
device_type?: string;
expires_at: Date;
is_revoked: boolean;
last_activity_at?: Date;
user?: any;
}