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,69 @@
import { ArchiveChatCommand, RestoreChatCommand } from './ChatCommands';
import { IChatRepository } from '../../../Domain/IRepository/IChatRepository';
import { ChatType } from '../../../Domain/Chat/ChatAggregate';
import { logAuth, logError, logWarning } from '../../Services/Logger';
export class ArchiveChatCommandHandler {
constructor(private chatRepository: IChatRepository) {}
async execute(command: ArchiveChatCommand): Promise<boolean> {
try {
const chat = await this.chatRepository.findById(command.chatId);
if (!chat) {
throw new Error('Chat not found');
}
await this.chatRepository.archiveChat(chat);
logAuth('Chat archived manually', undefined, {
chatId: command.chatId,
chatType: chat.type,
messageCount: chat.messages.length
});
return true;
} catch (error) {
logError('ArchiveChatCommandHandler error', error as Error);
return false;
}
}
}
export class RestoreChatCommandHandler {
constructor(private chatRepository: IChatRepository) {}
async execute(command: RestoreChatCommand): Promise<boolean> {
try {
const archive = await this.chatRepository.getArchivedChat(command.chatId);
if (!archive) {
throw new Error('Archived chat not found');
}
// Game chats cannot be restored, only viewed
if (archive.chatType === ChatType.GAME) {
logWarning('Attempt to restore game chat blocked', {
chatId: command.chatId,
chatType: archive.chatType
});
return false;
}
const restoredChat = await this.chatRepository.restoreFromArchive(command.chatId);
if (!restoredChat) {
throw new Error('Failed to restore chat from archive');
}
logAuth('Chat restored from archive', undefined, {
chatId: command.chatId,
messageCount: archive.archivedMessages.length
});
return true;
} catch (error) {
logError('RestoreChatCommandHandler error', error as Error);
return false;
}
}
}
@@ -0,0 +1,21 @@
export interface CreateChatCommand {
type: 'direct' | 'group' | 'game';
name?: string;
gameId?: string;
createdBy: string;
userIds: string[];
}
export interface SendMessageCommand {
chatId: string;
userId: string;
message: string;
}
export interface ArchiveChatCommand {
chatId: string;
}
export interface RestoreChatCommand {
chatId: string;
}
@@ -0,0 +1,85 @@
import { CreateChatCommand } from './ChatCommands';
import { IChatRepository } from '../../../Domain/IRepository/IChatRepository';
import { IUserRepository } from '../../../Domain/IRepository/IUserRepository';
import { ChatType, ChatAggregate } from '../../../Domain/Chat/ChatAggregate';
import { UserState } from '../../../Domain/User/UserAggregate';
import { logAuth, logError } from '../../Services/Logger';
export class CreateChatCommandHandler {
constructor(
private chatRepository: IChatRepository,
private userRepository: IUserRepository
) {}
async execute(command: CreateChatCommand): Promise<ChatAggregate | null> {
try {
// Validate creator exists
const creator = await this.userRepository.findById(command.createdBy);
if (!creator) {
throw new Error('Creator not found');
}
// For group chats, check if creator is premium
if (command.type === 'group' && creator.state !== UserState.VERIFIED_PREMIUM) {
throw new Error('Premium subscription required to create groups');
}
// Validate all target users exist
const targetUsers = await Promise.all(
command.userIds.map(id => this.userRepository.findById(id))
);
if (targetUsers.some(user => !user)) {
throw new Error('One or more target users not found');
}
// For direct chats, check if already exists
if (command.type === 'direct' && command.userIds.length === 1) {
const existingChats = await this.chatRepository.findByUserId(command.createdBy);
const existingDirectChat = existingChats.find(chat =>
chat.type === ChatType.DIRECT &&
chat.users.length === 2 &&
chat.users.includes(command.userIds[0])
);
if (existingDirectChat) {
return existingDirectChat;
}
}
// For game chats, check if already exists
if (command.type === 'game' && command.gameId) {
const existingGameChat = await this.chatRepository.findByGameId(command.gameId);
if (existingGameChat) {
return existingGameChat;
}
}
// Create chat
const chatData: Partial<ChatAggregate> = {
type: command.type as any,
name: command.name,
gameId: command.gameId,
createdBy: command.createdBy,
users: [command.createdBy, ...command.userIds],
messages: [],
lastActivity: new Date()
};
const chat = await this.chatRepository.create(chatData);
logAuth('Chat created successfully', command.createdBy, {
chatId: chat.id,
chatType: command.type,
participantCount: chat.users.length,
gameId: command.gameId
});
return chat;
} catch (error) {
logError('CreateChatCommandHandler error', error as Error);
return null;
}
}
}
@@ -0,0 +1,84 @@
import { SendMessageCommand } from './ChatCommands';
import { IChatRepository } from '../../../Domain/IRepository/IChatRepository';
import { Message } from '../../../Domain/Chat/ChatAggregate';
import { logAuth, logError } from '../../Services/Logger';
import { v4 as uuidv4 } from 'uuid';
export class SendMessageCommandHandler {
constructor(private chatRepository: IChatRepository) {}
async execute(command: SendMessageCommand): Promise<Message | null> {
try {
// Validate message is non-empty string
if (typeof command.message !== 'string' || !command.message.trim()) {
throw new Error('Message must be a non-empty string');
}
const chat = await this.chatRepository.findById(command.chatId);
if (!chat) {
throw new Error('Chat not found');
}
// Check if user is member of this chat
if (!chat.users.includes(command.userId)) {
throw new Error('User is not a member of this chat');
}
// Create message
const message: Message = {
id: uuidv4(),
date: new Date(),
userid: command.userId,
text: command.message.trim()
};
// Manage message history (keep last 10 per user, up to 2 weeks)
let updatedMessages = [...chat.messages, message];
updatedMessages = this.pruneMessages(updatedMessages);
// Update chat
await this.chatRepository.update(command.chatId, {
messages: updatedMessages,
lastActivity: new Date()
});
logAuth('Message sent successfully', command.userId, {
chatId: command.chatId,
messageLength: command.message.length,
totalMessages: updatedMessages.length
});
return message;
} catch (error) {
logError('SendMessageCommandHandler error', error as Error);
return null;
}
}
private pruneMessages(messages: Message[]): Message[] {
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);
// Group by user and keep last 10 messages per user
const messagesByUser = new Map<string, Message[]>();
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: Message[] = [];
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());
}
}