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,141 @@
import { GetChatHistoryQuery, GetArchivedChatsQuery } from './ChatQueries';
import { IChatRepository } from '../../../Domain/IRepository/IChatRepository';
import { IChatArchiveRepository } from '../../../Domain/IRepository/IChatArchiveRepository';
import { Message } from '../../../Domain/Chat/ChatAggregate';
import { logAuth, logError, logWarning } from '../../Services/Logger';
interface ChatHistoryResult {
chatId: string;
messages: Message[];
isArchived: boolean;
chatInfo: {
type: string;
name: string | null;
gameId: string | null;
users: string[];
};
}
export class GetChatHistoryQueryHandler {
constructor(
private chatRepository: IChatRepository,
private chatArchiveRepository: IChatArchiveRepository
) {}
async execute(query: GetChatHistoryQuery): Promise<ChatHistoryResult | null> {
try {
// First try to find active chat
const chat = await this.chatRepository.findById(query.chatId);
if (chat) {
// Check authorization
if (!chat.users.includes(query.userId)) {
logWarning('Unauthorized chat history access attempt', {
chatId: query.chatId,
userId: query.userId
});
return null;
}
logAuth('Chat history retrieved', query.userId, {
chatId: query.chatId,
messageCount: chat.messages.length,
isArchived: false
});
return {
chatId: query.chatId,
messages: chat.messages,
isArchived: false,
chatInfo: {
type: chat.type,
name: chat.name,
gameId: chat.gameId,
users: chat.users
}
};
}
// Try to find in archives
const archives = await this.chatArchiveRepository.findByChatId(query.chatId);
const userArchive = archives.find(archive =>
archive.participants.includes(query.userId)
);
if (userArchive) {
logAuth('Archived chat history retrieved', query.userId, {
chatId: query.chatId,
messageCount: userArchive.archivedMessages.length,
isArchived: true
});
return {
chatId: query.chatId,
messages: userArchive.archivedMessages,
isArchived: true,
chatInfo: {
type: userArchive.chatType,
name: userArchive.chatName,
gameId: userArchive.gameId,
users: userArchive.participants
}
};
}
logWarning('Chat history not found', {
chatId: query.chatId,
userId: query.userId
});
return null;
} catch (error) {
logError('GetChatHistoryQueryHandler error', error as Error);
return null;
}
}
}
export class GetArchivedChatsQueryHandler {
constructor(private chatArchiveRepository: IChatArchiveRepository) {}
async execute(query: GetArchivedChatsQuery): Promise<ChatHistoryResult[]> {
try {
let archives: any[] = [];
if (query.gameId) {
// Get archived game chats
archives = await this.chatArchiveRepository.findByGameId(query.gameId);
} else {
// Get all archived chats for user (would need different query)
// For now, return empty - this would need a new repository method
archives = [];
}
const result = archives
.filter(archive => archive.participants.includes(query.userId))
.map(archive => ({
chatId: archive.chatId,
messages: archive.archivedMessages,
isArchived: true,
chatInfo: {
type: archive.chatType,
name: archive.chatName,
gameId: archive.gameId,
users: archive.participants
}
}));
logAuth('Archived chats retrieved', query.userId, {
count: result.length,
gameId: query.gameId
});
return result;
} catch (error) {
logError('GetArchivedChatsQueryHandler error', error as Error);
return [];
}
}
}
@@ -0,0 +1,14 @@
export interface GetUserChatsQuery {
userId: string;
includeArchived?: boolean;
}
export interface GetChatHistoryQuery {
chatId: string;
userId: string; // For authorization
}
export interface GetArchivedChatsQuery {
userId: string;
gameId?: string;
}
@@ -0,0 +1,5 @@
export interface GetChatsByPageQuery {
from: number;
to: number;
includeDeleted?: boolean;
}
@@ -0,0 +1,55 @@
import { IChatRepository } from '../../../Domain/IRepository/IChatRepository';
import { GetChatsByPageQuery } from './GetChatsByPageQuery';
import { ShortChatDto } from '../../DTOs/ChatDto';
import { ChatMapper } from '../../DTOs/Mappers/ChatMapper';
import { logRequest, logError } from '../../Services/Logger';
export class GetChatsByPageQueryHandler {
constructor(private readonly chatRepo: IChatRepository) {}
async execute(query: GetChatsByPageQuery): Promise<{ chats: ShortChatDto[], totalCount: number }> {
try {
// Validate pagination parameters
if (query.from < 0 || query.to < query.from) {
throw new Error('Invalid pagination parameters');
}
const limit = query.to - query.from + 1;
if (limit > 100) {
throw new Error('Page size too large. Maximum 100 records per request');
}
logRequest('Get chats by page query started', undefined, undefined, {
from: query.from,
to: query.to,
includeDeleted: query.includeDeleted || false
});
const result = query.includeDeleted
? await this.chatRepo.findByPageIncludingDeleted(query.from, query.to)
: await this.chatRepo.findByPage(query.from, query.to);
logRequest('Get chats by page query completed', undefined, undefined, {
from: query.from,
to: query.to,
returned: result.chats.length,
totalCount: result.totalCount,
includeDeleted: query.includeDeleted || false
});
return {
chats: ChatMapper.toShortDtoList(result.chats),
totalCount: result.totalCount
};
} catch (error) {
logError('GetChatsByPageQueryHandler error', error instanceof Error ? error : new Error(String(error)));
// Re-throw validation errors as-is
if (error instanceof Error && (error.message.includes('Invalid pagination') || error.message.includes('Page size'))) {
throw error;
}
throw new Error('Failed to retrieve chats page');
}
}
}
@@ -0,0 +1,97 @@
import { GetUserChatsQuery } from './ChatQueries';
import { IChatRepository } from '../../../Domain/IRepository/IChatRepository';
import { IChatArchiveRepository } from '../../../Domain/IRepository/IChatArchiveRepository';
import { ChatAggregate } from '../../../Domain/Chat/ChatAggregate';
import { ChatArchiveAggregate } from '../../../Domain/Chat/ChatArchiveAggregate';
import { logAuth, logError } from '../../Services/Logger';
interface ChatWithMetadata {
id: string;
type: string;
name: string | null;
gameId: string | null;
users: string[];
lastActivity: Date | null;
isArchived: boolean;
messageCount: number;
unreadCount?: number;
}
export class GetUserChatsQueryHandler {
constructor(
private chatRepository: IChatRepository,
private chatArchiveRepository: IChatArchiveRepository
) {}
async execute(query: GetUserChatsQuery): Promise<ChatWithMetadata[]> {
try {
const result: ChatWithMetadata[] = [];
// Get active chats
const activeChats = await this.chatRepository.findActiveChatsForUser(query.userId);
result.push(...activeChats.map(chat => ({
id: chat.id,
type: chat.type,
name: chat.name,
gameId: chat.gameId,
users: chat.users,
lastActivity: chat.lastActivity,
isArchived: false,
messageCount: chat.messages.length,
unreadCount: this.calculateUnreadMessages(chat, query.userId)
})));
// Get archived chats if requested
if (query.includeArchived) {
const userActiveChats = await this.chatRepository.findByUserId(query.userId);
const archivedChatIds = userActiveChats
.filter(chat => chat.archiveDate !== null)
.map(chat => chat.id);
const archives = await Promise.all(
archivedChatIds.map(id => this.chatArchiveRepository.findByChatId(id))
);
archives.forEach(archiveArray => {
archiveArray.forEach(archive => {
if (archive.participants.includes(query.userId)) {
result.push({
id: archive.chatId,
type: archive.chatType,
name: archive.chatName,
gameId: archive.gameId,
users: archive.participants,
lastActivity: archive.archivedAt,
isArchived: true,
messageCount: archive.archivedMessages.length,
unreadCount: 0 // Archived chats have no unread messages
});
}
});
});
}
logAuth('User chats retrieved', query.userId, {
activeCount: activeChats.length,
totalCount: result.length,
includeArchived: query.includeArchived
});
return result.sort((a, b) => {
if (!a.lastActivity) return 1;
if (!b.lastActivity) return -1;
return new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime();
});
} catch (error) {
logError('GetUserChatsQueryHandler error', error as Error);
return [];
}
}
private calculateUnreadMessages(chat: ChatAggregate, userId: string): number {
// Simple implementation - count messages from other users
// In production, you'd store lastSeen timestamp per user per chat
return chat.messages.filter(msg => msg.userid !== userId).length;
}
}