import { Injectable } from '@nestjs/common'; import { DirectMessage, FriendRequest, FriendRequestStatus, Friendship, PlayerTravelUnlock, SocialNotification, UserBlock, UserReport, } from './social.entities'; import { SocialStore } from './social.store'; @Injectable() export class SocialMemoryStore implements SocialStore { private nextId = BigInt(1); private friendships: Friendship[] = []; private requests: FriendRequest[] = []; private blocks: UserBlock[] = []; private messages: DirectMessage[] = []; private reports: UserReport[] = []; private unlocks: PlayerTravelUnlock[] = []; private notifications: SocialNotification[] = []; private id(): bigint { return this.nextId++; } async listFriendships(userId: bigint): Promise { return this.friendships.filter((item) => item.user_low_id === userId || item.user_high_id === userId); } async findFriendship(userLowId: bigint, userHighId: bigint): Promise { return this.friendships.find((item) => item.user_low_id === userLowId && item.user_high_id === userHighId) || null; } async createFriendship(userLowId: bigint, userHighId: bigint): Promise { const found = await this.findFriendship(userLowId, userHighId); if (found) return found; const record = Object.assign(new Friendship(), { id: this.id(), user_low_id: userLowId, user_high_id: userHighId, created_at: new Date() }); this.friendships.push(record); return record; } async deleteFriendship(userLowId: bigint, userHighId: bigint): Promise { this.friendships = this.friendships.filter((item) => item.user_low_id !== userLowId || item.user_high_id !== userHighId); } async findPendingFriendRequest(requesterId: bigint, recipientId: bigint): Promise { return this.requests.find((item) => item.requester_id === requesterId && item.recipient_id === recipientId && item.status === FriendRequestStatus.PENDING && item.expires_at > new Date()) || null; } async createFriendRequest(requesterId: bigint, recipientId: bigint, expiresAt: Date): Promise { const record = Object.assign(new FriendRequest(), { id: this.id(), requester_id: requesterId, recipient_id: recipientId, status: FriendRequestStatus.PENDING, created_at: new Date(), expires_at: expiresAt, responded_at: null }); this.requests.push(record); return record; } async findFriendRequest(id: bigint): Promise { return this.requests.find((item) => item.id === id) || null; } async saveFriendRequest(request: FriendRequest): Promise { return request; } async cancelPendingRequestsBetween(userA: bigint, userB: bigint): Promise { for (const request of this.requests) if (request.status === FriendRequestStatus.PENDING && ((request.requester_id === userA && request.recipient_id === userB) || (request.requester_id === userB && request.recipient_id === userA))) { request.status = FriendRequestStatus.CANCELLED; request.responded_at = new Date(); } } async listFriendRequests(userId: bigint): Promise { return this.requests.filter((item) => item.recipient_id === userId && item.status === FriendRequestStatus.PENDING && item.expires_at > new Date()).sort((a, b) => b.created_at.getTime() - a.created_at.getTime()); } async createBlock(userId: bigint, blockedUserId: bigint): Promise { const found = this.blocks.find((item) => item.user_id === userId && item.blocked_user_id === blockedUserId); if (found) return found; const record = Object.assign(new UserBlock(), { id: this.id(), user_id: userId, blocked_user_id: blockedUserId, created_at: new Date() }); this.blocks.push(record); return record; } async deleteBlock(userId: bigint, blockedUserId: bigint): Promise { this.blocks = this.blocks.filter((item) => item.user_id !== userId || item.blocked_user_id !== blockedUserId); } async isBlocked(userId: bigint, blockedUserId: bigint): Promise { return this.blocks.some((item) => item.user_id === userId && item.blocked_user_id === blockedUserId); } async listBlocks(userId: bigint): Promise { return this.blocks.filter((item) => item.user_id === userId); } async createDirectMessage(input: Pick): Promise { const record = Object.assign(new DirectMessage(), { id: this.id(), ...input, created_at: new Date(), read_at: null }); this.messages.push(record); return record; } async listDirectMessages(userA: bigint, userB: bigint, limit: number, before?: Date): Promise { return this.messages.filter((item) => ((item.sender_id === userA && item.recipient_id === userB) || (item.sender_id === userB && item.recipient_id === userA)) && item.expires_at > new Date() && (!before || item.created_at < before)).sort((a, b) => b.created_at.getTime() - a.created_at.getTime()).slice(0, limit); } async markDirectMessagesRead(readerId: bigint, otherUserId: bigint): Promise { let affected = 0; for (const item of this.messages) if (item.sender_id === otherUserId && item.recipient_id === readerId && !item.read_at) { item.read_at = new Date(); affected++; } return affected; } async listConversations(userId: bigint): Promise { const latest = new Map(); for (const item of this.messages) { if (item.expires_at <= new Date() || (item.sender_id !== userId && item.recipient_id !== userId)) continue; const other = item.sender_id === userId ? item.recipient_id : item.sender_id; const old = latest.get(other.toString()); if (!old || old.created_at < item.created_at) latest.set(other.toString(), item); } return [...latest.values()].sort((a, b) => b.created_at.getTime() - a.created_at.getTime()); } async countUnreadDirectMessages(userId: bigint): Promise { return this.messages.filter((item) => item.recipient_id === userId && !item.read_at && item.expires_at > new Date()).length; } async createReport(input: Pick): Promise { const record = Object.assign(new UserReport(), { id: this.id(), ...input, status: 'received', created_at: new Date() }); this.reports.push(record); return record; } async createUnlock(userId: bigint, destinationId: string): Promise { const found = this.unlocks.find((item) => item.user_id === userId && item.destination_id === destinationId); if (found) return found; const record = Object.assign(new PlayerTravelUnlock(), { id: this.id(), user_id: userId, destination_id: destinationId, discovered_at: new Date() }); this.unlocks.push(record); return record; } async listUnlocks(userId: bigint): Promise { return this.unlocks.filter((item) => item.user_id === userId); } async createNotification(input: Pick): Promise { const now = new Date(); const record = Object.assign(new SocialNotification(), { id: this.id(), ...input, created_at: now, updated_at: now, read_at: null }); this.notifications.push(record); return record; } async listNotifications(userId: bigint, limit: number, before?: Date): Promise { return this.notifications.filter((item) => item.user_id === userId && item.expires_at > new Date() && (!before || item.created_at < before)).sort((a, b) => b.created_at.getTime() - a.created_at.getTime()).slice(0, limit); } async markNotificationRead(userId: bigint, id: bigint): Promise { const item = this.notifications.find((entry) => entry.user_id === userId && entry.id === id); if (item) { item.read_at = new Date(); item.updated_at = new Date(); } return item || null; } async markAllNotificationsRead(userId: bigint): Promise { let affected = 0; for (const item of this.notifications) if (item.user_id === userId && !item.read_at) { item.read_at = new Date(); item.updated_at = new Date(); affected++; } return affected; } async countUnreadNotifications(userId: bigint): Promise { return this.notifications.filter((item) => item.user_id === userId && !item.read_at && item.expires_at > new Date()).length; } async purgeUsers(userIds: bigint[]): Promise { const ids = new Set(userIds.map((id) => id.toString())); const has = (id: bigint) => ids.has(id.toString()); this.friendships = this.friendships.filter((item) => !has(item.user_low_id) && !has(item.user_high_id)); this.requests = this.requests.filter((item) => !has(item.requester_id) && !has(item.recipient_id)); this.blocks = this.blocks.filter((item) => !has(item.user_id) && !has(item.blocked_user_id)); this.messages = this.messages.filter((item) => !has(item.sender_id) && !has(item.recipient_id)); this.reports = this.reports.filter((item) => !has(item.reporter_id) && !has(item.reported_user_id)); this.unlocks = this.unlocks.filter((item) => !has(item.user_id)); this.notifications = this.notifications.filter((item) => !has(item.user_id) && !(item.action_metadata as Record | null)?.testLab); } async cleanupExpired(now: Date): Promise { this.requests = this.requests.filter((item) => item.status !== FriendRequestStatus.PENDING || item.expires_at > now); this.messages = this.messages.filter((item) => item.expires_at > now); this.notifications = this.notifications.filter((item) => item.expires_at > now); } }