Shared Packages - Paquetes Compartidos
Los Shared Packages son el código reutilizable que todos los microservicios usan. En lugar de duplicar código, se centraliza aquí.
¿Por qué Shared Packages?
Sin paquetes compartidos (código duplicado):
auth-service/
└── guards/jwt-auth.guard.ts
user-service/
└── guards/jwt-auth.guard.ts ❌ Mismo código
wallet-service/
└── guards/jwt-auth.guard.ts ❌ Mismo código
Con paquetes compartidos:
packages/
└── guards/jwt-auth.guard.ts ✅ Una sola versión
auth-service/
└── usa → packages/guards
user-service/
└── usa → packages/guards
wallet-service/
└── usa → packages/guards
Beneficios:
- ✅ Una sola fuente de verdad
- ✅ Actualizaciones en un solo lugar
- ✅ Consistencia entre servicios
- ✅ Menos bugs (mismo código testeado)
Estructura Completa
packages/
├── analytics/ # Sistema de analytics y métricas
├── chat/ # Sistema de chat compartido
├── controllers/ # Controladores reutilizables
├── dtos/ # Data Transfer Objects
├── email/ # Servicio de email
├── examples/ # Ejemplos de uso
├── gateways/ # WebSocket gateways
├── guards/ # Guards de autenticación/autorización
├── health/ # Health checks
├── interceptors/ # Interceptores (analytics, audit)
├── jwt/ # Módulo JWT
├── logs/ # Sistema de logging
├── middleware/ # Middlewares
├── rate-limiting/ # Rate limiting avanzado
├── redis/ # Servicio de Redis
├── schemas/ # 30+ Mongoose schemas
├── services/ # Servicios compartidos
├── tasks/ # Tareas programadas
└── utils/ # Utilidades
🛡️ Guards (Seguridad)
Los Guards son la primera línea de defensa. Deciden si una request puede continuar o debe ser rechazada.
JWT Auth Guard
// Uso en cualquier servicio
@UseGuards(JwtAuthGuard)
@Get('profile')
async getProfile(@User() user) {
// Solo llega aquí si JWT es válido
return user;
}
Funcionamiento interno:
KYC Guard
Verifica que el usuario tenga KYC aprobado:
@UseGuards(JwtAuthGuard, KycGuard)
@Post('withdraw')
async withdraw(@User() user, @Body() dto: WithdrawDto) {
// Solo usuarios con KYC aprobado
}
Implementación:
@Injectable()
export class KycGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;
// Verificar KYC en DB
const kyc = await this.kycService.getStatus(user.id);
if (kyc.status !== 'approved') {
throw new ForbiddenException('KYC verification required');
}
// Verificar que no haya expirado
if (kyc.expiresAt && new Date() > kyc.expiresAt) {
throw new ForbiddenException('KYC expired');
}
return true;
}
}
PIN Guard
Verifica el PIN de 6 dígitos para operaciones sensibles:
@UseGuards(JwtAuthGuard, PinGuard)
@Post('send-transaction')
async sendTransaction(@User() user, @Body() dto: SendDto) {
// Requiere PIN correcto
}
Sistema de PIN:
- PIN de 6 dígitos
- Hash con bcrypt
- Máx 5 intentos / 15 minutos
- Bloqueo automático si se excede
Rate Limit Guard
Limita requests por IP/usuario:
@UseGuards(RateLimitGuard)
@Post('login')
async login(@Body() dto: LoginDto) {
// Máx X intentos por tiempo
}
Device Auth Guard
Verifica que el dispositivo esté registrado:
@UseGuards(JwtAuthGuard, DeviceAuthGuard)
@Post('sensitive-action')
async sensitiveAction(@Headers() headers, @User() user) {
// Solo dispositivos confiables
}
Headers requeridos:
X-Device-Id: fingerprint_hash
X-Device-Type: mobile|desktop
X-Device-Name: iPhone 12 Pro
Roles Guard
Verifica roles de usuario (admin, user, etc.):
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMIN')
@Get('admin/users')
async getAllUsers() {
// Solo admins
}
📊 Analytics (Métricas y Estadísticas)
Sistema completo de analytics que rastrea TODO en el sistema.
Analytics Interceptor
Se ejecuta automáticamente en cada request:
@UseInterceptors(AnalyticsInterceptor)
export class UserController {
// Todos los endpoints registran analytics
}
Datos capturados:
{
endpoint: '/users/me',
method: 'GET',
userId: 'user_123',
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
responseTime: 45, // ms
statusCode: 200,
timestamp: '2025-10-20T15:30:00Z'
}
Dashboard Analytics
Endpoint para obtener métricas:
GET /analytics/dashboard
Response:
{
totalUsers: 10000,
activeUsers24h: 2500,
totalTransactions: 50000,
volumeUSD: 5000000,
topEndpoints: [
{ endpoint: '/wallets/balance', calls: 15000 },
{ endpoint: '/auth/login', calls: 8000 }
],
errorRate: 0.02 // 2%
}
Endpoint Analytics
Métricas por endpoint específico:
GET /analytics/endpoints/users/me
Response:
{
endpoint: '/users/me',
totalCalls: 5000,
avgResponseTime: 42, // ms
p95ResponseTime: 120,
errorRate: 0.01,
callsByHour: [...]
}
💬 Chat System (Sistema de Chat)
Sistema de chat bidireccional entre usuarios y admins.
Chat Service
class ChatService {
async sendMessage(threadId: string, message: string, senderId: string) {
// 1. Guardar mensaje en DB
const msg = await this.chatRepository.create({
threadId,
senderId,
message,
timestamp: new Date()
});
// 2. Publicar en Redis Pub/Sub
await this.redis.publish(`chat:${threadId}`, {
type: 'new_message',
data: msg
});
// 3. WebSocket notificará a ambas partes
return msg;
}
async createThread(userId: string, subject: string) {
return await this.chatRepository.createThread({
userId,
subject,
status: 'open',
createdAt: new Date()
});
}
async assignThread(threadId: string, adminId: string) {
await this.chatRepository.update(threadId, {
assignedTo: adminId,
status: 'assigned'
});
}
}
Chat Gateway (WebSocket)
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway {
@SubscribeMessage('send_message')
async handleMessage(client: Socket, payload: any) {
const userId = client.data.userId;
await this.chatService.sendMessage(
payload.threadId,
payload.message,
userId
);
}
@SubscribeMessage('typing')
async handleTyping(client: Socket, payload: any) {
// Notificar a la otra parte que está escribiendo
client.to(payload.threadId).emit('user_typing', {
userId: client.data.userId
});
}
}
🔒 Rate Limiting (Control de Abuso)
Sistema avanzado de rate limiting multicapa.
Niveles de Rate Limiting
Rate Limit Decorator
@RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5 // 5 requests
})
@Post('change-password')
async changePassword() {
// Máx 5 cambios de password cada 15 min
}
Implementación en Redis
async checkRateLimit(key: string, max: number, windowMs: number) {
const current = await this.redis.incr(key);
if (current === 1) {
// Primera request, setear expiración
await this.redis.expire(key, Math.ceil(windowMs / 1000));
}
if (current > max) {
throw new TooManyRequestsException(
`Rate limit exceeded. Try again in ${windowMs}ms`
);
}
return {
current,
remaining: max - current,
reset: Date.now() + windowMs
};
}
Estrategias por Tipo
Por IP (prevenir DDoS):
rate_limit:ip:192.168.1.1 → counter
Por Usuario (prevenir abuso):
rate_limit:user:user_123 → counter
Por Email (prevenir spam en registro):
rate_limit:email:test@example.com → counter
Por Endpoint (proteger endpoints críticos):
rate_limit:endpoint:change_password:user_123 → counter
📝 Logs & Audit (Auditoría)
Sistema completo de logging y auditoría.
Audit Interceptor
Registra automáticamente acciones críticas:
@UseInterceptors(AuditInterceptor)
export class UserController {
@Post('change-password')
async changePassword(@User() user, @Body() dto: ChangePasswordDto) {
// Se auditará automáticamente
}
}
Log generado:
{
userId: 'user_123',
action: 'PASSWORD_CHANGED',
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
timestamp: '2025-10-20T15:30:00Z',
metadata: {
method: 'POST',
endpoint: '/users/change-password'
}
}
Acciones Auditadas
- ✅ Login/Logout
- ✅ Cambio de password
- ✅ Cambio de email
- ✅ Activar/Desactivar 2FA
- ✅ Crear wallet
- ✅ Enviar transacción
- ✅ Cambiar datos de perfil
- ✅ Acciones de admin
Logger Service
class LoggerService {
log(message: string, context?: string) {
// Info level
}
error(message: string, trace?: string, context?: string) {
// Error level
}
warn(message: string, context?: string) {
// Warning level
}
debug(message: string, context?: string) {
// Debug level (solo desarrollo)
}
}
📧 Email Service
Envío de emails con templates:
class EmailService {
async sendWelcomeEmail(user: User) {
await this.send({
to: user.email,
subject: 'Welcome to SwapBits!',
template: 'welcome',
context: {
name: user.firstName,
verificationUrl: `${this.config.frontendUrl}/verify/${user.verificationToken}`
}
});
}
async sendTransactionNotification(user: User, tx: Transaction) {
await this.send({
to: user.email,
subject: `Transaction ${tx.type}: ${tx.amount} ${tx.coin}`,
template: 'transaction',
context: {
type: tx.type,
amount: tx.amount,
coin: tx.coin,
txHash: tx.txHash
}
});
}
}
Templates disponibles:
welcome: Email de bienvenidaverify-email: Verificación de emailreset-password: Recuperación de contraseñatransaction: Notificación de transacciónkyc-approved: KYC aprobadokyc-rejected: KYC rechazado
🗄️ Schemas (Modelos MongoDB)
30+ schemas de Mongoose compartidos por todos los servicios.
Schemas Principales
User Schema:
@Schema()
class User {
@Prop({ required: true, unique: true })
email: string;
@Prop({ required: true })
password: string;
@Prop({ default: false })
emailVerified: boolean;
@Prop({ default: false })
twoFactorEnabled: boolean;
@Prop()
twoFactorSecret?: string;
@Prop({ type: String, enum: ['pending', 'approved', 'rejected'] })
kycStatus: string;
}
Wallet Schema:
@Schema()
class Wallet {
@Prop({ required: true, ref: 'User' })
userId: Types.ObjectId;
@Prop({ required: true })
address: string;
@Prop({ required: true })
coin: string;
@Prop({ required: true })
type: string; // EVM, BTC, SOL, etc.
@Prop({ required: true })
privateKeyHash: string; // Nunca en texto plano
@Prop({ default: 0 })
balance: number;
}
Transaction Schema:
@Schema()
class Transaction {
@Prop({ required: true, ref: 'Wallet' })
walletId: Types.ObjectId;
@Prop({ required: true })
txHash: string;
@Prop({ required: true, enum: ['sent', 'received'] })
type: string;
@Prop({ required: true })
amount: number;
@Prop({ required: true })
coin: string;
@Prop()
from?: string;
@Prop()
to?: string;
@Prop({ default: 'pending', enum: ['pending', 'confirmed', 'failed'] })
status: string;
@Prop({ default: 0 })
confirmations: number;
}
Otros Schemas
Session: Sesiones activasOrder: Órdenes de tradingChatThread: Hilos de chatChatMessage: Mensajes de chatAuditLog: Logs de auditoríaNotification: NotificacionesDevice: Dispositivos registradosRateLimit: Rate limitsApiKey: API keysWebhook: Webhooks recibidos- Y 20 más...
🔧 Utils (Utilidades)
HTTP Helpers
class HttpHelpers {
static success(data: any, message?: string) {
return {
success: true,
message: message || 'Success',
data
};
}
static error(message: string, code?: string) {
return {
success: false,
message,
code: code || 'ERROR'
};
}
}
Event Codes
Códigos de eventos estandarizados:
export const EventCodes = {
// Auth
AUTH_LOGIN_SUCCESS: 'AUTH_001',
AUTH_LOGIN_FAILED: 'AUTH_002',
AUTH_2FA_REQUIRED: 'AUTH_003',
// Transactions
TX_CREATED: 'TX_001',
TX_CONFIRMED: 'TX_002',
TX_FAILED: 'TX_003',
// KYC
KYC_STARTED: 'KYC_001',
KYC_APPROVED: 'KYC_002',
KYC_REJECTED: 'KYC_003',
// Y muchos más...
};
🏥 Health Checks
Sistema de health checks para monitoreo:
@Controller('health')
export class HealthController {
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('mongodb'),
() => this.redis.pingCheck('redis'),
() => this.disk.checkStorage('storage', { threshold: 0.9 })
]);
}
}
Response:
{
"status": "ok",
"info": {
"mongodb": { "status": "up" },
"redis": { "status": "up" },
"storage": { "status": "up", "used": 0.65 }
}
}
📦 Cómo se Usan
En un Microservicio
// En auth-service/package.json
{
"dependencies": {
"@swapbits/guards": "file:../../packages/guards",
"@swapbits/schemas": "file:../../packages/schemas",
"@swapbits/redis": "file:../../packages/redis",
"@swapbits/jwt": "file:../../packages/jwt"
}
}
// En auth-service/src/auth.module.ts
import { JwtModule } from '@swapbits/jwt';
import { RedisModule } from '@swapbits/redis';
import { JwtAuthGuard, KycGuard } from '@swapbits/guards';
@Module({
imports: [JwtModule, RedisModule],
providers: [JwtAuthGuard, KycGuard]
})
export class AuthModule {}
Ventajas del Sistema de Packages
Beneficios:
- ✅ Consistencia: Todos usan el mismo código
- ✅ Mantenimiento: Actualizar en un solo lugar
- ✅ Testing: Testear una vez, usar en todos
- ✅ Escalabilidad: Agregar servicio nuevo es fácil
- ✅ Desacoplamiento: Packages son independientes
Resumen de Packages
| Package | Función | Usado en |
|---|---|---|
| guards | Autenticación/Autorización | Todos los servicios |
| schemas | Modelos MongoDB | Todos los servicios |
| redis | Cliente Redis compartido | WS, User, Auth |
| jwt | JWT tokens | Auth, User, Admin |
| analytics | Métricas | Todos los servicios |
| chat | Sistema de chat | WS, Admin, User |
| rate-limiting | Rate limits | Todos los servicios |
| logs | Logging/Audit | Todos los servicios |
| Envío de emails | Auth, User, Bank | |
| health | Health checks | Todos los servicios |
Para Desarrolladores
Los Shared Packages son el pegamento del sistema. Sin ellos, habría código duplicado por todos lados.
Regla de oro: Si el código se usa en 2+ servicios, va en Packages.
Actualizar un package:
- Hacer cambio en
packages/ - Los servicios lo verán automáticamente (file: link)
- No necesitas publicar a npm
Agregar un nuevo package:
- Crear carpeta en
packages/ - Agregar
package.jsoncon nombre@swapbits/xxx - Importar en servicios que lo necesiten