feat: add nearby social services

This commit is contained in:
ANG-Server
2026-07-21 22:19:58 +08:00
parent 77302354ac
commit b5365751bb
17 changed files with 1266 additions and 121 deletions

View File

@@ -0,0 +1,78 @@
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<Friendship[]> {
return this.friendships.filter((item) => item.user_low_id === userId || item.user_high_id === userId);
}
async findFriendship(userLowId: bigint, userHighId: bigint): Promise<Friendship | null> {
return this.friendships.find((item) => item.user_low_id === userLowId && item.user_high_id === userHighId) || null;
}
async createFriendship(userLowId: bigint, userHighId: bigint): Promise<Friendship> {
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<void> {
this.friendships = this.friendships.filter((item) => item.user_low_id !== userLowId || item.user_high_id !== userHighId);
}
async findPendingFriendRequest(requesterId: bigint, recipientId: bigint): Promise<FriendRequest | null> {
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<FriendRequest> {
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<FriendRequest | null> { return this.requests.find((item) => item.id === id) || null; }
async saveFriendRequest(request: FriendRequest): Promise<FriendRequest> { return request; }
async cancelPendingRequestsBetween(userA: bigint, userB: bigint): Promise<void> {
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<FriendRequest[]> { 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<UserBlock> {
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<void> { this.blocks = this.blocks.filter((item) => item.user_id !== userId || item.blocked_user_id !== blockedUserId); }
async isBlocked(userId: bigint, blockedUserId: bigint): Promise<boolean> { return this.blocks.some((item) => item.user_id === userId && item.blocked_user_id === blockedUserId); }
async listBlocks(userId: bigint): Promise<UserBlock[]> { return this.blocks.filter((item) => item.user_id === userId); }
async createDirectMessage(input: Pick<DirectMessage, 'sender_id' | 'recipient_id' | 'content' | 'expires_at'>): Promise<DirectMessage> {
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<DirectMessage[]> { 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<number> { 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<DirectMessage[]> { const latest = new Map<string, DirectMessage>(); 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<number> { return this.messages.filter((item) => item.recipient_id === userId && !item.read_at && item.expires_at > new Date()).length; }
async createReport(input: Pick<UserReport, 'reporter_id' | 'reported_user_id' | 'reason' | 'note' | 'message_id'>): Promise<UserReport> { 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<PlayerTravelUnlock> { 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<PlayerTravelUnlock[]> { return this.unlocks.filter((item) => item.user_id === userId); }
async createNotification(input: Pick<SocialNotification, 'user_id' | 'category' | 'title' | 'content' | 'action_metadata' | 'expires_at'>): Promise<SocialNotification> { 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<SocialNotification[]> { 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<SocialNotification | null> { 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<number> { 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<number> { return this.notifications.filter((item) => item.user_id === userId && !item.read_at && item.expires_at > new Date()).length; }
async cleanupExpired(now: Date): Promise<void> { 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); }
}