https://project.mdnd-it.cc/work_packages/94
This commit is contained in:
2025-08-23 04:25:28 +02:00
parent 725516ad6c
commit 19cfa031d0
25823 changed files with 1095587 additions and 2801760 deletions
@@ -0,0 +1,61 @@
import { UserState } from '../../Domain/User/UserAggregate';
import { Request, Response } from 'express';
/**
* Admin Bypass Service - Centralized admin privilege checking and logging
*/
export declare class AdminBypassService {
/**
* Check if user has admin privileges
* @param userState - User's current state
* @returns true if user is admin
*/
static isAdmin(userState: UserState): boolean;
/**
* Check if user should bypass all restrictions
* @param userState - User's current state
* @returns true if restrictions should be bypassed
*/
static shouldBypassRestrictions(userState: UserState): boolean;
/**
* Log admin bypass action for audit trail
* @param action - Description of the action being bypassed
* @param adminUserId - ID of the admin user
* @param targetId - ID of the target resource
* @param details - Additional details about the bypass
* @param req - Optional request object for context
* @param res - Optional response object for context
*/
static logAdminBypass(action: string, adminUserId: string, targetId: string, details?: any, req?: Request, res?: Response): void;
}
/**
* Admin Audit Service - Enhanced logging for all admin actions
*/
export declare class AdminAuditService {
/**
* Log comprehensive admin action for audit trail
* @param action - Action being performed
* @param adminUserId - ID of the admin user
* @param details - Detailed information about the action
* @param req - Request object for context
* @param res - Response object for context
*/
static logAdminAction(action: string, adminUserId: string, details: {
targetType: 'user' | 'organization' | 'deck' | 'contact' | 'chat';
targetId: string;
operation: 'create' | 'read' | 'update' | 'delete' | 'bypass' | 'export' | 'import';
changes?: any;
sensitive?: boolean;
metadata?: any;
}, req?: Request, res?: Response): void;
/**
* Log bulk admin operations
* @param action - Bulk action being performed
* @param adminUserId - ID of the admin user
* @param affectedCount - Number of resources affected
* @param targetType - Type of resources affected
* @param req - Request object for context
* @param res - Response object for context
*/
static logBulkAdminAction(action: string, adminUserId: string, affectedCount: number, targetType: string, req?: Request, res?: Response): void;
}
//# sourceMappingURL=AdminBypassService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"AdminBypassService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/AdminBypassService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C;;GAEG;AACH,qBAAa,kBAAkB;IAC3B;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO;IAI7C;;;;OAIG;IACH,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO;IAI9D;;;;;;;;OAQG;IACH,MAAM,CAAC,cAAc,CACjB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,GAAG,EACb,GAAG,CAAC,EAAE,OAAO,EACb,GAAG,CAAC,EAAE,QAAQ,GACf,IAAI;CASV;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC1B;;;;;;;OAOG;IACH,MAAM,CAAC,cAAc,CACjB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;QACL,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;QAClE,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;QACpF,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,QAAQ,CAAC,EAAE,GAAG,CAAC;KAClB,EACD,GAAG,CAAC,EAAE,OAAO,EACb,GAAG,CAAC,EAAE,QAAQ,GACf,IAAI;IA2BP;;;;;;;;OAQG;IACH,MAAM,CAAC,kBAAkB,CACrB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,OAAO,EACb,GAAG,CAAC,EAAE,QAAQ,GACf,IAAI;CASV"}
@@ -0,0 +1,101 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminAuditService = exports.AdminBypassService = void 0;
const UserAggregate_1 = require("../../Domain/User/UserAggregate");
const Logger_1 = require("./Logger");
/**
* Admin Bypass Service - Centralized admin privilege checking and logging
*/
class AdminBypassService {
/**
* Check if user has admin privileges
* @param userState - User's current state
* @returns true if user is admin
*/
static isAdmin(userState) {
return userState === UserAggregate_1.UserState.ADMIN;
}
/**
* Check if user should bypass all restrictions
* @param userState - User's current state
* @returns true if restrictions should be bypassed
*/
static shouldBypassRestrictions(userState) {
return this.isAdmin(userState);
}
/**
* Log admin bypass action for audit trail
* @param action - Description of the action being bypassed
* @param adminUserId - ID of the admin user
* @param targetId - ID of the target resource
* @param details - Additional details about the bypass
* @param req - Optional request object for context
* @param res - Optional response object for context
*/
static logAdminBypass(action, adminUserId, targetId, details, req, res) {
(0, Logger_1.logAuth)(`ADMIN_BYPASS: ${action}`, adminUserId, {
targetId,
action,
bypassReason: 'Admin privileges',
timestamp: new Date().toISOString(),
...details
}, req, res);
}
}
exports.AdminBypassService = AdminBypassService;
/**
* Admin Audit Service - Enhanced logging for all admin actions
*/
class AdminAuditService {
/**
* Log comprehensive admin action for audit trail
* @param action - Action being performed
* @param adminUserId - ID of the admin user
* @param details - Detailed information about the action
* @param req - Request object for context
* @param res - Response object for context
*/
static logAdminAction(action, adminUserId, details, req, res) {
const auditData = {
timestamp: new Date().toISOString(),
adminUserId,
action,
...details,
ip: req?.ip,
userAgent: req?.get('User-Agent'),
endpoint: req?.path,
method: req?.method,
requestId: req?.headers['x-request-id'] || 'unknown'
};
// Enhanced logging for admin actions
(0, Logger_1.logAuth)(`ADMIN_AUDIT: ${action}`, adminUserId, auditData, req, res);
// Additional security logging for sensitive operations
if (details.sensitive) {
(0, Logger_1.logAuth)(`ADMIN_SENSITIVE: ${action}`, adminUserId, {
...auditData,
alertLevel: 'HIGH',
requiresReview: true
}, req, res);
}
}
/**
* Log bulk admin operations
* @param action - Bulk action being performed
* @param adminUserId - ID of the admin user
* @param affectedCount - Number of resources affected
* @param targetType - Type of resources affected
* @param req - Request object for context
* @param res - Response object for context
*/
static logBulkAdminAction(action, adminUserId, affectedCount, targetType, req, res) {
this.logAdminAction(`BULK_${action}`, adminUserId, {
targetType: targetType,
targetId: `bulk-${affectedCount}-items`,
operation: 'update',
metadata: { affectedCount },
sensitive: affectedCount > 10 // Mark large bulk operations as sensitive
}, req, res);
}
}
exports.AdminAuditService = AdminAuditService;
//# sourceMappingURL=AdminBypassService.js.map
@@ -0,0 +1 @@
{"version":3,"file":"AdminBypassService.js","sourceRoot":"","sources":["../../../src/Application/Services/AdminBypassService.ts"],"names":[],"mappings":";;;AAAA,mEAA4D;AAC5D,qCAAmC;AAGnC;;GAEG;AACH,MAAa,kBAAkB;IAC3B;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,SAAoB;QAC/B,OAAO,SAAS,KAAK,yBAAS,CAAC,KAAK,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,wBAAwB,CAAC,SAAoB;QAChD,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,cAAc,CACjB,MAAc,EACd,WAAmB,EACnB,QAAgB,EAChB,OAAa,EACb,GAAa,EACb,GAAc;QAEd,IAAA,gBAAO,EAAC,iBAAiB,MAAM,EAAE,EAAE,WAAW,EAAE;YAC5C,QAAQ;YACR,MAAM;YACN,YAAY,EAAE,kBAAkB;YAChC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,OAAO;SACb,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACjB,CAAC;CACJ;AA5CD,gDA4CC;AAED;;GAEG;AACH,MAAa,iBAAiB;IAC1B;;;;;;;OAOG;IACH,MAAM,CAAC,cAAc,CACjB,MAAc,EACd,WAAmB,EACnB,OAOC,EACD,GAAa,EACb,GAAc;QAGd,MAAM,SAAS,GAAG;YACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,WAAW;YACX,MAAM;YACN,GAAG,OAAO;YACV,EAAE,EAAE,GAAG,EAAE,EAAE;YACX,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC;YACjC,QAAQ,EAAE,GAAG,EAAE,IAAI;YACnB,MAAM,EAAE,GAAG,EAAE,MAAM;YACnB,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,IAAI,SAAS;SACvD,CAAC;QAEF,qCAAqC;QACrC,IAAA,gBAAO,EAAC,gBAAgB,MAAM,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAEpE,uDAAuD;QACvD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACpB,IAAA,gBAAO,EAAC,oBAAoB,MAAM,EAAE,EAAE,WAAW,EAAE;gBAC/C,GAAG,SAAS;gBACZ,UAAU,EAAE,MAAM;gBAClB,cAAc,EAAE,IAAI;aACvB,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACjB,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,kBAAkB,CACrB,MAAc,EACd,WAAmB,EACnB,aAAqB,EACrB,UAAkB,EAClB,GAAa,EACb,GAAc;QAEd,IAAI,CAAC,cAAc,CAAC,QAAQ,MAAM,EAAE,EAAE,WAAW,EAAE;YAC/C,UAAU,EAAE,UAAiB;YAC7B,QAAQ,EAAE,QAAQ,aAAa,QAAQ;YACvC,SAAS,EAAE,QAAe;YAC1B,QAAQ,EAAE,EAAE,aAAa,EAAE;YAC3B,SAAS,EAAE,aAAa,GAAG,EAAE,CAAC,0CAA0C;SAC3E,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACjB,CAAC;CACJ;AA1ED,8CA0EC"}
@@ -0,0 +1,6 @@
import { Request, Response, NextFunction } from 'express';
import { JWTService } from './JWTService';
export declare const jwtService: JWTService;
export declare function authRequired(req: Request, res: Response, next: NextFunction): Response<any, Record<string, any>> | undefined;
export declare function adminRequired(req: Request, res: Response, next: NextFunction): Response<any, Record<string, any>> | undefined;
//# sourceMappingURL=AuthMiddleware.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"AuthMiddleware.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/AuthMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,eAAO,MAAM,UAAU,YAAmB,CAAC;AAE3C,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,kDAuB3E;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,kDAyB5E"}
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.jwtService = void 0;
exports.authRequired = authRequired;
exports.adminRequired = adminRequired;
const JWTService_1 = require("./JWTService");
const Logger_1 = require("./Logger");
exports.jwtService = new JWTService_1.JWTService();
function authRequired(req, res, next) {
const payload = exports.jwtService.verify(req);
if (!payload) {
(0, Logger_1.logAuth)('Authentication failed - No valid token', undefined, {
ip: req.ip,
userAgent: req.get ? req.get('User-Agent') : 'unknown',
path: req.path
}, req);
return res.status(401).json({ error: 'Unauthorized' });
}
(0, Logger_1.logAuth)('Authentication successful', payload.userId, {
authLevel: payload.authLevel,
orgId: payload.orgId
}, req);
const refreshed = exports.jwtService.refreshIfNeeded(payload, res);
if (refreshed) {
(0, Logger_1.logAuth)('Token refreshed', payload.userId, undefined, req);
}
req.user = payload;
next();
}
function adminRequired(req, res, next) {
const payload = exports.jwtService.verify(req);
if (!payload || payload.authLevel !== 1) {
(0, Logger_1.logWarning)('Admin access denied', {
hasPayload: !!payload,
authLevel: payload?.authLevel,
userId: payload?.userId,
ip: req.ip,
path: req.path
}, req);
return res.status(403).json({ error: 'Forbidden' });
}
(0, Logger_1.logAuth)('Admin authentication successful', payload.userId, {
authLevel: payload.authLevel,
orgId: payload.orgId
}, req);
const refreshed = exports.jwtService.refreshIfNeeded(payload, res);
if (refreshed) {
(0, Logger_1.logAuth)('Admin token refreshed', payload.userId, undefined, req);
}
req.user = payload;
next();
}
//# sourceMappingURL=AuthMiddleware.js.map
@@ -0,0 +1 @@
{"version":3,"file":"AuthMiddleware.js","sourceRoot":"","sources":["../../../src/Application/Services/AuthMiddleware.ts"],"names":[],"mappings":";;;AAMA,oCAuBC;AAED,sCAyBC;AAvDD,6CAA0C;AAC1C,qCAA+C;AAElC,QAAA,UAAU,GAAG,IAAI,uBAAU,EAAE,CAAC;AAE3C,SAAgB,YAAY,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IACxE,MAAM,OAAO,GAAG,kBAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,IAAA,gBAAO,EAAC,wCAAwC,EAAE,SAAS,EAAE;YACzD,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS;YACtD,IAAI,EAAE,GAAG,CAAC,IAAI;SACjB,EAAE,GAAG,CAAC,CAAC;QACR,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAA,gBAAO,EAAC,2BAA2B,EAAE,OAAO,CAAC,MAAM,EAAE;QACjD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;KACvB,EAAE,GAAG,CAAC,CAAC;IAER,MAAM,SAAS,GAAG,kBAAU,CAAC,eAAe,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC3D,IAAI,SAAS,EAAE,CAAC;QACZ,IAAA,gBAAO,EAAC,iBAAiB,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IAC/D,CAAC;IAEA,GAAW,CAAC,IAAI,GAAG,OAAO,CAAC;IAC5B,IAAI,EAAE,CAAC;AACX,CAAC;AAED,SAAgB,aAAa,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IACzE,MAAM,OAAO,GAAG,kBAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,SAAS,KAAK,CAAC,EAAE,CAAC;QACtC,IAAA,mBAAU,EAAC,qBAAqB,EAAE;YAC9B,UAAU,EAAE,CAAC,CAAC,OAAO;YACrB,SAAS,EAAE,OAAO,EAAE,SAAS;YAC7B,MAAM,EAAE,OAAO,EAAE,MAAM;YACvB,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;SACjB,EAAE,GAAG,CAAC,CAAC;QACR,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,IAAA,gBAAO,EAAC,iCAAiC,EAAE,OAAO,CAAC,MAAM,EAAE;QACvD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;KACvB,EAAE,GAAG,CAAC,CAAC;IAER,MAAM,SAAS,GAAG,kBAAU,CAAC,eAAe,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC3D,IAAI,SAAS,EAAE,CAAC;QACZ,IAAA,gBAAO,EAAC,uBAAuB,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACrE,CAAC;IAEA,GAAW,CAAC,IAAI,GAAG,OAAO,CAAC;IAC5B,IAAI,EAAE,CAAC;AACX,CAAC"}
@@ -0,0 +1,22 @@
import { IContactRepository } from '../../Domain/IRepository/IContactRepository';
import { ContactType } from '../../Domain/Contact/ContactAggregate';
export interface EmailResponseData {
to: string;
message: string;
contactId: string;
adminUserId: string;
contactName: string;
contactType: ContactType;
originalMessage: string;
language?: 'en' | 'hu' | 'de';
}
export declare class ContactEmailService {
private readonly contactRepo;
private emailService;
constructor(contactRepo: IContactRepository);
sendResponse(responseData: EmailResponseData): Promise<void>;
private getLocalizedContactResponseSubject;
private getContactTypeString;
private getContactTypeBadge;
}
//# sourceMappingURL=ContactEmailService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ContactEmailService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/ContactEmailService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAEjF,OAAO,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAIpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAFxC,OAAO,CAAC,YAAY,CAAe;gBAEN,WAAW,EAAE,kBAAkB;IAItD,YAAY,CAAC,YAAY,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDlE,OAAO,CAAC,kCAAkC;IAW1C,OAAO,CAAC,oBAAoB;IAgC5B,OAAO,CAAC,mBAAmB;CAgB5B"}
@@ -0,0 +1,117 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContactEmailService = void 0;
const EmailService_1 = require("./EmailService");
const ContactAggregate_1 = require("../../Domain/Contact/ContactAggregate");
const Logger_1 = require("./Logger");
const EmailTemplateHelper_1 = require("./EmailTemplateHelper");
class ContactEmailService {
constructor(contactRepo) {
this.contactRepo = contactRepo;
this.emailService = new EmailService_1.EmailService();
}
async sendResponse(responseData) {
try {
// First update the contact with the response
await this.contactRepo.update(responseData.contactId, {
adminResponse: responseData.message,
responseDate: new Date(),
respondedBy: responseData.adminUserId,
});
// Determine language and template
const language = responseData.language || 'en';
const templateName = language === 'en' ? 'contact-response' : `contact-response-${language}`;
// Prepare template data
const templateData = {
contactName: responseData.contactName,
contactTypeString: this.getContactTypeString(responseData.contactType, language),
contactTypeBadge: this.getContactTypeBadge(responseData.contactType),
originalMessage: responseData.originalMessage,
adminResponse: responseData.message,
companyName: 'SerpentRace',
supportEmail: 'support@serpentrace.com'
};
// Send email using EmailService with template
const emailSent = await this.emailService.sendEmail({
to: responseData.to,
subject: this.getLocalizedContactResponseSubject(language),
template: templateName,
templateData
});
if (emailSent) {
(0, Logger_1.logOther)('Contact response email sent successfully', {
to: responseData.to,
subject: this.getLocalizedContactResponseSubject(language),
contactId: responseData.contactId,
respondedBy: responseData.adminUserId,
language
});
}
else {
throw new Error('Email service failed to send email');
}
}
catch (error) {
(0, Logger_1.logError)('Failed to send contact response email', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to send email response');
}
}
getLocalizedContactResponseSubject(language) {
const subjects = {
contactResponse: {
en: 'SerpentRace - Response to Your Message',
hu: 'SerpentRace - Válasz az üzenetére',
de: 'SerpentRace - Antwort auf Ihre Nachricht'
}
};
return EmailTemplateHelper_1.EmailTemplateHelper.getLocalizedSubject('contactResponse', subjects, language);
}
getContactTypeString(type, language = 'en') {
const translations = {
[ContactAggregate_1.ContactType.BUG]: {
en: 'Bug Report',
hu: 'Hiba bejelentés',
de: 'Fehlerbericht'
},
[ContactAggregate_1.ContactType.PROBLEM]: {
en: 'Problem',
hu: 'Probléma',
de: 'Problem'
},
[ContactAggregate_1.ContactType.QUESTION]: {
en: 'Question',
hu: 'Kérdés',
de: 'Frage'
},
[ContactAggregate_1.ContactType.SALES]: {
en: 'Sales Inquiry',
hu: 'Értékesítési kérdés',
de: 'Verkaufsanfrage'
},
[ContactAggregate_1.ContactType.OTHER]: {
en: 'General Inquiry',
hu: 'Általános kérdés',
de: 'Allgemeine Anfrage'
}
};
return translations[type]?.[language] || translations[type]?.['en'] || 'Contact';
}
getContactTypeBadge(type) {
switch (type) {
case ContactAggregate_1.ContactType.BUG:
return 'bug';
case ContactAggregate_1.ContactType.PROBLEM:
return 'problem';
case ContactAggregate_1.ContactType.QUESTION:
return 'question';
case ContactAggregate_1.ContactType.SALES:
return 'sales';
case ContactAggregate_1.ContactType.OTHER:
return 'other';
default:
return 'other';
}
}
}
exports.ContactEmailService = ContactEmailService;
//# sourceMappingURL=ContactEmailService.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ContactEmailService.js","sourceRoot":"","sources":["../../../src/Application/Services/ContactEmailService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
import { IUserRepository } from '../../Domain/IRepository/IUserRepository';
import { IChatRepository } from '../../Domain/IRepository/IChatRepository';
import { IChatArchiveRepository } from '../../Domain/IRepository/IChatArchiveRepository';
import { IDeckRepository } from '../../Domain/IRepository/IDeckRepository';
import { IOrganizationRepository } from '../../Domain/IRepository/IOrganizationRepository';
import { IContactRepository } from '../../Domain/IRepository/IContactRepository';
import { CreateUserCommandHandler } from '../User/commands/CreateUserCommandHandler';
import { LoginCommandHandler } from '../User/commands/LoginCommandHandler';
import { UpdateUserCommandHandler } from '../User/commands/UpdateUserCommandHandler';
import { DeactivateUserCommandHandler } from '../User/commands/DeactivateUserCommandHandler';
import { DeleteUserCommandHandler } from '../User/commands/DeleteUserCommandHandler';
import { VerifyEmailCommandHandler } from '../User/commands/VerifyEmailCommandHandler';
import { RequestPasswordResetCommandHandler } from '../User/commands/RequestPasswordResetCommandHandler';
import { ResetPasswordCommandHandler } from '../User/commands/ResetPasswordCommandHandler';
import { CreateChatCommandHandler } from '../Chat/commands/CreateChatCommandHandler';
import { SendMessageCommandHandler } from '../Chat/commands/SendMessageCommandHandler';
import { ArchiveChatCommandHandler, RestoreChatCommandHandler } from '../Chat/commands/ChatArchiveCommandHandlers';
import { CreateDeckCommandHandler } from '../Deck/commands/CreateDeckCommandHandler';
import { UpdateDeckCommandHandler } from '../Deck/commands/UpdateDeckCommandHandler';
import { DeleteDeckCommandHandler } from '../Deck/commands/DeleteDeckCommandHandler';
import { CreateOrganizationCommandHandler } from '../Organization/commands/CreateOrganizationCommandHandler';
import { UpdateOrganizationCommandHandler } from '../Organization/commands/UpdateOrganizationCommandHandler';
import { DeleteOrganizationCommandHandler } from '../Organization/commands/DeleteOrganizationCommandHandler';
import { ProcessOrgAuthCallbackCommandHandler } from '../Organization/commands/ProcessOrgAuthCallbackCommandHandler';
import { CreateContactCommandHandler } from '../Contact/commands/CreateContactCommandHandler';
import { UpdateContactCommandHandler } from '../Contact/commands/UpdateContactCommandHandler';
import { DeleteContactCommandHandler } from '../Contact/commands/DeleteContactCommandHandler';
import { GetUserByIdQueryHandler } from '../User/queries/GetUserByIdQueryHandler';
import { GetUsersByPageQueryHandler } from '../User/queries/GetUsersByPageQueryHandler';
import { GetUserChatsQueryHandler } from '../Chat/queries/GetUserChatsQueryHandler';
import { GetChatHistoryQueryHandler, GetArchivedChatsQueryHandler } from '../Chat/queries/ChatHistoryQueryHandlers';
import { GetChatsByPageQueryHandler } from '../Chat/queries/GetChatsByPageQueryHandler';
import { GetDeckByIdQueryHandler } from '../Deck/queries/GetDeckByIdQueryHandler';
import { GetDecksByPageQueryHandler } from '../Deck/queries/GetDecksByPageQueryHandler';
import { GetOrganizationByIdQueryHandler } from '../Organization/queries/GetOrganizationByIdQueryHandler';
import { GetOrganizationsByPageQueryHandler } from '../Organization/queries/GetOrganizationsByPageQueryHandler';
import { GetOrganizationLoginUrlQueryHandler } from '../Organization/queries/GetOrganizationLoginUrlQueryHandler';
import { GetContactByIdQueryHandler } from '../Contact/queries/GetContactByIdQueryHandler';
import { GetContactsByPageQueryHandler } from '../Contact/queries/GetContactsByPageQueryHandler';
import { JWTService } from './JWTService';
import { ContactEmailService } from './ContactEmailService';
import { DeckImportExportService } from './DeckImportExportService';
/**
* Central Dependency Injection Container
* Manages all repositories, command handlers, and query handlers as singletons
*/
export declare class DIContainer {
private static instance;
private _userRepository;
private _chatRepository;
private _chatArchiveRepository;
private _deckRepository;
private _organizationRepository;
private _contactRepository;
private _jwtService;
private _contactEmailService;
private _deckImportExportService;
private _createUserCommandHandler;
private _loginCommandHandler;
private _updateUserCommandHandler;
private _deactivateUserCommandHandler;
private _deleteUserCommandHandler;
private _verifyEmailCommandHandler;
private _requestPasswordResetCommandHandler;
private _resetPasswordCommandHandler;
private _createChatCommandHandler;
private _sendMessageCommandHandler;
private _archiveChatCommandHandler;
private _restoreChatCommandHandler;
private _createDeckCommandHandler;
private _updateDeckCommandHandler;
private _deleteDeckCommandHandler;
private _createOrganizationCommandHandler;
private _updateOrganizationCommandHandler;
private _deleteOrganizationCommandHandler;
private _processOrgAuthCallbackCommandHandler;
private _createContactCommandHandler;
private _updateContactCommandHandler;
private _deleteContactCommandHandler;
private _getUserByIdQueryHandler;
private _getUsersByPageQueryHandler;
private _getUserChatsQueryHandler;
private _getChatHistoryQueryHandler;
private _getArchivedChatsQueryHandler;
private _getChatsByPageQueryHandler;
private _getDeckByIdQueryHandler;
private _getDecksByPageQueryHandler;
private _getOrganizationByIdQueryHandler;
private _getOrganizationsByPageQueryHandler;
private _getOrganizationLoginUrlQueryHandler;
private _getContactByIdQueryHandler;
private _getContactsByPageQueryHandler;
private constructor();
static getInstance(): DIContainer;
get userRepository(): IUserRepository;
get chatRepository(): IChatRepository;
get chatArchiveRepository(): IChatArchiveRepository;
get deckRepository(): IDeckRepository;
get organizationRepository(): IOrganizationRepository;
get contactRepository(): IContactRepository;
get jwtService(): JWTService;
get contactEmailService(): ContactEmailService;
get deckImportExportService(): DeckImportExportService;
get createUserCommandHandler(): CreateUserCommandHandler;
get loginCommandHandler(): LoginCommandHandler;
get updateUserCommandHandler(): UpdateUserCommandHandler;
get deactivateUserCommandHandler(): DeactivateUserCommandHandler;
get deleteUserCommandHandler(): DeleteUserCommandHandler;
get verifyEmailCommandHandler(): VerifyEmailCommandHandler;
get requestPasswordResetCommandHandler(): RequestPasswordResetCommandHandler;
get resetPasswordCommandHandler(): ResetPasswordCommandHandler;
get createChatCommandHandler(): CreateChatCommandHandler;
get sendMessageCommandHandler(): SendMessageCommandHandler;
get archiveChatCommandHandler(): ArchiveChatCommandHandler;
get restoreChatCommandHandler(): RestoreChatCommandHandler;
get createDeckCommandHandler(): CreateDeckCommandHandler;
get updateDeckCommandHandler(): UpdateDeckCommandHandler;
get deleteDeckCommandHandler(): DeleteDeckCommandHandler;
get createOrganizationCommandHandler(): CreateOrganizationCommandHandler;
get updateOrganizationCommandHandler(): UpdateOrganizationCommandHandler;
get deleteOrganizationCommandHandler(): DeleteOrganizationCommandHandler;
get processOrgAuthCallbackCommandHandler(): ProcessOrgAuthCallbackCommandHandler;
get createContactCommandHandler(): CreateContactCommandHandler;
get updateContactCommandHandler(): UpdateContactCommandHandler;
get deleteContactCommandHandler(): DeleteContactCommandHandler;
get getUserByIdQueryHandler(): GetUserByIdQueryHandler;
get getUserChatsQueryHandler(): GetUserChatsQueryHandler;
get getChatHistoryQueryHandler(): GetChatHistoryQueryHandler;
get getArchivedChatsQueryHandler(): GetArchivedChatsQueryHandler;
get getDeckByIdQueryHandler(): GetDeckByIdQueryHandler;
get getOrganizationByIdQueryHandler(): GetOrganizationByIdQueryHandler;
get getOrganizationLoginUrlQueryHandler(): GetOrganizationLoginUrlQueryHandler;
get getContactByIdQueryHandler(): GetContactByIdQueryHandler;
get getContactsByPageQueryHandler(): GetContactsByPageQueryHandler;
get getUsersByPageQueryHandler(): GetUsersByPageQueryHandler;
get getDecksByPageQueryHandler(): GetDecksByPageQueryHandler;
get getOrganizationsByPageQueryHandler(): GetOrganizationsByPageQueryHandler;
get getChatsByPageQueryHandler(): GetChatsByPageQueryHandler;
}
export declare const container: DIContainer;
//# sourceMappingURL=DIContainer.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"DIContainer.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/DIContainer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,384 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.container = exports.DIContainer = void 0;
// Repository Implementations
const UserRepository_1 = require("../../Infrastructure/Repository/UserRepository");
const ChatRepository_1 = require("../../Infrastructure/Repository/ChatRepository");
const ChatArchiveRepository_1 = require("../../Infrastructure/Repository/ChatArchiveRepository");
const DeckRepository_1 = require("../../Infrastructure/Repository/DeckRepository");
const OrganizationRepository_1 = require("../../Infrastructure/Repository/OrganizationRepository");
const ContactRepository_1 = require("../../Infrastructure/Repository/ContactRepository");
// Command Handlers
const CreateUserCommandHandler_1 = require("../User/commands/CreateUserCommandHandler");
const LoginCommandHandler_1 = require("../User/commands/LoginCommandHandler");
const UpdateUserCommandHandler_1 = require("../User/commands/UpdateUserCommandHandler");
const DeactivateUserCommandHandler_1 = require("../User/commands/DeactivateUserCommandHandler");
const DeleteUserCommandHandler_1 = require("../User/commands/DeleteUserCommandHandler");
const VerifyEmailCommandHandler_1 = require("../User/commands/VerifyEmailCommandHandler");
const RequestPasswordResetCommandHandler_1 = require("../User/commands/RequestPasswordResetCommandHandler");
const ResetPasswordCommandHandler_1 = require("../User/commands/ResetPasswordCommandHandler");
const CreateChatCommandHandler_1 = require("../Chat/commands/CreateChatCommandHandler");
const SendMessageCommandHandler_1 = require("../Chat/commands/SendMessageCommandHandler");
const ChatArchiveCommandHandlers_1 = require("../Chat/commands/ChatArchiveCommandHandlers");
const CreateDeckCommandHandler_1 = require("../Deck/commands/CreateDeckCommandHandler");
const UpdateDeckCommandHandler_1 = require("../Deck/commands/UpdateDeckCommandHandler");
const DeleteDeckCommandHandler_1 = require("../Deck/commands/DeleteDeckCommandHandler");
const CreateOrganizationCommandHandler_1 = require("../Organization/commands/CreateOrganizationCommandHandler");
const UpdateOrganizationCommandHandler_1 = require("../Organization/commands/UpdateOrganizationCommandHandler");
const DeleteOrganizationCommandHandler_1 = require("../Organization/commands/DeleteOrganizationCommandHandler");
const ProcessOrgAuthCallbackCommandHandler_1 = require("../Organization/commands/ProcessOrgAuthCallbackCommandHandler");
const CreateContactCommandHandler_1 = require("../Contact/commands/CreateContactCommandHandler");
const UpdateContactCommandHandler_1 = require("../Contact/commands/UpdateContactCommandHandler");
const DeleteContactCommandHandler_1 = require("../Contact/commands/DeleteContactCommandHandler");
// Query Handlers
const GetUserByIdQueryHandler_1 = require("../User/queries/GetUserByIdQueryHandler");
const GetUsersByPageQueryHandler_1 = require("../User/queries/GetUsersByPageQueryHandler");
const GetUserChatsQueryHandler_1 = require("../Chat/queries/GetUserChatsQueryHandler");
const ChatHistoryQueryHandlers_1 = require("../Chat/queries/ChatHistoryQueryHandlers");
const GetChatsByPageQueryHandler_1 = require("../Chat/queries/GetChatsByPageQueryHandler");
const GetDeckByIdQueryHandler_1 = require("../Deck/queries/GetDeckByIdQueryHandler");
const GetDecksByPageQueryHandler_1 = require("../Deck/queries/GetDecksByPageQueryHandler");
const GetOrganizationByIdQueryHandler_1 = require("../Organization/queries/GetOrganizationByIdQueryHandler");
const GetOrganizationsByPageQueryHandler_1 = require("../Organization/queries/GetOrganizationsByPageQueryHandler");
const GetOrganizationLoginUrlQueryHandler_1 = require("../Organization/queries/GetOrganizationLoginUrlQueryHandler");
const GetContactByIdQueryHandler_1 = require("../Contact/queries/GetContactByIdQueryHandler");
const GetContactsByPageQueryHandler_1 = require("../Contact/queries/GetContactsByPageQueryHandler");
// Services
const JWTService_1 = require("./JWTService");
const ContactEmailService_1 = require("./ContactEmailService");
const DeckImportExportService_1 = require("./DeckImportExportService");
/**
* Central Dependency Injection Container
* Manages all repositories, command handlers, and query handlers as singletons
*/
class DIContainer {
constructor() {
// Repositories - Using interfaces for better abstraction
this._userRepository = null;
this._chatRepository = null;
this._chatArchiveRepository = null;
this._deckRepository = null;
this._organizationRepository = null;
this._contactRepository = null;
// Services
this._jwtService = null;
this._contactEmailService = null;
this._deckImportExportService = null;
// Command Handlers
this._createUserCommandHandler = null;
this._loginCommandHandler = null;
this._updateUserCommandHandler = null;
this._deactivateUserCommandHandler = null;
this._deleteUserCommandHandler = null;
this._verifyEmailCommandHandler = null;
this._requestPasswordResetCommandHandler = null;
this._resetPasswordCommandHandler = null;
this._createChatCommandHandler = null;
this._sendMessageCommandHandler = null;
this._archiveChatCommandHandler = null;
this._restoreChatCommandHandler = null;
this._createDeckCommandHandler = null;
this._updateDeckCommandHandler = null;
this._deleteDeckCommandHandler = null;
this._createOrganizationCommandHandler = null;
this._updateOrganizationCommandHandler = null;
this._deleteOrganizationCommandHandler = null;
this._processOrgAuthCallbackCommandHandler = null;
this._createContactCommandHandler = null;
this._updateContactCommandHandler = null;
this._deleteContactCommandHandler = null;
// Query Handlers
this._getUserByIdQueryHandler = null;
this._getUsersByPageQueryHandler = null;
this._getUserChatsQueryHandler = null;
this._getChatHistoryQueryHandler = null;
this._getArchivedChatsQueryHandler = null;
this._getChatsByPageQueryHandler = null;
this._getDeckByIdQueryHandler = null;
this._getDecksByPageQueryHandler = null;
this._getOrganizationByIdQueryHandler = null;
this._getOrganizationsByPageQueryHandler = null;
this._getOrganizationLoginUrlQueryHandler = null;
this._getContactByIdQueryHandler = null;
this._getContactsByPageQueryHandler = null;
}
static getInstance() {
if (!DIContainer.instance) {
DIContainer.instance = new DIContainer();
}
return DIContainer.instance;
}
// Repository getters - Return interfaces for better abstraction
get userRepository() {
if (!this._userRepository) {
this._userRepository = new UserRepository_1.UserRepository();
}
return this._userRepository;
}
get chatRepository() {
if (!this._chatRepository) {
this._chatRepository = new ChatRepository_1.ChatRepository();
}
return this._chatRepository;
}
get chatArchiveRepository() {
if (!this._chatArchiveRepository) {
this._chatArchiveRepository = new ChatArchiveRepository_1.ChatArchiveRepository();
}
return this._chatArchiveRepository;
}
get deckRepository() {
if (!this._deckRepository) {
this._deckRepository = new DeckRepository_1.DeckRepository();
}
return this._deckRepository;
}
get organizationRepository() {
if (!this._organizationRepository) {
this._organizationRepository = new OrganizationRepository_1.OrganizationRepository();
}
return this._organizationRepository;
}
get contactRepository() {
if (!this._contactRepository) {
this._contactRepository = new ContactRepository_1.ContactRepository();
}
return this._contactRepository;
}
// Services getters
get jwtService() {
if (!this._jwtService) {
this._jwtService = new JWTService_1.JWTService();
}
return this._jwtService;
}
get contactEmailService() {
if (!this._contactEmailService) {
this._contactEmailService = new ContactEmailService_1.ContactEmailService(this.contactRepository);
}
return this._contactEmailService;
}
get deckImportExportService() {
if (!this._deckImportExportService) {
this._deckImportExportService = new DeckImportExportService_1.DeckImportExportService(this.deckRepository);
}
return this._deckImportExportService;
}
// Command Handler getters
get createUserCommandHandler() {
if (!this._createUserCommandHandler) {
this._createUserCommandHandler = new CreateUserCommandHandler_1.CreateUserCommandHandler(this.userRepository);
}
return this._createUserCommandHandler;
}
get loginCommandHandler() {
if (!this._loginCommandHandler) {
this._loginCommandHandler = new LoginCommandHandler_1.LoginCommandHandler(this.userRepository, this.jwtService, this.organizationRepository);
}
return this._loginCommandHandler;
}
get updateUserCommandHandler() {
if (!this._updateUserCommandHandler) {
this._updateUserCommandHandler = new UpdateUserCommandHandler_1.UpdateUserCommandHandler(this.userRepository);
}
return this._updateUserCommandHandler;
}
get deactivateUserCommandHandler() {
if (!this._deactivateUserCommandHandler) {
this._deactivateUserCommandHandler = new DeactivateUserCommandHandler_1.DeactivateUserCommandHandler(this.userRepository);
}
return this._deactivateUserCommandHandler;
}
get deleteUserCommandHandler() {
if (!this._deleteUserCommandHandler) {
this._deleteUserCommandHandler = new DeleteUserCommandHandler_1.DeleteUserCommandHandler(this.userRepository);
}
return this._deleteUserCommandHandler;
}
get verifyEmailCommandHandler() {
if (!this._verifyEmailCommandHandler) {
this._verifyEmailCommandHandler = new VerifyEmailCommandHandler_1.VerifyEmailCommandHandler(this.userRepository);
}
return this._verifyEmailCommandHandler;
}
get requestPasswordResetCommandHandler() {
if (!this._requestPasswordResetCommandHandler) {
this._requestPasswordResetCommandHandler = new RequestPasswordResetCommandHandler_1.RequestPasswordResetCommandHandler(this.userRepository);
}
return this._requestPasswordResetCommandHandler;
}
get resetPasswordCommandHandler() {
if (!this._resetPasswordCommandHandler) {
this._resetPasswordCommandHandler = new ResetPasswordCommandHandler_1.ResetPasswordCommandHandler(this.userRepository);
}
return this._resetPasswordCommandHandler;
}
get createChatCommandHandler() {
if (!this._createChatCommandHandler) {
this._createChatCommandHandler = new CreateChatCommandHandler_1.CreateChatCommandHandler(this.chatRepository, this.userRepository);
}
return this._createChatCommandHandler;
}
get sendMessageCommandHandler() {
if (!this._sendMessageCommandHandler) {
this._sendMessageCommandHandler = new SendMessageCommandHandler_1.SendMessageCommandHandler(this.chatRepository);
}
return this._sendMessageCommandHandler;
}
get archiveChatCommandHandler() {
if (!this._archiveChatCommandHandler) {
this._archiveChatCommandHandler = new ChatArchiveCommandHandlers_1.ArchiveChatCommandHandler(this.chatRepository);
}
return this._archiveChatCommandHandler;
}
get restoreChatCommandHandler() {
if (!this._restoreChatCommandHandler) {
this._restoreChatCommandHandler = new ChatArchiveCommandHandlers_1.RestoreChatCommandHandler(this.chatRepository);
}
return this._restoreChatCommandHandler;
}
get createDeckCommandHandler() {
if (!this._createDeckCommandHandler) {
this._createDeckCommandHandler = new CreateDeckCommandHandler_1.CreateDeckCommandHandler(this.deckRepository, this.userRepository, this.organizationRepository);
}
return this._createDeckCommandHandler;
}
get updateDeckCommandHandler() {
if (!this._updateDeckCommandHandler) {
this._updateDeckCommandHandler = new UpdateDeckCommandHandler_1.UpdateDeckCommandHandler(this.deckRepository);
}
return this._updateDeckCommandHandler;
}
get deleteDeckCommandHandler() {
if (!this._deleteDeckCommandHandler) {
this._deleteDeckCommandHandler = new DeleteDeckCommandHandler_1.DeleteDeckCommandHandler(this.deckRepository);
}
return this._deleteDeckCommandHandler;
}
get createOrganizationCommandHandler() {
if (!this._createOrganizationCommandHandler) {
this._createOrganizationCommandHandler = new CreateOrganizationCommandHandler_1.CreateOrganizationCommandHandler(this.organizationRepository);
}
return this._createOrganizationCommandHandler;
}
get updateOrganizationCommandHandler() {
if (!this._updateOrganizationCommandHandler) {
this._updateOrganizationCommandHandler = new UpdateOrganizationCommandHandler_1.UpdateOrganizationCommandHandler(this.organizationRepository);
}
return this._updateOrganizationCommandHandler;
}
get deleteOrganizationCommandHandler() {
if (!this._deleteOrganizationCommandHandler) {
this._deleteOrganizationCommandHandler = new DeleteOrganizationCommandHandler_1.DeleteOrganizationCommandHandler(this.organizationRepository);
}
return this._deleteOrganizationCommandHandler;
}
get processOrgAuthCallbackCommandHandler() {
if (!this._processOrgAuthCallbackCommandHandler) {
this._processOrgAuthCallbackCommandHandler = new ProcessOrgAuthCallbackCommandHandler_1.ProcessOrgAuthCallbackCommandHandler(this.userRepository, this.organizationRepository);
}
return this._processOrgAuthCallbackCommandHandler;
}
get createContactCommandHandler() {
if (!this._createContactCommandHandler) {
this._createContactCommandHandler = new CreateContactCommandHandler_1.CreateContactCommandHandler(this.contactRepository);
}
return this._createContactCommandHandler;
}
get updateContactCommandHandler() {
if (!this._updateContactCommandHandler) {
this._updateContactCommandHandler = new UpdateContactCommandHandler_1.UpdateContactCommandHandler(this.contactRepository);
}
return this._updateContactCommandHandler;
}
get deleteContactCommandHandler() {
if (!this._deleteContactCommandHandler) {
this._deleteContactCommandHandler = new DeleteContactCommandHandler_1.DeleteContactCommandHandler(this.contactRepository);
}
return this._deleteContactCommandHandler;
}
// Query Handler getters
get getUserByIdQueryHandler() {
if (!this._getUserByIdQueryHandler) {
this._getUserByIdQueryHandler = new GetUserByIdQueryHandler_1.GetUserByIdQueryHandler(this.userRepository);
}
return this._getUserByIdQueryHandler;
}
get getUserChatsQueryHandler() {
if (!this._getUserChatsQueryHandler) {
this._getUserChatsQueryHandler = new GetUserChatsQueryHandler_1.GetUserChatsQueryHandler(this.chatRepository, this.chatArchiveRepository);
}
return this._getUserChatsQueryHandler;
}
get getChatHistoryQueryHandler() {
if (!this._getChatHistoryQueryHandler) {
this._getChatHistoryQueryHandler = new ChatHistoryQueryHandlers_1.GetChatHistoryQueryHandler(this.chatRepository, this.chatArchiveRepository);
}
return this._getChatHistoryQueryHandler;
}
get getArchivedChatsQueryHandler() {
if (!this._getArchivedChatsQueryHandler) {
this._getArchivedChatsQueryHandler = new ChatHistoryQueryHandlers_1.GetArchivedChatsQueryHandler(this.chatArchiveRepository);
}
return this._getArchivedChatsQueryHandler;
}
get getDeckByIdQueryHandler() {
if (!this._getDeckByIdQueryHandler) {
this._getDeckByIdQueryHandler = new GetDeckByIdQueryHandler_1.GetDeckByIdQueryHandler(this.deckRepository);
}
return this._getDeckByIdQueryHandler;
}
get getOrganizationByIdQueryHandler() {
if (!this._getOrganizationByIdQueryHandler) {
this._getOrganizationByIdQueryHandler = new GetOrganizationByIdQueryHandler_1.GetOrganizationByIdQueryHandler(this.organizationRepository);
}
return this._getOrganizationByIdQueryHandler;
}
get getOrganizationLoginUrlQueryHandler() {
if (!this._getOrganizationLoginUrlQueryHandler) {
this._getOrganizationLoginUrlQueryHandler = new GetOrganizationLoginUrlQueryHandler_1.GetOrganizationLoginUrlQueryHandler(this.organizationRepository);
}
return this._getOrganizationLoginUrlQueryHandler;
}
get getContactByIdQueryHandler() {
if (!this._getContactByIdQueryHandler) {
this._getContactByIdQueryHandler = new GetContactByIdQueryHandler_1.GetContactByIdQueryHandler(this.contactRepository);
}
return this._getContactByIdQueryHandler;
}
get getContactsByPageQueryHandler() {
if (!this._getContactsByPageQueryHandler) {
this._getContactsByPageQueryHandler = new GetContactsByPageQueryHandler_1.GetContactsByPageQueryHandler(this.contactRepository);
}
return this._getContactsByPageQueryHandler;
}
// New paginated query handlers
get getUsersByPageQueryHandler() {
if (!this._getUsersByPageQueryHandler) {
this._getUsersByPageQueryHandler = new GetUsersByPageQueryHandler_1.GetUsersByPageQueryHandler(this.userRepository);
}
return this._getUsersByPageQueryHandler;
}
get getDecksByPageQueryHandler() {
if (!this._getDecksByPageQueryHandler) {
this._getDecksByPageQueryHandler = new GetDecksByPageQueryHandler_1.GetDecksByPageQueryHandler(this.deckRepository);
}
return this._getDecksByPageQueryHandler;
}
get getOrganizationsByPageQueryHandler() {
if (!this._getOrganizationsByPageQueryHandler) {
this._getOrganizationsByPageQueryHandler = new GetOrganizationsByPageQueryHandler_1.GetOrganizationsByPageQueryHandler(this.organizationRepository);
}
return this._getOrganizationsByPageQueryHandler;
}
get getChatsByPageQueryHandler() {
if (!this._getChatsByPageQueryHandler) {
this._getChatsByPageQueryHandler = new GetChatsByPageQueryHandler_1.GetChatsByPageQueryHandler(this.chatRepository);
}
return this._getChatsByPageQueryHandler;
}
}
exports.DIContainer = DIContainer;
// Export singleton instance
exports.container = DIContainer.getInstance();
//# sourceMappingURL=DIContainer.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
import { DeckAggregate } from '../../Domain/Deck/DeckAggregate';
import { IDeckRepository } from '../../Domain/IRepository/IDeckRepository';
export interface SprDeckData {
name: string;
type: number;
cards: any[];
ctype: number;
exportDate: string;
version: string;
}
export interface ImportDeckCommand {
name: string;
type: number;
cards: any[];
ctype?: number;
userid: string;
}
export declare class DeckImportExportService {
private readonly deckRepo;
private readonly encryptionKey;
private readonly algorithm;
constructor(deckRepo: IDeckRepository);
exportDeckToSpr(deckId: string, userId: string): Promise<Buffer>;
importDeckFromSpr(sprData: Buffer, userId: string): Promise<DeckAggregate>;
importDeckFromJson(jsonData: any, userId: string): Promise<DeckAggregate>;
adminImportFromJson(jsonData: any, targetUserId: string, adminUserId: string): Promise<DeckAggregate>;
private encrypt;
private decrypt;
generateFilename(deckName: string): string;
}
//# sourceMappingURL=DeckImportExportService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"DeckImportExportService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/DeckImportExportService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAgB,MAAM,iCAAiC,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAG3E,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,uBAAuB;IAIpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAHrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;gBAEd,QAAQ,EAAE,eAAe;IAQhD,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqChE,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAmC1E,kBAAkB,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAgCzE,mBAAmB,CAAC,QAAQ,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA8B3G,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,OAAO;IAmBf,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;CAM7C"}
@@ -0,0 +1,195 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeckImportExportService = void 0;
const crypto = __importStar(require("crypto"));
const DeckAggregate_1 = require("../../Domain/Deck/DeckAggregate");
const Logger_1 = require("./Logger");
class DeckImportExportService {
constructor(deckRepo) {
this.deckRepo = deckRepo;
this.algorithm = 'aes-256-gcm';
this.encryptionKey = process.env.DECK_ENCRYPTION_KEY || 'your-32-byte-encryption-key-here!!';
if (this.encryptionKey.length !== 32) {
throw new Error('DECK_ENCRYPTION_KEY must be exactly 32 characters long');
}
}
async exportDeckToSpr(deckId, userId) {
try {
const deck = await this.deckRepo.findByIdIncludingDeleted(deckId);
if (!deck) {
throw new Error('Deck not found');
}
if (deck.userid !== userId) {
throw new Error('Unauthorized: You can only export your own decks');
}
const deckData = {
name: deck.name,
type: deck.type,
cards: deck.cards,
ctype: deck.ctype,
exportDate: new Date().toISOString(),
version: '1.0'
};
const jsonString = JSON.stringify(deckData);
const encrypted = this.encrypt(jsonString);
(0, Logger_1.logAuth)('Deck exported to SPR format', userId, {
deckId: deck.id,
deckName: deck.name,
cardCount: deck.cards.length
});
return encrypted;
}
catch (error) {
(0, Logger_1.logError)('Failed to export deck to SPR', error);
throw error;
}
}
async importDeckFromSpr(sprData, userId) {
try {
const decrypted = this.decrypt(sprData);
const deckData = JSON.parse(decrypted);
// Validate required fields
if (!deckData.name || !deckData.cards || deckData.type === undefined) {
throw new Error('Invalid SPR file format: missing required fields');
}
// Create new deck
const newDeck = new DeckAggregate_1.DeckAggregate();
newDeck.name = deckData.name;
newDeck.type = deckData.type;
newDeck.userid = userId;
newDeck.cards = deckData.cards;
newDeck.ctype = deckData.ctype || DeckAggregate_1.CType.PUBLIC;
newDeck.state = DeckAggregate_1.State.ACTIVE;
const createdDeck = await this.deckRepo.create(newDeck);
(0, Logger_1.logAuth)('Deck imported from SPR format', userId, {
deckId: createdDeck.id,
deckName: createdDeck.name,
cardCount: createdDeck.cards.length,
originalExportDate: deckData.exportDate
});
return createdDeck;
}
catch (error) {
(0, Logger_1.logError)('Failed to import deck from SPR', error);
throw error;
}
}
async importDeckFromJson(jsonData, userId) {
try {
// Validate required fields
if (!jsonData.name || !jsonData.cards || jsonData.type === undefined) {
throw new Error('Invalid JSON format: missing required fields (name, cards, type)');
}
// Create new deck
const newDeck = new DeckAggregate_1.DeckAggregate();
newDeck.name = jsonData.name;
newDeck.type = jsonData.type;
newDeck.userid = userId;
newDeck.cards = jsonData.cards;
newDeck.ctype = jsonData.ctype || DeckAggregate_1.CType.PUBLIC;
newDeck.state = DeckAggregate_1.State.ACTIVE;
const createdDeck = await this.deckRepo.create(newDeck);
(0, Logger_1.logAuth)('Deck imported from JSON format', userId, {
deckId: createdDeck.id,
deckName: createdDeck.name,
cardCount: createdDeck.cards.length
});
return createdDeck;
}
catch (error) {
(0, Logger_1.logError)('Failed to import deck from JSON', error);
throw error;
}
}
// Admin-only function to import JSON without encryption
async adminImportFromJson(jsonData, targetUserId, adminUserId) {
try {
if (!jsonData.name || !jsonData.cards || jsonData.type === undefined) {
throw new Error('Invalid JSON format: missing required fields (name, cards, type)');
}
const newDeck = new DeckAggregate_1.DeckAggregate();
newDeck.name = jsonData.name;
newDeck.type = jsonData.type;
newDeck.userid = targetUserId;
newDeck.cards = jsonData.cards;
newDeck.ctype = jsonData.ctype || DeckAggregate_1.CType.PUBLIC;
newDeck.state = jsonData.state || DeckAggregate_1.State.ACTIVE;
const createdDeck = await this.deckRepo.create(newDeck);
(0, Logger_1.logAuth)('Deck imported by admin from JSON', adminUserId, {
deckId: createdDeck.id,
deckName: createdDeck.name,
cardCount: createdDeck.cards.length,
targetUserId: targetUserId
});
return createdDeck;
}
catch (error) {
(0, Logger_1.logError)('Failed to admin import deck from JSON', error);
throw error;
}
}
encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.algorithm, this.encryptionKey, iv);
cipher.setAAD(Buffer.from('SerpentRace-Deck', 'utf8'));
let encrypted = cipher.update(text, 'utf8');
encrypted = Buffer.concat([encrypted, cipher.final()]);
const authTag = cipher.getAuthTag();
return Buffer.concat([iv, authTag, encrypted]);
}
decrypt(encryptedData) {
if (encryptedData.length < 32) {
throw new Error('Invalid SPR file: file too short');
}
const iv = encryptedData.slice(0, 16);
const authTag = encryptedData.slice(16, 32);
const encrypted = encryptedData.slice(32);
const decipher = crypto.createDecipheriv(this.algorithm, this.encryptionKey, iv);
decipher.setAAD(Buffer.from('SerpentRace-Deck', 'utf8'));
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, undefined, 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
generateFilename(deckName) {
// Sanitize deck name for filename
const sanitized = deckName.replace(/[^a-zA-Z0-9\-_]/g, '_');
const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
return `${sanitized}_${timestamp}.spr`;
}
}
exports.DeckImportExportService = DeckImportExportService;
//# sourceMappingURL=DeckImportExportService.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,65 @@
export interface EmailOptions {
to: string;
subject: string;
html?: string;
text?: string;
template?: string;
templateData?: any;
}
export interface EmailConfig {
host: string;
port: number;
secure: boolean;
auth: {
user: string;
pass: string;
};
from: string;
}
export declare class EmailService {
private transporter;
private config;
private templatesPath;
constructor();
private initializeTransporter;
/**
* Send email with template
* @param options - Email options including template and data
*/
sendEmail(options: EmailOptions): Promise<boolean>;
/**
* Send verification email to user
* @param userEmail - User's email address
* @param userName - User's name
* @param verificationToken - Verification token
* @param verificationUrl - Complete verification URL
* @param language - Language code ('en', 'hu', 'de')
*/
sendVerificationEmail(userEmail: string, userName: string, verificationToken: string, verificationUrl: string, language?: 'en' | 'hu' | 'de'): Promise<boolean>;
/**
* Send password reset email
* @param userEmail - User's email address
* @param userName - User's name
* @param resetToken - Password reset token
* @param resetUrl - Complete password reset URL
* @param language - Language code ('en', 'hu', 'de')
*/
sendPasswordResetEmail(userEmail: string, userName: string, resetToken: string, resetUrl: string, language?: 'en' | 'hu' | 'de'): Promise<boolean>;
/**
* Load and compile email template with language support
* @param templateName - Name of the template file (with or without language suffix)
* @param data - Data to replace placeholders in the template
*/
private loadTemplate;
/**
* Get localized verification email subject
* @param language - Language code ('en', 'hu', 'de')
*/
private getLocalizedVerificationSubject;
/**
* Get localized password reset email subject
* @param language - Language code ('en', 'hu', 'de')
*/
private getLocalizedPasswordResetSubject;
}
//# sourceMappingURL=EmailService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"EmailService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/EmailService.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,aAAa,CAAS;;IAmB9B,OAAO,CAAC,qBAAqB;IAiB7B;;;OAGG;IACG,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;IAgCxD;;;;;;;OAOG;IACG,qBAAqB,CACzB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,MAAM,EACzB,eAAe,EAAE,MAAM,EACvB,QAAQ,GAAE,IAAI,GAAG,IAAI,GAAG,IAAW,GAClC,OAAO,CAAC,OAAO,CAAC;IAuBnB;;;;;;;OAOG;IACG,sBAAsB,CAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,IAAI,GAAG,IAAI,GAAG,IAAW,GAClC,OAAO,CAAC,OAAO,CAAC;IAuBnB;;;;OAIG;YACW,YAAY;IAsD1B;;;OAGG;IACH,OAAO,CAAC,+BAA+B;IAWvC;;;OAGG;IACH,OAAO,CAAC,gCAAgC;CAUzC"}
@@ -0,0 +1,249 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailService = void 0;
const nodemailer = __importStar(require("nodemailer"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const Logger_1 = require("./Logger");
const EmailTemplateHelper_1 = require("./EmailTemplateHelper");
class EmailService {
constructor() {
this.templatesPath = path.join(__dirname, '../../Templates');
this.config = {
host: process.env.EMAIL_HOST || 'smtp.gmail.com',
port: parseInt(process.env.EMAIL_PORT || '587'),
secure: process.env.EMAIL_SECURE === 'true',
auth: {
user: process.env.EMAIL_USER || '',
pass: process.env.EMAIL_PASS || ''
},
from: process.env.EMAIL_FROM || 'noreply@serpentrace.com'
};
this.initializeTransporter();
}
initializeTransporter() {
try {
this.transporter = nodemailer.createTransport({
host: this.config.host,
port: this.config.port,
secure: this.config.secure,
auth: {
user: this.config.auth.user,
pass: this.config.auth.pass
}
});
}
catch (error) {
(0, Logger_1.logError)('EmailService initialization failed', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to initialize email service');
}
}
/**
* Send email with template
* @param options - Email options including template and data
*/
async sendEmail(options) {
try {
let htmlContent = options.html;
let textContent = options.text;
if (options.template) {
const templateResult = await this.loadTemplate(options.template, options.templateData || {});
htmlContent = templateResult.html;
textContent = templateResult.text;
}
const mailOptions = {
from: this.config.from,
to: options.to,
subject: options.subject,
html: htmlContent,
text: textContent
};
const result = await this.transporter.sendMail(mailOptions);
(0, Logger_1.logAuth)('Email sent successfully', undefined, {
messageId: result.messageId,
to: options.to,
subject: options.subject
});
return true;
}
catch (error) {
(0, Logger_1.logError)('Email sending failed', error instanceof Error ? error : new Error(String(error)));
return false;
}
}
/**
* Send verification email to user
* @param userEmail - User's email address
* @param userName - User's name
* @param verificationToken - Verification token
* @param verificationUrl - Complete verification URL
* @param language - Language code ('en', 'hu', 'de')
*/
async sendVerificationEmail(userEmail, userName, verificationToken, verificationUrl, language = 'en') {
try {
const templateName = language === 'en' ? 'verification' : `verification-${language}`;
const subject = this.getLocalizedVerificationSubject(language);
return await this.sendEmail({
to: userEmail,
subject,
template: templateName,
templateData: {
userName,
verificationToken,
verificationUrl,
companyName: 'SerpentRace',
supportEmail: 'support@serpentrace.com'
}
});
}
catch (error) {
(0, Logger_1.logError)('Verification email sending failed', error instanceof Error ? error : new Error(String(error)));
return false;
}
}
/**
* Send password reset email
* @param userEmail - User's email address
* @param userName - User's name
* @param resetToken - Password reset token
* @param resetUrl - Complete password reset URL
* @param language - Language code ('en', 'hu', 'de')
*/
async sendPasswordResetEmail(userEmail, userName, resetToken, resetUrl, language = 'en') {
try {
const templateName = language === 'en' ? 'password-reset' : `password-reset-${language}`;
const subject = this.getLocalizedPasswordResetSubject(language);
return await this.sendEmail({
to: userEmail,
subject,
template: templateName,
templateData: {
userName,
resetToken,
resetUrl,
companyName: 'SerpentRace',
supportEmail: 'support@serpentrace.com'
}
});
}
catch (error) {
(0, Logger_1.logError)('Password reset email sending failed', error instanceof Error ? error : new Error(String(error)));
return false;
}
}
/**
* Load and compile email template with language support
* @param templateName - Name of the template file (with or without language suffix)
* @param data - Data to replace placeholders in the template
*/
async loadTemplate(templateName, data) {
try {
// Try the specified template first
let htmlTemplatePath = path.join(this.templatesPath, `${templateName}.html`);
let textTemplatePath = path.join(this.templatesPath, `${templateName}.txt`);
let htmlTemplate = '';
let textTemplate = '';
// Load HTML template if it exists
if (fs.existsSync(htmlTemplatePath)) {
htmlTemplate = fs.readFileSync(htmlTemplatePath, 'utf8');
}
else {
// If language-specific template doesn't exist, try fallback to English
const baseName = templateName.replace(/-[a-z]{2}$/, ''); // Remove language suffix
const fallbackHtmlPath = path.join(this.templatesPath, `${baseName}.html`);
if (fs.existsSync(fallbackHtmlPath)) {
htmlTemplate = fs.readFileSync(fallbackHtmlPath, 'utf8');
}
}
// Load text template if it exists
if (fs.existsSync(textTemplatePath)) {
textTemplate = fs.readFileSync(textTemplatePath, 'utf8');
}
else {
// If language-specific template doesn't exist, try fallback to English
const baseName = templateName.replace(/-[a-z]{2}$/, ''); // Remove language suffix
const fallbackTextPath = path.join(this.templatesPath, `${baseName}.txt`);
if (fs.existsSync(fallbackTextPath)) {
textTemplate = fs.readFileSync(fallbackTextPath, 'utf8');
}
}
// If no templates found, throw error
if (!htmlTemplate && !textTemplate) {
throw new Error(`Template '${templateName}' not found`);
}
// Replace placeholders in templates
const processedTemplate = EmailTemplateHelper_1.EmailTemplateHelper.processTemplate({ html: htmlTemplate, text: textTemplate }, data);
return {
html: processedTemplate.html,
text: processedTemplate.text
};
}
catch (error) {
(0, Logger_1.logError)('Email template loading failed', error instanceof Error ? error : new Error(String(error)));
throw new Error(`Failed to load email template: ${templateName}`);
}
}
/**
* Get localized verification email subject
* @param language - Language code ('en', 'hu', 'de')
*/
getLocalizedVerificationSubject(language) {
const subjects = {
verification: {
en: 'SerpentRace - Verify Your Account',
hu: 'SerpentRace - Fiók megerősítése',
de: 'SerpentRace - Konto verifizieren'
}
};
return EmailTemplateHelper_1.EmailTemplateHelper.getLocalizedSubject('verification', subjects, language);
}
/**
* Get localized password reset email subject
* @param language - Language code ('en', 'hu', 'de')
*/
getLocalizedPasswordResetSubject(language) {
const subjects = {
passwordReset: {
en: 'SerpentRace - Password Reset Request',
hu: 'SerpentRace - Jelszó visszaállítás kérése',
de: 'SerpentRace - Passwort zurücksetzen'
}
};
return EmailTemplateHelper_1.EmailTemplateHelper.getLocalizedSubject('passwordReset', subjects, language);
}
}
exports.EmailService = EmailService;
//# sourceMappingURL=EmailService.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
export interface LocalizedSubjects {
[key: string]: {
en: string;
hu: string;
de: string;
};
}
export interface TemplateData {
[key: string]: any;
}
export interface EmailTemplate {
html: string;
text: string;
}
export declare class EmailTemplateHelper {
static getLocalizedSubject(subjectKey: string, subjects: LocalizedSubjects, language: 'en' | 'hu' | 'de'): string;
static replaceTemplatePlaceholders(template: string, data: TemplateData): string;
static processTemplate(templateContent: EmailTemplate, data: TemplateData): EmailTemplate;
}
//# sourceMappingURL=EmailTemplateHelper.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"EmailTemplateHelper.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/EmailTemplateHelper.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,mBAAmB;WAChB,mBAAmB,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAC3B,MAAM;WAIK,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM;WAMzE,eAAe,CAAC,eAAe,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,aAAa;CAMjG"}
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailTemplateHelper = void 0;
class EmailTemplateHelper {
static getLocalizedSubject(subjectKey, subjects, language) {
return subjects[subjectKey]?.[language] || subjects[subjectKey]?.['en'] || 'SerpentRace';
}
static replaceTemplatePlaceholders(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] !== undefined ? String(data[key]) : match;
});
}
static processTemplate(templateContent, data) {
return {
html: this.replaceTemplatePlaceholders(templateContent.html, data),
text: this.replaceTemplatePlaceholders(templateContent.text, data)
};
}
}
exports.EmailTemplateHelper = EmailTemplateHelper;
//# sourceMappingURL=EmailTemplateHelper.js.map
@@ -0,0 +1 @@
{"version":3,"file":"EmailTemplateHelper.js","sourceRoot":"","sources":["../../../src/Application/Services/EmailTemplateHelper.ts"],"names":[],"mappings":";;;AAiBA,MAAa,mBAAmB;IACvB,MAAM,CAAC,mBAAmB,CAC/B,UAAkB,EAClB,QAA2B,EAC3B,QAA4B;QAE5B,OAAO,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC;IAC3F,CAAC;IAEM,MAAM,CAAC,2BAA2B,CAAC,QAAgB,EAAE,IAAkB;QAC5E,OAAO,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACvD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,eAAe,CAAC,eAA8B,EAAE,IAAkB;QAC9E,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,2BAA2B,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC;YAClE,IAAI,EAAE,IAAI,CAAC,2BAA2B,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC;SACnE,CAAC;IACJ,CAAC;CACF;AArBD,kDAqBC"}
@@ -0,0 +1,11 @@
import { Response } from 'express';
export declare class ErrorResponseService {
static sendError(res: Response, statusCode: number, message: string, details?: any): Response;
static sendInternalServerError(res: Response): Response;
static sendBadRequest(res: Response, message?: string, details?: any): Response;
static sendUnauthorized(res: Response, message?: string): Response;
static sendForbidden(res: Response, message?: string): Response;
static sendNotFound(res: Response, message?: string): Response;
static sendConflict(res: Response, message?: string): Response;
}
//# sourceMappingURL=ErrorResponseService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ErrorResponseService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/ErrorResponseService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,QAAQ;IAQ7F,MAAM,CAAC,uBAAuB,CAAC,GAAG,EAAE,QAAQ,GAAG,QAAQ;IAIvD,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,MAAsB,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,QAAQ;IAI9F,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,MAAuB,GAAG,QAAQ;IAIlF,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,MAAoB,GAAG,QAAQ;IAI5E,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,MAAoB,GAAG,QAAQ;IAI3E,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,MAAmB,GAAG,QAAQ;CAG3E"}
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorResponseService = void 0;
class ErrorResponseService {
static sendError(res, statusCode, message, details) {
const errorResponse = { error: message };
if (details) {
errorResponse.details = details;
}
return res.status(statusCode).json(errorResponse);
}
static sendInternalServerError(res) {
return this.sendError(res, 500, 'Internal server error');
}
static sendBadRequest(res, message = 'Bad request', details) {
return this.sendError(res, 400, message, details);
}
static sendUnauthorized(res, message = 'Unauthorized') {
return this.sendError(res, 401, message);
}
static sendForbidden(res, message = 'Forbidden') {
return this.sendError(res, 403, message);
}
static sendNotFound(res, message = 'Not found') {
return this.sendError(res, 404, message);
}
static sendConflict(res, message = 'Conflict') {
return this.sendError(res, 409, message);
}
}
exports.ErrorResponseService = ErrorResponseService;
//# sourceMappingURL=ErrorResponseService.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ErrorResponseService.js","sourceRoot":"","sources":["../../../src/Application/Services/ErrorResponseService.ts"],"names":[],"mappings":";;;AAEA,MAAa,oBAAoB;IAC/B,MAAM,CAAC,SAAS,CAAC,GAAa,EAAE,UAAkB,EAAE,OAAe,EAAE,OAAa;QAChF,MAAM,aAAa,GAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAC9C,IAAI,OAAO,EAAE,CAAC;YACZ,aAAa,CAAC,OAAO,GAAG,OAAO,CAAC;QAClC,CAAC;QACD,OAAO,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,CAAC,uBAAuB,CAAC,GAAa;QAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,GAAa,EAAE,UAAkB,aAAa,EAAE,OAAa;QACjF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,GAAa,EAAE,UAAkB,cAAc;QACrE,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,GAAa,EAAE,UAAkB,WAAW;QAC/D,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,GAAa,EAAE,UAAkB,WAAW;QAC9D,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,GAAa,EAAE,UAAkB,UAAU;QAC7D,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACF;AAhCD,oDAgCC"}
@@ -0,0 +1,27 @@
import { Request, Response } from 'express';
import { UserState } from '../../Domain/User/UserAggregate';
export interface TokenPayload {
userId: string;
authLevel: 0 | 1;
userStatus: UserState;
orgId: string;
iat?: number;
exp?: number;
}
export declare class JWTService {
private readonly secretKey;
private readonly tokenExpiry;
private readonly cookieName;
constructor();
create(payload: TokenPayload, res: Response): string;
verify(req: Request): TokenPayload | null;
shouldRefreshToken(payload: TokenPayload): boolean;
refreshIfNeeded(payload: TokenPayload, res: Response): boolean;
/**
* Parse duration string to seconds (e.g., "24h", "7d", "30m")
* @param duration Duration string
* @returns Duration in seconds
*/
private parseDuration;
}
//# sourceMappingURL=JWTService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"JWTService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/JWTService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAE5D,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,UAAU;IACnB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;;IAqBpC,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,GAAG,MAAM;IAuBpD,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI;IAazC,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAYlD,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO;IAe9D;;;;OAIG;IACH,OAAO,CAAC,aAAa;CAkBxB"}
@@ -0,0 +1,102 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JWTService = void 0;
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
class JWTService {
constructor() {
this.secretKey = process.env.JWT_SECRET || 'your-secret-key';
let expiry = 86400;
if (process.env.JWT_EXPIRY) {
expiry = parseInt(process.env.JWT_EXPIRY);
}
else if (process.env.JWT_EXPIRATION) {
expiry = this.parseDuration(process.env.JWT_EXPIRATION);
}
this.tokenExpiry = expiry;
this.cookieName = 'auth_token';
if (process.env.NODE_ENV === 'production' && (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'your-secret-key')) {
throw new Error('JWT_SECRET environment variable must be set in production');
}
}
create(payload, res) {
const now = Math.floor(Date.now() / 1000);
const payloadWithTimestamps = {
...payload,
iat: now,
exp: now + this.tokenExpiry
};
// Don't use expiresIn option since we're manually setting exp in payload
const options = {};
const token = jsonwebtoken_1.default.sign(payloadWithTimestamps, this.secretKey, options);
res.cookie(this.cookieName, token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: this.tokenExpiry * 1000, // Convert to milliseconds
});
return token;
}
verify(req) {
try {
const token = req.cookies[this.cookieName];
if (!token)
return null;
const decoded = jsonwebtoken_1.default.verify(token, this.secretKey);
return decoded;
}
catch (error) {
return null;
}
}
// Check if token needs refresh (within 25% of expiry time)
shouldRefreshToken(payload) {
if (!payload.exp || !payload.iat)
return false;
const now = Math.floor(Date.now() / 1000);
const tokenAge = now - payload.iat;
const tokenLifetime = payload.exp - payload.iat;
const refreshThreshold = tokenLifetime * 0.75; // Refresh when 75% of lifetime has passed
return tokenAge >= refreshThreshold;
}
// Conditionally refresh token only if needed
refreshIfNeeded(payload, res) {
if (this.shouldRefreshToken(payload)) {
// Create new token with fresh timestamps, but same user data
const freshPayload = {
userId: payload.userId,
authLevel: payload.authLevel,
userStatus: payload.userStatus,
orgId: payload.orgId
};
this.create(freshPayload, res);
return true;
}
return false;
}
/**
* Parse duration string to seconds (e.g., "24h", "7d", "30m")
* @param duration Duration string
* @returns Duration in seconds
*/
parseDuration(duration) {
const match = duration.match(/^(\d+)([smhd])$/);
if (!match) {
throw new Error(`Invalid duration format: ${duration}. Use format like '24h', '7d', '30m'`);
}
const [, value, unit] = match;
const num = parseInt(value);
switch (unit) {
case 's': return num; // seconds
case 'm': return num * 60; // minutes
case 'h': return num * 60 * 60; // hours
case 'd': return num * 60 * 60 * 24; // days
default:
throw new Error(`Unsupported duration unit: ${unit}`);
}
}
}
exports.JWTService = JWTService;
//# sourceMappingURL=JWTService.js.map
@@ -0,0 +1 @@
{"version":3,"file":"JWTService.js","sourceRoot":"","sources":["../../../src/Application/Services/JWTService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
import { LoggingService, LogLevel } from './LoggingService';
import { Request, Response } from 'express';
declare const logger: LoggingService;
export declare const logRequest: (message: string, req?: Request, res?: Response, metadata?: any) => void;
export declare const logError: (message: string, error?: Error, req?: Request, res?: Response) => void;
export declare const logWarning: (message: string, metadata?: any, req?: Request, res?: Response) => void;
export declare const logAuth: (message: string, userId?: string, metadata?: any, req?: Request, res?: Response) => void;
export declare const logDatabase: (message: string, query?: string, executionTime?: number, metadata?: any) => void;
export declare const logStartup: (message: string, metadata?: any) => void;
export declare const logConnection: (message: string, type: string, status: "success" | "failure" | "attempt", metadata?: any) => void;
export declare const logOther: (message: string, metadata?: any, req?: Request, res?: Response) => void;
export { LoggingService, LogLevel };
export default logger;
//# sourceMappingURL=Logger.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/Logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAG5C,QAAA,MAAM,MAAM,gBAA+B,CAAC;AAG5C,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,MAAM,OAAO,EAAE,MAAM,QAAQ,EAAE,WAAW,GAAG,SAExF,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,EAAE,MAAM,OAAO,EAAE,MAAM,QAAQ,SAOrF,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,WAAW,GAAG,EAAE,MAAM,OAAO,EAAE,MAAM,QAAQ,SAExF,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,EAAE,SAAS,MAAM,EAAE,WAAW,GAAG,EAAE,MAAM,OAAO,EAAE,MAAM,QAAQ,SAMtG,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,EAAE,QAAQ,MAAM,EAAE,gBAAgB,MAAM,EAAE,WAAW,GAAG,SAOlG,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,WAAW,GAAG,SAEzD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,EAAE,MAAM,MAAM,EAAE,QAAQ,SAAS,GAAG,SAAS,GAAG,SAAS,EAAE,WAAW,GAAG,SAOrH,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,EAAE,WAAW,GAAG,EAAE,MAAM,OAAO,EAAE,MAAM,QAAQ,SAEtF,CAAC;AAGF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC;AACpC,eAAe,MAAM,CAAC"}
+62
View File
@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LogLevel = exports.LoggingService = exports.logOther = exports.logConnection = exports.logStartup = exports.logDatabase = exports.logAuth = exports.logWarning = exports.logError = exports.logRequest = void 0;
const LoggingService_1 = require("./LoggingService");
Object.defineProperty(exports, "LoggingService", { enumerable: true, get: function () { return LoggingService_1.LoggingService; } });
Object.defineProperty(exports, "LogLevel", { enumerable: true, get: function () { return LoggingService_1.LogLevel; } });
// Singleton instance
const logger = LoggingService_1.LoggingService.getInstance();
// Convenience functions for each log level
const logRequest = (message, req, res, metadata) => {
logger.log(LoggingService_1.LogLevel.REQUEST, message, metadata, req, res);
};
exports.logRequest = logRequest;
const logError = (message, error, req, res) => {
const metadata = error ? {
name: error.name,
message: error.message,
stack: error.stack
} : undefined;
logger.log(LoggingService_1.LogLevel.ERROR, message, metadata, req, res);
};
exports.logError = logError;
const logWarning = (message, metadata, req, res) => {
logger.log(LoggingService_1.LogLevel.WARNING, message, metadata, req, res);
};
exports.logWarning = logWarning;
const logAuth = (message, userId, metadata, req, res) => {
const authMetadata = {
userId,
...metadata
};
logger.log(LoggingService_1.LogLevel.AUTH, message, authMetadata, req, res);
};
exports.logAuth = logAuth;
const logDatabase = (message, query, executionTime, metadata) => {
const dbMetadata = {
query: query ? query.substring(0, 200) : undefined,
executionTime,
...metadata
};
logger.log(LoggingService_1.LogLevel.DATABASE, message, dbMetadata);
};
exports.logDatabase = logDatabase;
const logStartup = (message, metadata) => {
logger.log(LoggingService_1.LogLevel.STARTUP, message, metadata);
};
exports.logStartup = logStartup;
const logConnection = (message, type, status, metadata) => {
const connectionMetadata = {
connectionType: type,
status,
...metadata
};
logger.log(LoggingService_1.LogLevel.CONNECTION, message, connectionMetadata);
};
exports.logConnection = logConnection;
const logOther = (message, metadata, req, res) => {
logger.log(LoggingService_1.LogLevel.OTHER, message, metadata, req, res);
};
exports.logOther = logOther;
exports.default = logger;
//# sourceMappingURL=Logger.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../../../src/Application/Services/Logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
import { Request, Response, NextFunction } from 'express';
export declare enum LogLevel {
REQUEST = "REQUEST",
ERROR = "ERROR",
WARNING = "WARNING",
AUTH = "AUTH",
DATABASE = "DATABASE",
STARTUP = "STARTUP",
CONNECTION = "CONNECTION",
OTHER = "OTHER"
}
export interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
metadata?: any;
requestId?: string;
userId?: string;
ip?: string;
userAgent?: string;
method?: string;
url?: string;
statusCode?: number;
responseTime?: number;
}
export declare class LoggingService {
private static instance;
private minioClient;
private logBuffer;
private currentLogFile;
private logCount;
private readonly maxLogsPerFile;
private readonly logsDir;
private readonly bucketName;
private uploadInterval;
private constructor();
static getInstance(): LoggingService;
private initializeLogsDirectory;
private initializeMinioClient;
private ensureBucketExists;
private startPeriodicUpload;
private getMonthlyDirectory;
private getMonthlyMinioPrefix;
private createNewLogFile;
private formatLogEntry;
private writeToLocalFile;
private rotateLogFile;
private uploadToMinio;
private logToConsole;
log(level: LogLevel, message: string, metadata?: any, req?: Request, res?: Response, responseTime?: number): void;
private generateRequestId;
shutdown(): Promise<void>;
requestLoggingMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
errorLoggingMiddleware(): (error: Error, req: Request, res: Response, next: NextFunction) => void;
}
export default LoggingService;
//# sourceMappingURL=LoggingService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"LoggingService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/LoggingService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG1D,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,KAAK,UAAU;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAiB;IACxC,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsD;IACrF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuD;IAClF,OAAO,CAAC,cAAc,CAA+B;IAErD,OAAO;IAcP,MAAM,CAAC,WAAW,IAAI,cAAc;IAOpC,OAAO,CAAC,uBAAuB;IAgB/B,OAAO,CAAC,qBAAqB;YAqCf,kBAAkB;IAchC,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,cAAc;YAmBR,gBAAgB;YAkBhB,aAAa;YAgBb,aAAa;IA2B3B,OAAO,CAAC,YAAY;IAwBb,GAAG,CACR,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,GAAG,EACd,GAAG,CAAC,EAAE,OAAO,EACb,GAAG,CAAC,EAAE,QAAQ,EACd,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI;IAuCP,OAAO,CAAC,iBAAiB;IAIZ,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB/B,wBAAwB,KACrB,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IA4BlD,sBAAsB,KACnB,OAAO,KAAK,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;CAcxE;AAED,eAAe,cAAc,CAAC"}
@@ -0,0 +1,363 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoggingService = exports.LogLevel = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const Minio = __importStar(require("minio"));
var LogLevel;
(function (LogLevel) {
LogLevel["REQUEST"] = "REQUEST";
LogLevel["ERROR"] = "ERROR";
LogLevel["WARNING"] = "WARNING";
LogLevel["AUTH"] = "AUTH";
LogLevel["DATABASE"] = "DATABASE";
LogLevel["STARTUP"] = "STARTUP";
LogLevel["CONNECTION"] = "CONNECTION";
LogLevel["OTHER"] = "OTHER";
})(LogLevel || (exports.LogLevel = LogLevel = {}));
class LoggingService {
constructor() {
this.minioClient = null;
this.logBuffer = [];
this.currentLogFile = null;
this.logCount = 0;
this.maxLogsPerFile = parseInt(process.env.MAX_LOGS_PER_FILE || '10000');
this.logsDir = path_1.default.join(process.cwd(), 'logs');
this.bucketName = process.env.MINIO_BUCKET_NAME || 'serpentrace-logs';
this.uploadInterval = null;
this.initializeLogsDirectory();
this.initializeMinioClient();
this.createNewLogFile();
if (process.env.NODE_ENV !== 'test') {
this.startPeriodicUpload();
}
process.on('SIGTERM', () => this.shutdown());
process.on('SIGINT', () => this.shutdown());
process.on('beforeExit', () => this.shutdown());
}
static getInstance() {
if (!LoggingService.instance) {
LoggingService.instance = new LoggingService();
}
return LoggingService.instance;
}
initializeLogsDirectory() {
try {
if (!fs_1.default.existsSync(this.logsDir)) {
fs_1.default.mkdirSync(this.logsDir, { recursive: true });
}
// Create monthly subdirectory
const monthlyDir = this.getMonthlyDirectory();
if (!fs_1.default.existsSync(monthlyDir)) {
fs_1.default.mkdirSync(monthlyDir, { recursive: true });
}
}
catch (error) {
console.error('Failed to initialize logs directory:', error);
}
}
initializeMinioClient() {
try {
// Check if in production or development
if (process.env.NODE_ENV === 'production') {
if (process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY) {
this.minioClient = new Minio.Client({
endPoint: process.env.MINIO_ENDPOINT,
port: parseInt(process.env.MINIO_PORT || '9000'),
useSSL: process.env.MINIO_USE_SSL === 'true',
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY
});
this.ensureBucketExists();
}
else {
console.warn('Minio configuration not found. Logs will only be stored locally and in console.');
}
}
else {
// Development-specific Minio configuration
this.minioClient = new Minio.Client({
endPoint: 'localhost',
port: 9000,
useSSL: false,
accessKey: 'serpentrace',
secretKey: 'serpentrace123!'
});
this.ensureBucketExists();
}
}
catch (error) {
console.error('Failed to initialize Minio client:', error);
this.minioClient = null;
}
}
async ensureBucketExists() {
if (!this.minioClient)
return;
try {
const exists = await this.minioClient.bucketExists(this.bucketName);
if (!exists) {
await this.minioClient.makeBucket(this.bucketName);
this.log(LogLevel.STARTUP, `Created Minio bucket: ${this.bucketName}`);
}
}
catch (error) {
console.error('Failed to ensure bucket exists:', error);
}
}
startPeriodicUpload() {
// Upload current log file to Minio every 2 minutes
this.uploadInterval = setInterval(async () => {
if (this.currentLogFile && this.minioClient) {
await this.uploadToMinio(this.currentLogFile);
}
}, 2 * 60 * 1000); // 2 minutes
}
getMonthlyDirectory() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return path_1.default.join(this.logsDir, `${year}-${month}`);
}
getMonthlyMinioPrefix() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return `${year}-${month}/`;
}
createNewLogFile() {
const now = new Date();
const timestamp = now.toISOString().replace(/[:.]/g, '-');
const fileName = `serpentrace-${timestamp}.log`;
this.currentLogFile = path_1.default.join(this.getMonthlyDirectory(), fileName);
this.logCount = 0;
// Write log file header
const header = `# SerpentRace Backend Logs\n# Started: ${now.toISOString()}\n# Max entries per file: ${this.maxLogsPerFile}\n\n`;
try {
fs_1.default.writeFileSync(this.currentLogFile, header);
}
catch (error) {
console.error('Failed to create log file:', error);
}
}
formatLogEntry(entry) {
const parts = [
entry.timestamp,
`[${entry.level}]`,
entry.message
];
if (entry.requestId)
parts.push(`ReqId:${entry.requestId}`);
if (entry.userId)
parts.push(`UserId:${entry.userId}`);
if (entry.ip)
parts.push(`IP:${entry.ip}`);
if (entry.method && entry.url)
parts.push(`${entry.method} ${entry.url}`);
if (entry.statusCode)
parts.push(`Status:${entry.statusCode}`);
if (entry.responseTime)
parts.push(`Time:${entry.responseTime}ms`);
if (entry.userAgent)
parts.push(`UA:${entry.userAgent.substring(0, 50)}`);
if (entry.metadata)
parts.push(`Meta:${JSON.stringify(entry.metadata)}`);
return parts.join(' | ');
}
async writeToLocalFile(entry) {
if (!this.currentLogFile)
return;
try {
const logLine = this.formatLogEntry(entry) + '\n';
fs_1.default.appendFileSync(this.currentLogFile, logLine);
this.logCount++;
// Check if we need to rotate the log file
if (this.logCount >= this.maxLogsPerFile) {
await this.rotateLogFile();
}
}
catch (error) {
console.error('Failed to write to log file:', error);
}
}
async rotateLogFile() {
if (!this.currentLogFile)
return;
try {
// Upload current file to Minio before rotating
await this.uploadToMinio(this.currentLogFile);
// Create new log file
this.createNewLogFile();
this.log(LogLevel.OTHER, 'Log file rotated due to size limit');
}
catch (error) {
console.error('Failed to rotate log file:', error);
}
}
async uploadToMinio(filePath) {
if (!this.minioClient) {
console.warn('Minio client not initialized, skipping upload');
return;
}
if (!fs_1.default.existsSync(filePath)) {
console.warn(`Log file does not exist: ${filePath}`);
return;
}
try {
const fileName = path_1.default.basename(filePath);
const objectName = this.getMonthlyMinioPrefix() + fileName;
console.log(`Attempting to upload log file to Minio: ${objectName}`);
await this.minioClient.fPutObject(this.bucketName, objectName, filePath);
console.log(`Successfully uploaded log file to Minio: ${objectName}`);
}
catch (error) {
console.error('Failed to upload to Minio:', error);
console.error('Minio config:', {
endpoint: this.minioClient ? 'configured' : 'not configured',
bucket: this.bucketName
});
}
}
logToConsole(entry) {
const formattedEntry = this.formatLogEntry(entry);
switch (entry.level) {
case LogLevel.ERROR:
console.error(formattedEntry);
break;
case LogLevel.WARNING:
console.warn(formattedEntry);
break;
case LogLevel.REQUEST:
case LogLevel.AUTH:
case LogLevel.DATABASE:
case LogLevel.CONNECTION:
console.info(formattedEntry);
break;
case LogLevel.STARTUP:
console.log(formattedEntry);
break;
default:
console.log(formattedEntry);
}
}
log(level, message, metadata, req, res, responseTime) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
metadata
};
// Add request context if available
if (req) {
entry.requestId = req.requestId || this.generateRequestId();
entry.userId = req.user?.userId;
entry.ip = req.ip || req.socket?.remoteAddress || 'unknown';
entry.userAgent = req.get ? req.get('User-Agent') : 'unknown';
entry.method = req.method;
entry.url = req.originalUrl || req.url;
}
if (res) {
entry.statusCode = res.statusCode;
}
if (responseTime !== undefined) {
entry.responseTime = responseTime;
}
// Log to all three destinations
this.logToConsole(entry);
this.writeToLocalFile(entry);
// Add to buffer for potential batch processing
this.logBuffer.push(entry);
// Limit buffer size
if (this.logBuffer.length > 1000) {
this.logBuffer = this.logBuffer.slice(-500);
}
}
generateRequestId() {
return Math.random().toString(36).substr(2, 9);
}
async shutdown() {
try {
// Clear the upload interval
if (this.uploadInterval) {
clearInterval(this.uploadInterval);
this.uploadInterval = null;
}
// Upload current log file to Minio
if (this.currentLogFile) {
await this.uploadToMinio(this.currentLogFile);
}
this.log(LogLevel.STARTUP, 'Logging service shutting down gracefully');
// Give time for final logs to be written
await new Promise(resolve => setTimeout(resolve, 1000));
}
catch (error) {
console.error('Error during logging service shutdown:', error);
}
}
// Middleware factory methods
requestLoggingMiddleware() {
return (req, res, next) => {
const startTime = Date.now();
// Generate request ID
req.requestId = this.generateRequestId();
// Log request start
this.log(LogLevel.REQUEST, `Incoming request`, undefined, req);
// Override res.end to log response
const originalEnd = res.end.bind(res);
res.end = (...args) => {
const responseTime = Date.now() - startTime;
LoggingService.getInstance().log(LogLevel.REQUEST, `Request completed`, undefined, req, res, responseTime);
return originalEnd(...args);
};
next();
};
}
errorLoggingMiddleware() {
return (error, req, res, next) => {
this.log(LogLevel.ERROR, `Unhandled error: ${error.message}`, {
stack: error.stack,
name: error.name
}, req, res);
next(error);
};
}
}
exports.LoggingService = LoggingService;
exports.default = LoggingService;
//# sourceMappingURL=LoggingService.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
export declare class PasswordService {
private static readonly SALT_ROUNDS;
/**
* Hashes a plain text password using bcrypt
* @param password - The plain text password to hash
* @returns Promise<string> - The hashed password
*/
static hashPassword(password: string): Promise<string>;
/**
* Verifies a plain text password against a hashed password
* @param password - The plain text password to verify
* @param hashedPassword - The hashed password to compare against
* @returns Promise<boolean> - True if password matches, false otherwise
*/
static verifyPassword(password: string, hashedPassword: string): Promise<boolean>;
/**
* Validates password strength requirements
* @param password - The password to validate
* @returns object - Object containing isValid boolean and error messages
*/
static validatePasswordStrength(password: string): {
isValid: boolean;
errors: string[];
};
}
//# sourceMappingURL=PasswordService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"PasswordService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/PasswordService.ts"],"names":[],"mappings":"AAGA,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAM;IAEzC;;;;OAIG;WACU,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkB5D;;;;;OAKG;WACU,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBvF;;;;OAIG;IACH,MAAM,CAAC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE;CAyC1F"}
@@ -0,0 +1,124 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PasswordService = void 0;
const bcrypt = __importStar(require("bcrypt"));
const Logger_1 = require("./Logger");
class PasswordService {
/**
* Hashes a plain text password using bcrypt
* @param password - The plain text password to hash
* @returns Promise<string> - The hashed password
*/
static async hashPassword(password) {
try {
if (!password || typeof password !== 'string') {
throw new Error('Password must be a non-empty string');
}
return await bcrypt.hash(password, this.SALT_ROUNDS);
}
catch (error) {
(0, Logger_1.logError)('PasswordService.hashPassword error', error instanceof Error ? error : new Error(String(error)));
if (error instanceof Error && error.message === 'Password must be a non-empty string') {
throw error; // Re-throw validation errors as-is
}
throw new Error('Failed to hash password');
}
}
/**
* Verifies a plain text password against a hashed password
* @param password - The plain text password to verify
* @param hashedPassword - The hashed password to compare against
* @returns Promise<boolean> - True if password matches, false otherwise
*/
static async verifyPassword(password, hashedPassword) {
try {
if (!password || typeof password !== 'string') {
return false; // Invalid input should return false, not throw
}
if (!hashedPassword || typeof hashedPassword !== 'string') {
return false; // Invalid input should return false, not throw
}
return await bcrypt.compare(password, hashedPassword);
}
catch (error) {
(0, Logger_1.logError)('PasswordService.verifyPassword error', error instanceof Error ? error : new Error(String(error)));
return false; // Return false on error instead of throwing
}
}
/**
* Validates password strength requirements
* @param password - The password to validate
* @returns object - Object containing isValid boolean and error messages
*/
static validatePasswordStrength(password) {
try {
const errors = [];
if (!password || typeof password !== 'string') {
errors.push('Password must be provided as a string');
return { isValid: false, errors };
}
if (password.length < 8) {
errors.push('Password must be at least 8 characters long');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (!/\d/.test(password)) {
errors.push('Password must contain at least one number');
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
errors.push('Password must contain at least one special character');
}
return {
isValid: errors.length === 0,
errors
};
}
catch (error) {
(0, Logger_1.logError)('PasswordService.validatePasswordStrength error', error instanceof Error ? error : new Error(String(error)));
return {
isValid: false,
errors: ['Password validation failed due to internal error']
};
}
}
}
exports.PasswordService = PasswordService;
PasswordService.SALT_ROUNDS = 12;
//# sourceMappingURL=PasswordService.js.map
@@ -0,0 +1 @@
{"version":3,"file":"PasswordService.js","sourceRoot":"","sources":["../../../src/Application/Services/PasswordService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
export interface ActiveChatData {
chatId: string;
participants: string[];
lastActivity: Date;
messageCount: number;
chatType: 'direct' | 'group' | 'game';
gameId?: string;
name?: string;
}
export interface ActiveUserData {
userId: string;
activeChatIds: string[];
lastActivity: Date;
isOnline: boolean;
}
export declare class RedisService {
private static instance;
private client;
private isConnected;
private constructor();
static getInstance(): RedisService;
connect(): Promise<void>;
disconnect(): Promise<void>;
setActiveChat(chatId: string, chatData: ActiveChatData): Promise<void>;
getActiveChat(chatId: string): Promise<ActiveChatData | null>;
removeActiveChat(chatId: string): Promise<void>;
getAllActiveChats(): Promise<ActiveChatData[]>;
setActiveUser(userId: string, userData: ActiveUserData): Promise<void>;
getActiveUser(userId: string): Promise<ActiveUserData | null>;
removeActiveUser(userId: string): Promise<void>;
addUserToChat(userId: string, chatId: string): Promise<void>;
removeUserFromChat(userId: string, chatId: string): Promise<void>;
getUserActiveChats(userId: string): Promise<string[]>;
updateChatActivity(chatId: string, messageCount?: number): Promise<void>;
getInactiveChats(inactivityMinutes: number): Promise<string[]>;
cleanupInactiveChats(inactivityMinutes: number): Promise<string[]>;
ping(): Promise<boolean>;
isRedisConnected(): boolean;
}
//# sourceMappingURL=RedisService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"RedisService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/RedisService.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,IAAI,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,IAAI,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAe;IACtC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAkB;IAErC,OAAO;WAyBO,WAAW,IAAI,YAAY;IAO5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAWxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBtE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAwB7D,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/C,iBAAiB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IA4B9C,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBtE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAqB7D,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/C,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB5D,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAajE,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAUrD,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAexE,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAc9D,oBAAoB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAelE,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IAU9B,gBAAgB,IAAI,OAAO;CAGrC"}
@@ -0,0 +1,273 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisService = void 0;
const redis_1 = require("redis");
const Logger_1 = require("./Logger");
class RedisService {
constructor() {
this.isConnected = false;
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
this.client = (0, redis_1.createClient)({
url: redisUrl,
socket: {
reconnectStrategy: (retries) => Math.min(retries * 50, 500)
}
});
this.client.on('error', (err) => {
(0, Logger_1.logError)('Redis connection error', err);
this.isConnected = false;
});
this.client.on('connect', () => {
(0, Logger_1.logStartup)('Redis client connected successfully');
this.isConnected = true;
});
this.client.on('disconnect', () => {
(0, Logger_1.logWarning)('Redis client disconnected');
this.isConnected = false;
});
}
static getInstance() {
if (!RedisService.instance) {
RedisService.instance = new RedisService();
}
return RedisService.instance;
}
async connect() {
try {
if (!this.isConnected) {
await this.client.connect();
}
}
catch (error) {
(0, Logger_1.logError)('Failed to connect to Redis', error);
throw error;
}
}
async disconnect() {
try {
if (this.isConnected) {
await this.client.disconnect();
}
}
catch (error) {
(0, Logger_1.logError)('Failed to disconnect from Redis', error);
}
}
async setActiveChat(chatId, chatData) {
try {
const key = `active_chat:${chatId}`;
await this.client.hSet(key, {
chatId: chatData.chatId,
participants: JSON.stringify(chatData.participants),
lastActivity: chatData.lastActivity.toISOString(),
messageCount: chatData.messageCount.toString(),
chatType: chatData.chatType,
gameId: chatData.gameId || '',
name: chatData.name || ''
});
// Set expiration for 1 hour of inactivity
await this.client.expire(key, 3600);
}
catch (error) {
(0, Logger_1.logError)(`Failed to set active chat ${chatId}`, error);
}
}
async getActiveChat(chatId) {
try {
const key = `active_chat:${chatId}`;
const data = await this.client.hGetAll(key);
if (!data.chatId) {
return null;
}
return {
chatId: data.chatId,
participants: JSON.parse(data.participants),
lastActivity: new Date(data.lastActivity),
messageCount: parseInt(data.messageCount, 10),
chatType: data.chatType,
gameId: data.gameId || undefined,
name: data.name || undefined
};
}
catch (error) {
(0, Logger_1.logError)(`Failed to get active chat ${chatId}`, error);
return null;
}
}
async removeActiveChat(chatId) {
try {
const key = `active_chat:${chatId}`;
await this.client.del(key);
}
catch (error) {
(0, Logger_1.logError)(`Failed to remove active chat ${chatId}`, error);
}
}
async getAllActiveChats() {
try {
const pattern = 'active_chat:*';
const keys = await this.client.keys(pattern);
const chats = [];
for (const key of keys) {
const data = await this.client.hGetAll(key);
if (data.chatId) {
chats.push({
chatId: data.chatId,
participants: JSON.parse(data.participants),
lastActivity: new Date(data.lastActivity),
messageCount: parseInt(data.messageCount, 10),
chatType: data.chatType,
gameId: data.gameId || undefined,
name: data.name || undefined
});
}
}
return chats;
}
catch (error) {
(0, Logger_1.logError)('Failed to get all active chats', error);
return [];
}
}
async setActiveUser(userId, userData) {
try {
const key = `active_user:${userId}`;
await this.client.hSet(key, {
userId: userData.userId,
activeChatIds: JSON.stringify(userData.activeChatIds),
lastActivity: userData.lastActivity.toISOString(),
isOnline: userData.isOnline.toString()
});
// Set expiration for 2 hours
await this.client.expire(key, 7200);
}
catch (error) {
(0, Logger_1.logError)(`Failed to set active user ${userId}`, error);
}
}
async getActiveUser(userId) {
try {
const key = `active_user:${userId}`;
const data = await this.client.hGetAll(key);
if (!data.userId) {
return null;
}
return {
userId: data.userId,
activeChatIds: JSON.parse(data.activeChatIds),
lastActivity: new Date(data.lastActivity),
isOnline: data.isOnline === 'true'
};
}
catch (error) {
(0, Logger_1.logError)(`Failed to get active user ${userId}`, error);
return null;
}
}
async removeActiveUser(userId) {
try {
const key = `active_user:${userId}`;
await this.client.del(key);
}
catch (error) {
(0, Logger_1.logError)(`Failed to remove active user ${userId}`, error);
}
}
async addUserToChat(userId, chatId) {
try {
const userData = await this.getActiveUser(userId) || {
userId,
activeChatIds: [],
lastActivity: new Date(),
isOnline: true
};
if (!userData.activeChatIds.includes(chatId)) {
userData.activeChatIds.push(chatId);
userData.lastActivity = new Date();
await this.setActiveUser(userId, userData);
}
}
catch (error) {
(0, Logger_1.logError)(`Failed to add user ${userId} to chat ${chatId}`, error);
}
}
async removeUserFromChat(userId, chatId) {
try {
const userData = await this.getActiveUser(userId);
if (userData) {
userData.activeChatIds = userData.activeChatIds.filter(id => id !== chatId);
userData.lastActivity = new Date();
await this.setActiveUser(userId, userData);
}
}
catch (error) {
(0, Logger_1.logError)(`Failed to remove user ${userId} from chat ${chatId}`, error);
}
}
async getUserActiveChats(userId) {
try {
const userData = await this.getActiveUser(userId);
return userData?.activeChatIds || [];
}
catch (error) {
(0, Logger_1.logError)(`Failed to get active chats for user ${userId}`, error);
return [];
}
}
async updateChatActivity(chatId, messageCount) {
try {
const chatData = await this.getActiveChat(chatId);
if (chatData) {
chatData.lastActivity = new Date();
if (messageCount !== undefined) {
chatData.messageCount = messageCount;
}
await this.setActiveChat(chatId, chatData);
}
}
catch (error) {
(0, Logger_1.logError)(`Failed to update chat activity ${chatId}`, error);
}
}
async getInactiveChats(inactivityMinutes) {
try {
const cutoffTime = new Date(Date.now() - inactivityMinutes * 60 * 1000);
const allChats = await this.getAllActiveChats();
return allChats
.filter(chat => chat.lastActivity < cutoffTime)
.map(chat => chat.chatId);
}
catch (error) {
(0, Logger_1.logError)('Failed to get inactive chats', error);
return [];
}
}
async cleanupInactiveChats(inactivityMinutes) {
try {
const inactiveChats = await this.getInactiveChats(inactivityMinutes);
for (const chatId of inactiveChats) {
await this.removeActiveChat(chatId);
}
return inactiveChats;
}
catch (error) {
(0, Logger_1.logError)('Failed to cleanup inactive chats', error);
return [];
}
}
async ping() {
try {
const result = await this.client.ping();
return result === 'PONG';
}
catch (error) {
(0, Logger_1.logError)('Redis ping failed', error);
return false;
}
}
isRedisConnected() {
return this.isConnected;
}
}
exports.RedisService = RedisService;
//# sourceMappingURL=RedisService.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,80 @@
export interface VerificationToken {
token: string;
expiresAt: Date;
createdAt: Date;
}
export interface PasswordResetToken {
token: string;
expiresAt: Date;
createdAt: Date;
}
export declare class TokenService {
private static readonly VERIFICATION_TOKEN_EXPIRES_HOURS;
private static readonly PASSWORD_RESET_TOKEN_EXPIRES_HOURS;
private static readonly TOKEN_LENGTH;
/**
* Generate a secure random token
* @param length - Length of the token in bytes (default: 32)
* @returns Hexadecimal string token
*/
static generateSecureToken(length?: number): string;
/**
* Generate email verification token with expiration
* @returns VerificationToken object with token and expiration info
*/
static generateVerificationToken(): VerificationToken;
/**
* Generate password reset token with expiration
* @returns PasswordResetToken object with token and expiration info
*/
static generatePasswordResetToken(): PasswordResetToken;
/**
* Check if a token has expired
* @param expiresAt - Expiration date of the token
* @returns True if token has expired, false otherwise
*/
static isTokenExpired(expiresAt: Date): boolean;
/**
* Validate token format (basic validation)
* @param token - Token to validate
* @returns True if token format is valid, false otherwise
*/
static isValidTokenFormat(token: string): boolean;
/**
* Generate a verification URL with token
* @param baseUrl - Base URL of the application
* @param token - Verification token
* @returns Complete verification URL
*/
static generateVerificationUrl(baseUrl: string, token: string): string;
/**
* Generate a password reset URL with token
* @param baseUrl - Base URL of the application
* @param token - Password reset token
* @returns Complete password reset URL
*/
static generatePasswordResetUrl(baseUrl: string, token: string): string;
/**
* Hash a token for secure storage in database
* @param token - Plain text token to hash
* @returns Hashed token
*/
static hashToken(token: string): Promise<string>;
/**
* Verify a plain text token against a hashed token
* @param plainToken - Plain text token to verify
* @param hashedToken - Hashed token to compare against
* @returns True if tokens match, false otherwise
*/
static verifyToken(plainToken: string, hashedToken: string): Promise<boolean>;
/**
* Get token expiration info in human-readable format
* @param expiresAt - Expiration date
* @returns Human-readable expiration info
*/
static getExpirationInfo(expiresAt: Date): {
expired: boolean;
timeLeft: string;
};
}
//# sourceMappingURL=TokenService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"TokenService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/TokenService.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gCAAgC,CAAM;IAC9D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kCAAkC,CAAK;IAC/D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAM;IAE1C;;;;OAIG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,GAAE,MAAkC,GAAG,MAAM;IAS9E;;;OAGG;IACH,MAAM,CAAC,yBAAyB,IAAI,iBAAiB;IAiBrD;;;OAGG;IACH,MAAM,CAAC,0BAA0B,IAAI,kBAAkB;IAiBvD;;;;OAIG;IACH,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,IAAI,GAAG,OAAO;IAS/C;;;;OAIG;IACH,MAAM,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAiBjD;;;;;OAKG;IACH,MAAM,CAAC,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAWtE;;;;;OAKG;IACH,MAAM,CAAC,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAWvE;;;;OAIG;WACU,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAatD;;;;;OAKG;WACU,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAcnF;;;;OAIG;IACH,MAAM,CAAC,iBAAiB,CAAC,SAAS,EAAE,IAAI,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;CAuClF"}
@@ -0,0 +1,245 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TokenService = void 0;
const crypto = __importStar(require("crypto"));
const Logger_1 = require("./Logger");
class TokenService {
/**
* Generate a secure random token
* @param length - Length of the token in bytes (default: 32)
* @returns Hexadecimal string token
*/
static generateSecureToken(length = TokenService.TOKEN_LENGTH) {
try {
return crypto.randomBytes(length).toString('hex');
}
catch (error) {
(0, Logger_1.logError)('TokenService.generateSecureToken error', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to generate secure token');
}
}
/**
* Generate email verification token with expiration
* @returns VerificationToken object with token and expiration info
*/
static generateVerificationToken() {
try {
const token = this.generateSecureToken();
const createdAt = new Date();
const expiresAt = new Date(createdAt.getTime() + (this.VERIFICATION_TOKEN_EXPIRES_HOURS * 60 * 60 * 1000));
return {
token,
createdAt,
expiresAt
};
}
catch (error) {
(0, Logger_1.logError)('TokenService.generateVerificationToken error', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to generate verification token');
}
}
/**
* Generate password reset token with expiration
* @returns PasswordResetToken object with token and expiration info
*/
static generatePasswordResetToken() {
try {
const token = this.generateSecureToken();
const createdAt = new Date();
const expiresAt = new Date(createdAt.getTime() + (this.PASSWORD_RESET_TOKEN_EXPIRES_HOURS * 60 * 60 * 1000));
return {
token,
createdAt,
expiresAt
};
}
catch (error) {
(0, Logger_1.logError)('TokenService.generatePasswordResetToken error', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to generate password reset token');
}
}
/**
* Check if a token has expired
* @param expiresAt - Expiration date of the token
* @returns True if token has expired, false otherwise
*/
static isTokenExpired(expiresAt) {
try {
return new Date() > expiresAt;
}
catch (error) {
(0, Logger_1.logError)('TokenService.isTokenExpired error', error instanceof Error ? error : new Error(String(error)));
return true; // Assume expired on error for security
}
}
/**
* Validate token format (basic validation)
* @param token - Token to validate
* @returns True if token format is valid, false otherwise
*/
static isValidTokenFormat(token) {
try {
if (!token || typeof token !== 'string') {
return false;
}
// Check if token is hexadecimal and has expected length
const hexRegex = /^[a-f0-9]+$/i;
const expectedLength = this.TOKEN_LENGTH * 2; // Each byte becomes 2 hex characters
return hexRegex.test(token) && token.length === expectedLength;
}
catch (error) {
(0, Logger_1.logError)('TokenService.isValidTokenFormat error', error instanceof Error ? error : new Error(String(error)));
return false;
}
}
/**
* Generate a verification URL with token
* @param baseUrl - Base URL of the application
* @param token - Verification token
* @returns Complete verification URL
*/
static generateVerificationUrl(baseUrl, token) {
try {
// Remove trailing slash from baseUrl if present
const cleanBaseUrl = baseUrl.replace(/\/$/, '');
return `${cleanBaseUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`;
}
catch (error) {
(0, Logger_1.logError)('TokenService.generateVerificationUrl error', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to generate verification URL');
}
}
/**
* Generate a password reset URL with token
* @param baseUrl - Base URL of the application
* @param token - Password reset token
* @returns Complete password reset URL
*/
static generatePasswordResetUrl(baseUrl, token) {
try {
// Remove trailing slash from baseUrl if present
const cleanBaseUrl = baseUrl.replace(/\/$/, '');
return `${cleanBaseUrl}/api/auth/reset-password?token=${encodeURIComponent(token)}`;
}
catch (error) {
(0, Logger_1.logError)('TokenService.generatePasswordResetUrl error', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to generate password reset URL');
}
}
/**
* Hash a token for secure storage in database
* @param token - Plain text token to hash
* @returns Hashed token
*/
static async hashToken(token) {
try {
if (!token || typeof token !== 'string') {
throw new Error('Token must be a non-empty string');
}
return crypto.createHash('sha256').update(token).digest('hex');
}
catch (error) {
(0, Logger_1.logError)('TokenService.hashToken error', error instanceof Error ? error : new Error(String(error)));
throw new Error('Failed to hash token');
}
}
/**
* Verify a plain text token against a hashed token
* @param plainToken - Plain text token to verify
* @param hashedToken - Hashed token to compare against
* @returns True if tokens match, false otherwise
*/
static async verifyToken(plainToken, hashedToken) {
try {
if (!plainToken || !hashedToken) {
return false;
}
const hashedPlainToken = await this.hashToken(plainToken);
return hashedPlainToken === hashedToken;
}
catch (error) {
(0, Logger_1.logError)('TokenService.verifyToken error', error instanceof Error ? error : new Error(String(error)));
return false;
}
}
/**
* Get token expiration info in human-readable format
* @param expiresAt - Expiration date
* @returns Human-readable expiration info
*/
static getExpirationInfo(expiresAt) {
try {
const now = new Date();
const expired = now > expiresAt;
if (expired) {
const timeAgo = Math.floor((now.getTime() - expiresAt.getTime()) / (1000 * 60));
return {
expired: true,
timeLeft: `Expired ${timeAgo} minute(s) ago`
};
}
const timeLeft = Math.floor((expiresAt.getTime() - now.getTime()) / (1000 * 60));
const hours = Math.floor(timeLeft / 60);
const minutes = timeLeft % 60;
let timeString = '';
if (hours > 0) {
timeString = `${hours} hour(s)`;
if (minutes > 0) {
timeString += ` and ${minutes} minute(s)`;
}
}
else {
timeString = `${minutes} minute(s)`;
}
return {
expired: false,
timeLeft: `Expires in ${timeString}`
};
}
catch (error) {
(0, Logger_1.logError)('TokenService.getExpirationInfo error', error instanceof Error ? error : new Error(String(error)));
return {
expired: true,
timeLeft: 'Unable to determine expiration'
};
}
}
}
exports.TokenService = TokenService;
TokenService.VERIFICATION_TOKEN_EXPIRES_HOURS = 24;
TokenService.PASSWORD_RESET_TOKEN_EXPIRES_HOURS = 1;
TokenService.TOKEN_LENGTH = 32;
//# sourceMappingURL=TokenService.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,64 @@
import { Request, Response, NextFunction } from 'express';
/**
* Common validation middleware functions for request validation
*/
export declare class ValidationMiddleware {
/**
* Validates required fields in request body
* @param requiredFields Array of required field names
*/
static validateRequiredFields(requiredFields: string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Validates field types in request body
* @param fieldTypes Object mapping field names to expected types
*/
static validateFieldTypes(fieldTypes: Record<string, 'string' | 'number' | 'boolean' | 'array' | 'object'>): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Validates string field length constraints
* @param constraints Object mapping field names to min/max length
*/
static validateStringLength(constraints: Record<string, {
min?: number;
max?: number;
}>): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Validates email format
* @param emailFields Array of field names that should contain valid emails
*/
static validateEmailFormat(emailFields: string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Validates UUIDs format
* @param uuidFields Array of field names that should contain valid UUIDs
*/
static validateUUIDFormat(uuidFields: string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Validates numeric constraints
* @param constraints Object mapping field names to min/max values
*/
static validateNumericConstraints(constraints: Record<string, {
min?: number;
max?: number;
}>): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Validates that arrays are not empty
* @param arrayFields Array of field names that should contain non-empty arrays
*/
static validateNonEmptyArrays(arrayFields: string[]): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Validates allowed values for enum-like fields
* @param allowedValues Object mapping field names to arrays of allowed values
*/
static validateAllowedValues(allowedValues: Record<string, any[]>): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
/**
* Combines multiple validation middlewares
* @param validations Array of validation middleware functions
*/
static combine(validations: Array<(req: Request, res: Response, next: NextFunction) => void>): (req: Request, res: Response, next: NextFunction) => Promise<void>;
/**
* Helper method to get nested values from request
* @param req Request object
* @param path Dot-notation path like 'body.user.id'
*/
private static getNestedValue;
}
//# sourceMappingURL=ValidationMiddleware.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ValidationMiddleware.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/ValidationMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI1D;;GAEG;AACH,qBAAa,oBAAoB;IAE7B;;;OAGG;IACH,MAAM,CAAC,sBAAsB,CAAC,cAAc,EAAE,MAAM,EAAE,IAC1C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IAyB3D;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,IAC9F,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IA6B3D;;;OAGG;IACH,MAAM,CAAC,oBAAoB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,IAC3E,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IAiC3D;;;OAGG;IACH,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IACpC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IA4B3D;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,IAClC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IAgC3D;;;OAGG;IACH,MAAM,CAAC,0BAA0B,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,IACjF,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IAiC3D;;;OAGG;IACH,MAAM,CAAC,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,IACvC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IA6B3D;;;OAGG;IACH,MAAM,CAAC,qBAAqB,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IACrD,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IA2B3D;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC,IAC1E,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY;IA+BjE;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;CAchC"}
@@ -0,0 +1,268 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationMiddleware = void 0;
const ErrorResponseService_1 = require("./ErrorResponseService");
const Logger_1 = require("./Logger");
/**
* Common validation middleware functions for request validation
*/
class ValidationMiddleware {
/**
* Validates required fields in request body
* @param requiredFields Array of required field names
*/
static validateRequiredFields(requiredFields) {
return (req, res, next) => {
const missingFields = [];
for (const field of requiredFields) {
if (!req.body || req.body[field] === undefined || req.body[field] === null || req.body[field] === '') {
missingFields.push(field);
}
}
if (missingFields.length > 0) {
(0, Logger_1.logWarning)('Validation failed - missing required fields', {
missingFields,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Missing required fields', { missingFields });
}
next();
};
}
/**
* Validates field types in request body
* @param fieldTypes Object mapping field names to expected types
*/
static validateFieldTypes(fieldTypes) {
return (req, res, next) => {
const typeErrors = [];
for (const [field, expectedType] of Object.entries(fieldTypes)) {
if (req.body && req.body[field] !== undefined) {
const actualType = Array.isArray(req.body[field]) ? 'array' : typeof req.body[field];
if (actualType !== expectedType) {
typeErrors.push(`Field '${field}' should be ${expectedType}, got ${actualType}`);
}
}
}
if (typeErrors.length > 0) {
(0, Logger_1.logWarning)('Validation failed - invalid field types', {
typeErrors,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Invalid field types', { errors: typeErrors });
}
next();
};
}
/**
* Validates string field length constraints
* @param constraints Object mapping field names to min/max length
*/
static validateStringLength(constraints) {
return (req, res, next) => {
const lengthErrors = [];
for (const [field, constraint] of Object.entries(constraints)) {
if (req.body && typeof req.body[field] === 'string') {
const value = req.body[field];
if (constraint.min !== undefined && value.length < constraint.min) {
lengthErrors.push(`Field '${field}' must be at least ${constraint.min} characters`);
}
if (constraint.max !== undefined && value.length > constraint.max) {
lengthErrors.push(`Field '${field}' must not exceed ${constraint.max} characters`);
}
}
}
if (lengthErrors.length > 0) {
(0, Logger_1.logWarning)('Validation failed - string length constraints', {
lengthErrors,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'String length validation failed', { errors: lengthErrors });
}
next();
};
}
/**
* Validates email format
* @param emailFields Array of field names that should contain valid emails
*/
static validateEmailFormat(emailFields) {
return (req, res, next) => {
const emailErrors = [];
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
for (const field of emailFields) {
if (req.body && req.body[field] && typeof req.body[field] === 'string') {
if (!emailRegex.test(req.body[field])) {
emailErrors.push(`Field '${field}' must contain a valid email address`);
}
}
}
if (emailErrors.length > 0) {
(0, Logger_1.logWarning)('Validation failed - invalid email format', {
emailErrors,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Email format validation failed', { errors: emailErrors });
}
next();
};
}
/**
* Validates UUIDs format
* @param uuidFields Array of field names that should contain valid UUIDs
*/
static validateUUIDFormat(uuidFields) {
return (req, res, next) => {
const uuidErrors = [];
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
for (const field of uuidFields) {
const value = field.includes('.')
? this.getNestedValue(req, field)
: req.body?.[field] || req.params?.[field] || req.query?.[field];
if (value && typeof value === 'string') {
if (!uuidRegex.test(value)) {
uuidErrors.push(`Field '${field}' must contain a valid UUID`);
}
}
}
if (uuidErrors.length > 0) {
(0, Logger_1.logWarning)('Validation failed - invalid UUID format', {
uuidErrors,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'UUID format validation failed', { errors: uuidErrors });
}
next();
};
}
/**
* Validates numeric constraints
* @param constraints Object mapping field names to min/max values
*/
static validateNumericConstraints(constraints) {
return (req, res, next) => {
const numericErrors = [];
for (const [field, constraint] of Object.entries(constraints)) {
if (req.body && typeof req.body[field] === 'number') {
const value = req.body[field];
if (constraint.min !== undefined && value < constraint.min) {
numericErrors.push(`Field '${field}' must be at least ${constraint.min}`);
}
if (constraint.max !== undefined && value > constraint.max) {
numericErrors.push(`Field '${field}' must not exceed ${constraint.max}`);
}
}
}
if (numericErrors.length > 0) {
(0, Logger_1.logWarning)('Validation failed - numeric constraints', {
numericErrors,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Numeric validation failed', { errors: numericErrors });
}
next();
};
}
/**
* Validates that arrays are not empty
* @param arrayFields Array of field names that should contain non-empty arrays
*/
static validateNonEmptyArrays(arrayFields) {
return (req, res, next) => {
const arrayErrors = [];
for (const field of arrayFields) {
if (req.body && Array.isArray(req.body[field])) {
if (req.body[field].length === 0) {
arrayErrors.push(`Field '${field}' must not be empty`);
}
}
else if (req.body && req.body[field] !== undefined) {
arrayErrors.push(`Field '${field}' must be an array`);
}
}
if (arrayErrors.length > 0) {
(0, Logger_1.logWarning)('Validation failed - empty arrays', {
arrayErrors,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Array validation failed', { errors: arrayErrors });
}
next();
};
}
/**
* Validates allowed values for enum-like fields
* @param allowedValues Object mapping field names to arrays of allowed values
*/
static validateAllowedValues(allowedValues) {
return (req, res, next) => {
const valueErrors = [];
for (const [field, allowed] of Object.entries(allowedValues)) {
if (req.body && req.body[field] !== undefined) {
if (!allowed.includes(req.body[field])) {
valueErrors.push(`Field '${field}' must be one of: ${allowed.join(', ')}`);
}
}
}
if (valueErrors.length > 0) {
(0, Logger_1.logWarning)('Validation failed - disallowed values', {
valueErrors,
endpoint: req.path
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Value validation failed', { errors: valueErrors });
}
next();
};
}
/**
* Combines multiple validation middlewares
* @param validations Array of validation middleware functions
*/
static combine(validations) {
return async (req, res, next) => {
let currentIndex = 0;
const runNext = (error) => {
if (error) {
return next(error);
}
if (currentIndex >= validations.length) {
return next();
}
const currentValidation = validations[currentIndex++];
try {
currentValidation(req, res, (err) => {
if (res.headersSent) {
return; // Response already sent, don't continue
}
runNext(err);
});
}
catch (error) {
(0, Logger_1.logError)('Validation middleware error', error, req, res);
ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
};
runNext();
};
}
/**
* Helper method to get nested values from request
* @param req Request object
* @param path Dot-notation path like 'body.user.id'
*/
static getNestedValue(req, path) {
const parts = path.split('.');
let current = req;
for (const part of parts) {
if (current && typeof current === 'object') {
current = current[part];
}
else {
return undefined;
}
}
return current;
}
}
exports.ValidationMiddleware = ValidationMiddleware;
//# sourceMappingURL=ValidationMiddleware.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,66 @@
import { Server as HttpServer } from 'http';
import { ChatAggregate } from '../../Domain/Chat/ChatAggregate';
export declare class WebSocketService {
private io;
private jwtService;
private chatRepository;
private chatArchiveRepository;
private userRepository;
private redisService;
private connectedUsers;
private chatTimeout;
private maxMessagesPerUser;
private messageCleanupWeeks;
private userMessageCounts;
constructor(httpServer: HttpServer);
private initializeRedis;
private setupSocketHandlers;
private handleConnection;
private handleJoinChat;
private handleLeaveChat;
private handleSendMessage;
private handleCreateGroup;
private handleCreateDirectChat;
private handleCreateGameChat;
private handleGetChatHistory;
private handleDeleteChat;
private handleDeleteChatArchive;
private handleDeleteMessage;
private handleDisconnection;
private calculateUnreadMessages;
private pruneMessages;
private notifyOfflineUsers;
private setupArchivingScheduler;
createGameChat(gameId: string, gameName: string, playerIds: string[]): Promise<ChatAggregate | null>;
getConnectedUserCount(): number;
isUserConnected(userId: string): boolean;
cleanup(): Promise<void>;
/**
* Manually trigger cleanup of old messages and chats
* This can be called by admin endpoints for maintenance
*/
triggerManualCleanup(): Promise<{
deletedArchives: number;
deletedChats: number;
}>;
/**
* Clean up old messages from archived chats based on messageCleanupWeeks setting
*/
private cleanupOldMessages;
/**
* Check if user has exceeded message rate limit
* @param userId User ID to check
* @returns true if within limit, false if exceeded
*/
private checkMessageRateLimit;
/**
* Delete a specific message from chat history
* This can be used for moderation purposes
*/
deleteMessage(chatId: string, messageId: string, moderatorUserId: string): Promise<boolean>;
/**
* Clean up old user message count entries (called periodically)
*/
private cleanupMessageCounts;
}
//# sourceMappingURL=WebSocketService.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"WebSocketService.d.ts","sourceRoot":"","sources":["../../../src/Application/Services/WebSocketService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAM5C,OAAO,EAAE,aAAa,EAAmC,MAAM,iCAAiC,CAAC;AAkDjG,qBAAa,gBAAgB;IACzB,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,qBAAqB,CAAwB;IACrD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,iBAAiB,CAAgE;gBAE7E,UAAU,EAAE,UAAU;YA6BpB,eAAe;IAQ7B,OAAO,CAAC,mBAAmB;YA4Db,gBAAgB;YA2EhB,cAAc;YA4Cd,eAAe;YAsBf,iBAAiB;YAsEjB,iBAAiB;YAwFjB,sBAAsB;YA4EtB,oBAAoB;YAsEpB,oBAAoB;YA4CpB,gBAAgB;YA2DhB,uBAAuB;YAqCvB,mBAAmB;YA4BnB,mBAAmB;IAoBjC,OAAO,CAAC,uBAAuB;IAM/B,OAAO,CAAC,aAAa;YAgCP,kBAAkB;IAgBhC,OAAO,CAAC,uBAAuB;IA8DlB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAyC1G,qBAAqB,IAAI,MAAM;IAI/B,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIlC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrC;;;OAGG;IACU,oBAAoB,IAAI,OAAO,CAAC;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAmC/F;;OAEG;YACW,kBAAkB;IAiChC;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAwB7B;;;OAGG;IACU,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiFxG;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAU/B"}
@@ -0,0 +1,966 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketService = void 0;
const socket_io_1 = require("socket.io");
const JWTService_1 = require("./JWTService");
const ChatRepository_1 = require("../../Infrastructure/Repository/ChatRepository");
const ChatArchiveRepository_1 = require("../../Infrastructure/Repository/ChatArchiveRepository");
const UserRepository_1 = require("../../Infrastructure/Repository/UserRepository");
const ChatAggregate_1 = require("../../Domain/Chat/ChatAggregate");
const UserAggregate_1 = require("../../Domain/User/UserAggregate");
const Logger_1 = require("./Logger");
const RedisService_1 = require("./RedisService");
const uuid_1 = require("uuid");
class WebSocketService {
constructor(httpServer) {
this.connectedUsers = new Map();
this.userMessageCounts = new Map();
this.io = new socket_io_1.Server(httpServer, {
cors: {
origin: ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:8080'],
methods: ['GET', 'POST'],
credentials: true
}
});
this.jwtService = new JWTService_1.JWTService();
this.chatRepository = new ChatRepository_1.ChatRepository();
this.chatArchiveRepository = new ChatArchiveRepository_1.ChatArchiveRepository();
this.userRepository = new UserRepository_1.UserRepository();
this.redisService = RedisService_1.RedisService.getInstance();
this.chatTimeout = parseInt(process.env.CHAT_INACTIVITY_TIMEOUT_MINUTES || '30');
this.maxMessagesPerUser = parseInt(process.env.CHAT_MAX_MESSAGES_PER_USER || '100');
this.messageCleanupWeeks = parseInt(process.env.CHAT_MESSAGE_CLEANUP_WEEKS || '4');
// Initialize Redis connection
this.initializeRedis();
this.setupSocketHandlers();
this.setupArchivingScheduler();
(0, Logger_1.logRequest)('WebSocket service initialized', undefined, undefined, {
chatTimeoutMinutes: this.chatTimeout
});
}
async initializeRedis() {
try {
await this.redisService.connect();
}
catch (error) {
(0, Logger_1.logError)('Failed to initialize Redis connection', error);
}
}
setupSocketHandlers() {
this.io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token || socket.handshake.headers.cookie
?.split(';')
.find(c => c.trim().startsWith('auth_token='))
?.split('=')[1];
if (!token) {
(0, Logger_1.logWarning)('WebSocket connection rejected - No token provided', {
socketId: socket.id,
ip: socket.handshake.address
});
return next(new Error('Authentication required'));
}
// Create a mock request object for JWT verification
const mockRequest = {
headers: {
authorization: `Bearer ${token}`,
cookie: `auth_token=${token}`
},
cookies: {
auth_token: token
}
};
const payload = this.jwtService.verify(mockRequest);
if (!payload) {
(0, Logger_1.logWarning)('WebSocket connection rejected - Invalid token', {
socketId: socket.id,
ip: socket.handshake.address
});
return next(new Error('Invalid token'));
}
socket.userId = payload.userId;
socket.authLevel = payload.authLevel;
socket.userStatus = payload.userStatus;
socket.orgId = payload.orgId;
(0, Logger_1.logAuth)('WebSocket connection authenticated', payload.userId, {
socketId: socket.id,
authLevel: payload.authLevel,
userStatus: payload.userStatus,
orgId: payload.orgId
});
next();
}
catch (error) {
(0, Logger_1.logError)('WebSocket authentication error', error);
next(new Error('Authentication failed'));
}
});
this.io.on('connection', (socket) => {
this.handleConnection(socket);
});
}
async handleConnection(socket) {
const userId = socket.userId;
// Store connected user
this.connectedUsers.set(userId, socket);
// Load user's active chats and join rooms
try {
const userChats = await this.chatRepository.findActiveChatsForUser(userId);
const chatIds = userChats.map(chat => chat.id);
// Join all chat rooms
chatIds.forEach(chatId => {
socket.join(chatId);
});
// Store user's chat memberships in Redis
await this.redisService.setActiveUser(userId, {
userId,
activeChatIds: chatIds,
lastActivity: new Date(),
isOnline: true
});
// Also store each active chat in Redis
for (const chat of userChats) {
await this.redisService.setActiveChat(chat.id, {
chatId: chat.id,
participants: chat.users,
lastActivity: chat.lastActivity || new Date(),
messageCount: chat.messages.length,
chatType: chat.type,
gameId: chat.gameId || undefined,
name: chat.name || undefined
});
}
(0, Logger_1.logAuth)('User connected to WebSocket', userId, {
socketId: socket.id,
activeChats: chatIds.length
});
// Send user their active chats with unread counts
const chatsWithUnread = await Promise.all(userChats.map(async (chat) => ({
id: chat.id,
type: chat.type,
name: chat.name,
gameId: chat.gameId,
users: chat.users,
lastActivity: chat.lastActivity,
unreadCount: this.calculateUnreadMessages(chat, userId),
isArchived: false
})));
socket.emit('chats:list', chatsWithUnread);
}
catch (error) {
(0, Logger_1.logError)('Error loading user chats on connection', error, undefined, undefined);
socket.emit('error', { message: 'Failed to load chats' });
}
// Setup event handlers
socket.on('chat:join', (data) => this.handleJoinChat(socket, data));
socket.on('chat:leave', (data) => this.handleLeaveChat(socket, data));
socket.on('message:send', (data) => this.handleSendMessage(socket, data));
socket.on('group:create', (data) => this.handleCreateGroup(socket, data));
socket.on('chat:direct', (data) => this.handleCreateDirectChat(socket, data));
socket.on('game:chat:create', (data) => this.handleCreateGameChat(socket, data));
socket.on('chat:history', (data) => this.handleGetChatHistory(socket, data));
socket.on('chat:delete', (data) => this.handleDeleteChat(socket, data));
socket.on('chat:archive:delete', (data) => this.handleDeleteChatArchive(socket, data));
socket.on('message:delete', (data) => this.handleDeleteMessage(socket, data));
socket.on('disconnect', () => this.handleDisconnection(socket));
}
async handleJoinChat(socket, data) {
try {
const userId = socket.userId;
const chat = await this.chatRepository.findById(data.chatId);
if (!chat) {
socket.emit('error', { message: 'Chat not found' });
return;
}
// Check if user is member of this chat
if (!chat.users.includes(userId)) {
socket.emit('error', { message: 'Unauthorized to join this chat' });
return;
}
// Join the chat room
socket.join(data.chatId);
// Add to user's active chats in Redis
await this.redisService.addUserToChat(userId, data.chatId);
// Update chat activity in Redis
await this.redisService.updateChatActivity(data.chatId);
// Update last activity in database
await this.chatRepository.update(data.chatId, { lastActivity: new Date() });
(0, Logger_1.logAuth)('User joined chat', userId, {
chatId: data.chatId,
chatType: chat.type
});
socket.emit('chat:joined', {
chatId: data.chatId,
messages: chat.messages.slice(-10) // Last 10 messages
});
}
catch (error) {
(0, Logger_1.logError)('Error joining chat', error);
socket.emit('error', { message: 'Failed to join chat' });
}
}
async handleLeaveChat(socket, data) {
try {
const userId = socket.userId;
// Leave the chat room
socket.leave(data.chatId);
// Remove from user's active chats in Redis
await this.redisService.removeUserFromChat(userId, data.chatId);
(0, Logger_1.logAuth)('User left chat', userId, {
chatId: data.chatId
});
socket.emit('chat:left', { chatId: data.chatId });
}
catch (error) {
(0, Logger_1.logError)('Error leaving chat', error);
socket.emit('error', { message: 'Failed to leave chat' });
}
}
async handleSendMessage(socket, data) {
try {
const userId = socket.userId;
// Rate limiting check
if (!this.checkMessageRateLimit(userId)) {
socket.emit('error', { message: `Rate limit exceeded. Maximum ${this.maxMessagesPerUser} messages per minute allowed.` });
return;
}
// Validate message is string and not empty
if (typeof data.message !== 'string' || !data.message.trim()) {
socket.emit('error', { message: 'Message must be a non-empty string' });
return;
}
const chat = await this.chatRepository.findById(data.chatId);
if (!chat) {
socket.emit('error', { message: 'Chat not found' });
return;
}
// Check if user is member of this chat
if (!chat.users.includes(userId)) {
socket.emit('error', { message: 'Unauthorized to send message to this chat' });
return;
}
// Create message
const message = {
id: (0, uuid_1.v4)(),
date: new Date(),
userid: userId,
text: data.message.trim()
};
// Manage message history based on chat type
let updatedMessages = [...chat.messages, message];
updatedMessages = this.pruneMessages(updatedMessages, chat.type);
// Update chat
await this.chatRepository.update(data.chatId, {
messages: updatedMessages,
lastActivity: new Date()
});
// Update chat activity in Redis with new message count
await this.redisService.updateChatActivity(data.chatId, updatedMessages.length);
// Broadcast to all users in the chat room
this.io.to(data.chatId).emit('message:received', {
chatId: data.chatId,
message: message
});
// Send notifications to offline users
await this.notifyOfflineUsers(chat, message);
(0, Logger_1.logAuth)('Message sent', userId, {
chatId: data.chatId,
messageLength: data.message.length,
chatType: chat.type
});
}
catch (error) {
(0, Logger_1.logError)('Error sending message', error);
socket.emit('error', { message: 'Failed to send message' });
}
}
async handleCreateGroup(socket, data) {
try {
const userId = socket.userId;
// Check if user is premium (required to create groups)
const user = await this.userRepository.findById(userId);
if (!user || user.state !== UserAggregate_1.UserState.VERIFIED_PREMIUM) {
socket.emit('error', { message: 'Premium subscription required to create groups' });
return;
}
// Validate group data
if (!data.name?.trim()) {
socket.emit('error', { message: 'Group name is required' });
return;
}
if (!data.userIds || data.userIds.length === 0) {
socket.emit('error', { message: 'At least one member is required' });
return;
}
// Verify all users exist
const members = await Promise.all(data.userIds.map(id => this.userRepository.findById(id)));
if (members.some(member => !member)) {
socket.emit('error', { message: 'One or more users not found' });
return;
}
// Create group chat
const groupChat = await this.chatRepository.create({
type: ChatAggregate_1.ChatType.GROUP,
name: data.name.trim(),
createdBy: userId,
users: [userId, ...data.userIds], // Include creator
messages: [],
lastActivity: new Date()
});
// Add all members to the group room and store in Redis
const allMemberIds = data.userIds.concat(userId);
for (const memberId of allMemberIds) {
const memberSocket = this.connectedUsers.get(memberId);
if (memberSocket) {
memberSocket.join(groupChat.id);
}
// Update user's chat list in Redis
await this.redisService.addUserToChat(memberId, groupChat.id);
}
// Store the group chat in Redis
await this.redisService.setActiveChat(groupChat.id, {
chatId: groupChat.id,
participants: allMemberIds,
lastActivity: new Date(),
messageCount: 0,
chatType: 'group',
name: groupChat.name || undefined
});
// Notify all members
this.io.to(groupChat.id).emit('group:created', {
chat: {
id: groupChat.id,
type: groupChat.type,
name: groupChat.name,
createdBy: groupChat.createdBy,
users: groupChat.users,
messages: []
}
});
(0, Logger_1.logAuth)('Group created', userId, {
groupId: groupChat.id,
groupName: data.name,
memberCount: groupChat.users.length
});
}
catch (error) {
(0, Logger_1.logError)('Error creating group', error);
socket.emit('error', { message: 'Failed to create group' });
}
}
async handleCreateDirectChat(socket, data) {
try {
const userId = socket.userId;
// Validate target user exists
const targetUser = await this.userRepository.findById(data.targetUserId);
if (!targetUser) {
socket.emit('error', { message: 'Target user not found' });
return;
}
// Check if direct chat already exists
const existingChats = await this.chatRepository.findByUserId(userId);
const existingDirectChat = existingChats.find(chat => chat.type === ChatAggregate_1.ChatType.DIRECT &&
chat.users.length === 2 &&
chat.users.includes(data.targetUserId));
if (existingDirectChat) {
socket.emit('chat:direct:exists', {
chatId: existingDirectChat.id
});
return;
}
// Create direct chat
const directChat = await this.chatRepository.create({
type: ChatAggregate_1.ChatType.DIRECT,
users: [userId, data.targetUserId],
messages: [],
lastActivity: new Date()
});
// Add both users to the chat room if they're online and store in Redis
const memberIds = [userId, data.targetUserId];
for (const memberId of memberIds) {
const memberSocket = this.connectedUsers.get(memberId);
if (memberSocket) {
memberSocket.join(directChat.id);
}
// Update user's chat list in Redis
await this.redisService.addUserToChat(memberId, directChat.id);
}
// Store the direct chat in Redis
await this.redisService.setActiveChat(directChat.id, {
chatId: directChat.id,
participants: memberIds,
lastActivity: new Date(),
messageCount: 0,
chatType: 'direct'
});
// Notify both users
this.io.to(directChat.id).emit('chat:direct:created', {
chat: {
id: directChat.id,
type: directChat.type,
users: directChat.users,
messages: []
}
});
(0, Logger_1.logAuth)('Direct chat created', userId, {
chatId: directChat.id,
targetUserId: data.targetUserId
});
}
catch (error) {
(0, Logger_1.logError)('Error creating direct chat', error);
socket.emit('error', { message: 'Failed to create direct chat' });
}
}
async handleCreateGameChat(socket, data) {
try {
const userId = socket.userId;
// Check if game chat already exists
const existingGameChat = await this.chatRepository.findByGameId(data.gameId);
if (existingGameChat) {
socket.emit('game:chat:exists', {
chatId: existingGameChat.id
});
return;
}
// Create game chat
const gameChat = await this.chatRepository.create({
type: ChatAggregate_1.ChatType.GAME,
name: data.gameName,
gameId: data.gameId,
users: data.playerIds,
messages: [],
lastActivity: new Date()
});
// Add all players to the game chat room if they're online and store in Redis
for (const playerId of data.playerIds) {
const playerSocket = this.connectedUsers.get(playerId);
if (playerSocket) {
playerSocket.join(gameChat.id);
}
// Update user's chat list in Redis
await this.redisService.addUserToChat(playerId, gameChat.id);
}
// Store the game chat in Redis
await this.redisService.setActiveChat(gameChat.id, {
chatId: gameChat.id,
participants: data.playerIds,
lastActivity: new Date(),
messageCount: 0,
chatType: 'game',
gameId: gameChat.gameId || undefined,
name: gameChat.name || undefined
});
// Notify all players
this.io.to(gameChat.id).emit('game:chat:created', {
chat: {
id: gameChat.id,
type: gameChat.type,
name: gameChat.name,
gameId: gameChat.gameId,
users: gameChat.users,
messages: []
}
});
(0, Logger_1.logAuth)('Game chat created', userId, {
chatId: gameChat.id,
gameId: data.gameId,
gameName: data.gameName,
playerCount: data.playerIds.length
});
}
catch (error) {
(0, Logger_1.logError)('Error creating game chat', error);
socket.emit('error', { message: 'Failed to create game chat' });
}
}
async handleGetChatHistory(socket, data) {
try {
const userId = socket.userId;
const chat = await this.chatRepository.findById(data.chatId);
if (!chat) {
// Check if it's archived
const archived = await this.chatRepository.getArchivedChat(data.chatId);
if (archived) {
socket.emit('chat:history:archived', {
chatId: data.chatId,
messages: archived.archivedMessages,
chatType: archived.chatType,
isGameChat: archived.chatType === ChatAggregate_1.ChatType.GAME
});
}
else {
socket.emit('error', { message: 'Chat not found' });
}
return;
}
// Check if user has access
if (!chat.users.includes(userId)) {
socket.emit('error', { message: 'Unauthorized to view this chat' });
return;
}
socket.emit('chat:history', {
chatId: data.chatId,
messages: chat.messages,
chatInfo: {
type: chat.type,
name: chat.name,
gameId: chat.gameId,
users: chat.users
}
});
}
catch (error) {
(0, Logger_1.logError)('Error getting chat history', error);
socket.emit('error', { message: 'Failed to get chat history' });
}
}
async handleDeleteChat(socket, data) {
try {
const userId = socket.userId;
const chat = await this.chatRepository.findById(data.chatId);
if (!chat) {
socket.emit('error', { message: 'Chat not found' });
return;
}
// Check if user is member of this chat
if (!chat.users.includes(userId)) {
socket.emit('error', { message: 'Unauthorized to delete this chat' });
return;
}
// Perform soft delete
const deletedChat = await this.chatRepository.softDelete(data.chatId);
if (!deletedChat) {
socket.emit('error', { message: 'Failed to delete chat' });
return;
}
// Remove from Redis active chats
await this.redisService.removeActiveChat(data.chatId);
// Notify all participants that the chat has been deleted
this.io.to(data.chatId).emit('chat:deleted', {
chatId: data.chatId,
deletedBy: userId
});
// Remove all users from the chat room
for (const participantId of chat.users) {
const participantSocket = this.connectedUsers.get(participantId);
if (participantSocket) {
participantSocket.leave(data.chatId);
}
// Remove from user's active chats in Redis
await this.redisService.removeUserFromChat(participantId, data.chatId);
}
(0, Logger_1.logAuth)('Chat deleted', userId, {
chatId: data.chatId,
chatType: chat.type,
participantCount: chat.users.length
});
socket.emit('chat:delete:success', {
chatId: data.chatId,
message: 'Chat deleted successfully'
});
}
catch (error) {
(0, Logger_1.logError)('Error deleting chat', error);
socket.emit('error', { message: 'Failed to delete chat' });
}
}
async handleDeleteChatArchive(socket, data) {
try {
const userId = socket.userId;
const archive = await this.chatArchiveRepository.findById(data.archiveId);
if (!archive) {
socket.emit('error', { message: 'Chat archive not found' });
return;
}
// Check if user was a participant in the archived chat
if (!archive.participants.includes(userId)) {
socket.emit('error', { message: 'Unauthorized to delete this chat archive' });
return;
}
// Hard delete the archive (since it's already archived)
await this.chatArchiveRepository.delete(data.archiveId);
(0, Logger_1.logAuth)('Chat archive deleted', userId, {
archiveId: data.archiveId,
originalChatId: archive.chatId,
chatType: archive.chatType,
participantCount: archive.participants.length
});
socket.emit('chat:archive:delete:success', {
archiveId: data.archiveId,
message: 'Chat archive deleted successfully'
});
}
catch (error) {
(0, Logger_1.logError)('Error deleting chat archive', error);
socket.emit('error', { message: 'Failed to delete chat archive' });
}
}
async handleDeleteMessage(socket, data) {
try {
const userId = socket.userId;
// Check if user has admin/moderator privileges
const user = await this.userRepository.findById(userId);
if (!user || user.state !== UserAggregate_1.UserState.ADMIN) { // Check if user is admin
socket.emit('error', { message: 'Insufficient permissions to delete messages' });
return;
}
const success = await this.deleteMessage(data.chatId, data.messageId, userId);
if (success) {
socket.emit('message:delete:success', {
chatId: data.chatId,
messageId: data.messageId,
message: 'Message deleted successfully'
});
}
else {
socket.emit('error', { message: 'Failed to delete message or message not found' });
}
}
catch (error) {
(0, Logger_1.logError)('Error handling delete message request', error);
socket.emit('error', { message: 'Failed to delete message' });
}
}
async handleDisconnection(socket) {
const userId = socket.userId;
if (userId) {
this.connectedUsers.delete(userId);
// Update user status in Redis
const userData = await this.redisService.getActiveUser(userId);
if (userData) {
userData.isOnline = false;
userData.lastActivity = new Date();
await this.redisService.setActiveUser(userId, userData);
}
(0, Logger_1.logAuth)('User disconnected from WebSocket', userId, {
socketId: socket.id
});
}
}
// Utility methods
calculateUnreadMessages(chat, userId) {
// Simple implementation - count messages after user's last seen
// In production, you'd store lastSeen timestamp per user per chat
return chat.messages.filter(msg => msg.userid !== userId).length;
}
pruneMessages(messages, chatType) {
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000);
// Remove messages older than 2 weeks
let prunedMessages = messages.filter(msg => new Date(msg.date) > twoWeeksAgo);
// For group chats, only apply the 2-week time limit (unlimited messages per user)
if (chatType === ChatAggregate_1.ChatType.GROUP) {
return prunedMessages.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
}
// For direct and game chats, apply both time limit and per-user message limit
// Group by user and keep last 10 messages per user
const messagesByUser = new Map();
prunedMessages.forEach(msg => {
if (!messagesByUser.has(msg.userid)) {
messagesByUser.set(msg.userid, []);
}
messagesByUser.get(msg.userid).push(msg);
});
// Keep only last 10 messages per user
const finalMessages = [];
messagesByUser.forEach((userMessages, userId) => {
const last10 = userMessages.slice(-10);
finalMessages.push(...last10);
});
// Sort by date
return finalMessages.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
}
async notifyOfflineUsers(chat, message) {
// Find users who are not currently connected
const offlineUsers = chat.users.filter(userId => userId !== message.userid && !this.connectedUsers.has(userId));
// In a real implementation, you would send push notifications or emails here
if (offlineUsers.length > 0) {
(0, Logger_1.logRequest)('Offline users to notify', undefined, undefined, {
chatId: chat.id,
offlineUserCount: offlineUsers.length,
messageFrom: message.userid
});
}
}
setupArchivingScheduler() {
// Run every hour to check for inactive chats
setInterval(async () => {
try {
// First, cleanup inactive chats from Redis and get their IDs
const inactiveChatIds = await this.redisService.cleanupInactiveChats(this.chatTimeout);
// Archive the inactive chats in the database
for (const chatId of inactiveChatIds) {
const chat = await this.chatRepository.findById(chatId);
if (chat) {
await this.chatRepository.archiveChat(chat);
(0, Logger_1.logRequest)('Chat archived due to inactivity', undefined, undefined, {
chatId: chat.id,
chatType: chat.type,
lastActivity: chat.lastActivity,
messageCount: chat.messages.length
});
}
}
// Also find inactive chats from database that might not be in Redis
const dbInactiveChats = await this.chatRepository.findInactiveChats(this.chatTimeout);
const additionalInactiveChats = dbInactiveChats.filter(chat => !inactiveChatIds.includes(chat.id));
for (const chat of additionalInactiveChats) {
await this.chatRepository.archiveChat(chat);
(0, Logger_1.logRequest)('Chat archived due to inactivity (from DB)', undefined, undefined, {
chatId: chat.id,
chatType: chat.type,
lastActivity: chat.lastActivity,
messageCount: chat.messages.length
});
}
const totalArchived = inactiveChatIds.length + additionalInactiveChats.length;
if (totalArchived > 0) {
(0, Logger_1.logRequest)('Chat archiving completed', undefined, undefined, {
archivedCount: totalArchived,
redisCleanedUp: inactiveChatIds.length,
databaseCleanedUp: additionalInactiveChats.length,
timeoutMinutes: this.chatTimeout
});
}
// Cleanup old messages from archived chats based on messageCleanupWeeks
await this.cleanupOldMessages();
}
catch (error) {
(0, Logger_1.logError)('Error in chat archiving scheduler', error);
}
}, 60 * 60 * 1000); // 1 hour
// Also run message count cleanup every 5 minutes
setInterval(() => {
this.cleanupMessageCounts();
}, 5 * 60 * 1000); // 5 minutes
}
// Public methods for game integration
async createGameChat(gameId, gameName, playerIds) {
try {
const existingGameChat = await this.chatRepository.findByGameId(gameId);
if (existingGameChat) {
return existingGameChat;
}
const gameChat = await this.chatRepository.create({
type: ChatAggregate_1.ChatType.GAME,
name: gameName,
gameId: gameId,
users: playerIds,
messages: [],
lastActivity: new Date()
});
// Notify connected players
playerIds.forEach(playerId => {
const playerSocket = this.connectedUsers.get(playerId);
if (playerSocket) {
playerSocket.join(gameChat.id);
playerSocket.emit('game:chat:created', {
chat: {
id: gameChat.id,
type: gameChat.type,
name: gameChat.name,
gameId: gameChat.gameId,
users: gameChat.users,
messages: []
}
});
}
});
return gameChat;
}
catch (error) {
(0, Logger_1.logError)('Error creating game chat programmatically', error);
return null;
}
}
getConnectedUserCount() {
return this.connectedUsers.size;
}
isUserConnected(userId) {
return this.connectedUsers.has(userId);
}
async cleanup() {
try {
await this.redisService.disconnect();
}
catch (error) {
(0, Logger_1.logError)('Error during WebSocket service cleanup', error);
}
}
/**
* Manually trigger cleanup of old messages and chats
* This can be called by admin endpoints for maintenance
*/
async triggerManualCleanup() {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - (this.messageCleanupWeeks * 7));
// Clean up old archived messages
const deletedArchivesCount = await this.chatArchiveRepository.cleanup(this.messageCleanupWeeks * 7);
// Clean up soft-deleted chats
const softDeletedChats = await this.chatRepository.findByPageIncludingDeleted(0, 1000);
let deletedChatsCount = 0;
for (const chat of softDeletedChats.chats) {
if (chat.state === 2 && chat.updateDate < cutoffDate) { // SOFT_DELETE state = 2
await this.chatRepository.delete(chat.id); // Hard delete
deletedChatsCount++;
}
}
(0, Logger_1.logRequest)('Manual cleanup triggered', undefined, undefined, {
cutoffDate: cutoffDate.toISOString(),
cleanupWeeks: this.messageCleanupWeeks,
deletedArchives: deletedArchivesCount,
deletedChats: deletedChatsCount,
triggeredBy: 'manual'
});
return { deletedArchives: deletedArchivesCount, deletedChats: deletedChatsCount };
}
catch (error) {
(0, Logger_1.logError)('Error during manual cleanup', error);
throw error;
}
}
/**
* Clean up old messages from archived chats based on messageCleanupWeeks setting
*/
async cleanupOldMessages() {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - (this.messageCleanupWeeks * 7));
// Clean up old archived messages using ChatArchiveRepository
const deletedArchivesCount = await this.chatArchiveRepository.cleanup(this.messageCleanupWeeks * 7);
// Also clean up soft-deleted chats from the main repository
// Get all soft-deleted chats that are older than the cleanup period
const softDeletedChats = await this.chatRepository.findByPageIncludingDeleted(0, 1000);
let deletedChatsCount = 0;
for (const chat of softDeletedChats.chats) {
if (chat.state === 2 && chat.updateDate < cutoffDate) { // SOFT_DELETE state = 2
await this.chatRepository.delete(chat.id); // Hard delete
deletedChatsCount++;
}
}
(0, Logger_1.logRequest)('Old message cleanup completed', undefined, undefined, {
cutoffDate: cutoffDate.toISOString(),
cleanupWeeks: this.messageCleanupWeeks,
deletedArchives: deletedArchivesCount,
deletedChats: deletedChatsCount,
note: 'Cleanup completed using both ChatRepository and ChatArchiveRepository'
});
}
catch (error) {
(0, Logger_1.logError)('Error cleaning up old messages', error);
}
}
/**
* Check if user has exceeded message rate limit
* @param userId User ID to check
* @returns true if within limit, false if exceeded
*/
checkMessageRateLimit(userId) {
const now = Date.now();
const minute = 60 * 1000; // 1 minute in milliseconds
const userStats = this.userMessageCounts.get(userId) || { count: 0, lastReset: now };
// Reset counter if more than a minute has passed
if (now - userStats.lastReset >= minute) {
userStats.count = 0;
userStats.lastReset = now;
}
// Check if user is within limits
if (userStats.count >= this.maxMessagesPerUser) {
return false;
}
// Increment counter
userStats.count++;
this.userMessageCounts.set(userId, userStats);
return true;
}
/**
* Delete a specific message from chat history
* This can be used for moderation purposes
*/
async deleteMessage(chatId, messageId, moderatorUserId) {
try {
// Get the chat
const chat = await this.chatRepository.findById(chatId);
if (!chat) {
// Check archived chats
const archivedChat = await this.chatRepository.getArchivedChat(chatId);
if (!archivedChat) {
(0, Logger_1.logWarning)('Chat not found for message deletion', {
chatId,
messageId,
moderatorUserId
});
return false;
}
// Remove message from archived chat
const updatedMessages = archivedChat.archivedMessages.filter(msg => msg.id !== messageId);
if (updatedMessages.length === archivedChat.archivedMessages.length) {
(0, Logger_1.logWarning)('Message not found in archived chat', {
chatId,
messageId,
moderatorUserId
});
return false;
}
// Update archived chat
await this.chatArchiveRepository.create({
...archivedChat,
archivedMessages: updatedMessages
});
(0, Logger_1.logAuth)('Message deleted from archived chat', moderatorUserId, {
chatId,
messageId,
originalMessageCount: archivedChat.archivedMessages.length,
newMessageCount: updatedMessages.length
});
return true;
}
// Remove message from active chat
const updatedMessages = chat.messages.filter(msg => msg.id !== messageId);
if (updatedMessages.length === chat.messages.length) {
(0, Logger_1.logWarning)('Message not found in active chat', {
chatId,
messageId,
moderatorUserId
});
return false;
}
// Update active chat
await this.chatRepository.update(chatId, {
messages: updatedMessages
});
// Notify all users in the chat about message deletion
this.io.to(chatId).emit('message:deleted', {
chatId,
messageId,
deletedBy: moderatorUserId
});
(0, Logger_1.logAuth)('Message deleted from active chat', moderatorUserId, {
chatId,
messageId,
originalMessageCount: chat.messages.length,
newMessageCount: updatedMessages.length
});
return true;
}
catch (error) {
(0, Logger_1.logError)('Error deleting message', error);
return false;
}
}
/**
* Clean up old user message count entries (called periodically)
*/
cleanupMessageCounts() {
const now = Date.now();
const minute = 60 * 1000;
for (const [userId, stats] of this.userMessageCounts.entries()) {
if (now - stats.lastReset >= minute * 5) { // Keep for 5 minutes
this.userMessageCounts.delete(userId);
}
}
}
}
exports.WebSocketService = WebSocketService;
//# sourceMappingURL=WebSocketService.js.map
File diff suppressed because one or more lines are too long