This repository has no description
Something went wrong. Try again.
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261// SPDX-License-Identifier: AGPL-3.0-or-later
import {RelationshipTypes} from '@fluxer/constants/src/UserConstants';import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';import type { AdminRelationshipEntry, ListUserRelationshipsRequest, ListUserRelationshipsResponse, RelationshipCategory, RemoveUserRelationshipRequest, RemoveUserRelationshipsByCategoryRequest, RemoveUserRelationshipsResponse,} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';import type {ApiContext} from '../../ApiContext';import {createUserID, type UserID} from '../../BrandedTypes';import type {Relationship} from '../../models/Relationship';import type {AdminAuditService} from './AdminAuditService';
interface AdminUserRelationshipServiceDeps { apiContext: ApiContext; auditService: AdminAuditService;}
const CATEGORY_TO_TYPE: Record<RelationshipCategory, number> = { friend: RelationshipTypes.FRIEND, incoming_request: RelationshipTypes.INCOMING_REQUEST, outgoing_request: RelationshipTypes.OUTGOING_REQUEST, blocked: RelationshipTypes.BLOCKED,};const TYPE_TO_CATEGORY: Record<number, RelationshipCategory> = { [RelationshipTypes.FRIEND]: 'friend', [RelationshipTypes.INCOMING_REQUEST]: 'incoming_request', [RelationshipTypes.OUTGOING_REQUEST]: 'outgoing_request', [RelationshipTypes.BLOCKED]: 'blocked',};
export class AdminUserRelationshipService { constructor(private readonly deps: AdminUserRelationshipServiceDeps) {}
async listRelationships(data: ListUserRelationshipsRequest): Promise<ListUserRelationshipsResponse> { const {users: userRepository} = this.deps.apiContext.services; const userId = createUserID(data.user_id); const user = await userRepository.findUnique(userId); if (!user) { throw new UnknownUserError(); } const relationships = await userRepository.listRelationships(userId); const targetUserIds = new Set<UserID>(); for (const rel of relationships) { targetUserIds.add(rel.targetUserId); } const resolvedUsers = await this.resolveUsers([...targetUserIds]); const friends: Array<AdminRelationshipEntry> = []; const incomingRequests: Array<AdminRelationshipEntry> = []; const outgoingRequests: Array<AdminRelationshipEntry> = []; const blocked: Array<AdminRelationshipEntry> = []; for (const rel of relationships) { const category = TYPE_TO_CATEGORY[rel.type]; if (!category) continue; const entry: AdminRelationshipEntry = { target_user_id: rel.targetUserId.toString(), category, nickname: rel.nickname, since: rel.since ? rel.since.toISOString() : null, target: resolvedUsers.get(rel.targetUserId) ?? null, }; switch (rel.type) { case RelationshipTypes.FRIEND: friends.push(entry); break; case RelationshipTypes.INCOMING_REQUEST: incomingRequests.push(entry); break; case RelationshipTypes.OUTGOING_REQUEST: outgoingRequests.push(entry); break; case RelationshipTypes.BLOCKED: blocked.push(entry); break; } } return { friends, incoming_requests: incomingRequests, outgoing_requests: outgoingRequests, blocked, }; }
async removeRelationship( data: RemoveUserRelationshipRequest, adminUserId: UserID, auditLogReason: string | null, ): Promise<void> { const {auditService} = this.deps; const {users: userRepository} = this.deps.apiContext.services; const userId = createUserID(data.user_id); const targetUserId = createUserID(data.target_user_id); const user = await userRepository.findUnique(userId); if (!user) { throw new UnknownUserError(); } const type = CATEGORY_TO_TYPE[data.category]; const relationship = await userRepository.getRelationship(userId, targetUserId, type); if (!relationship) { throw new UnknownUserError(); } await this.removePair({userId, targetUserId, category: data.category}); await auditService.createAuditLog({ adminUserId, targetType: 'user', targetId: BigInt(userId), action: 'remove_relationship', auditLogReason, metadata: new Map([ ['target_user_id', targetUserId.toString()], ['category', data.category], ]), }); }
async removeRelationshipsByCategory( data: RemoveUserRelationshipsByCategoryRequest, adminUserId: UserID, auditLogReason: string | null, ): Promise<RemoveUserRelationshipsResponse> { const {auditService} = this.deps; const {users: userRepository} = this.deps.apiContext.services; const userId = createUserID(data.user_id); const user = await userRepository.findUnique(userId); if (!user) { throw new UnknownUserError(); } const type = CATEGORY_TO_TYPE[data.category]; const allRelationships = await userRepository.listRelationships(userId); const matching: Array<Relationship> = allRelationships.filter((rel) => rel.type === type); for (const rel of matching) { await this.removePair({userId, targetUserId: rel.targetUserId, category: data.category}); } await auditService.createAuditLog({ adminUserId, targetType: 'user', targetId: BigInt(userId), action: 'remove_relationships_by_category', auditLogReason, metadata: new Map([ ['category', data.category], ['removed_count', matching.length.toString()], ]), }); return {removed_count: matching.length}; }
private async removePair({ userId, targetUserId, category, }: { userId: UserID; targetUserId: UserID; category: RelationshipCategory; }): Promise<void> { const {users: userRepository, gateway: gatewayService} = this.deps.apiContext.services; switch (category) { case 'friend': { await userRepository.deleteRelationship(userId, targetUserId, RelationshipTypes.FRIEND); await userRepository.deleteRelationship(targetUserId, userId, RelationshipTypes.FRIEND); await gatewayService.dispatchPresence({ userId, event: 'RELATIONSHIP_REMOVE', data: {id: targetUserId.toString()}, }); await gatewayService.dispatchPresence({ userId: targetUserId, event: 'RELATIONSHIP_REMOVE', data: {id: userId.toString()}, }); return; } case 'outgoing_request': { await userRepository.deleteRelationship(userId, targetUserId, RelationshipTypes.OUTGOING_REQUEST); await userRepository.deleteRelationship(targetUserId, userId, RelationshipTypes.INCOMING_REQUEST); await gatewayService.dispatchPresence({ userId, event: 'RELATIONSHIP_REMOVE', data: {id: targetUserId.toString()}, }); await gatewayService.dispatchPresence({ userId: targetUserId, event: 'RELATIONSHIP_REMOVE', data: {id: userId.toString()}, }); return; } case 'incoming_request': { await userRepository.deleteRelationship(userId, targetUserId, RelationshipTypes.INCOMING_REQUEST); await userRepository.deleteRelationship(targetUserId, userId, RelationshipTypes.OUTGOING_REQUEST); await gatewayService.dispatchPresence({ userId, event: 'RELATIONSHIP_REMOVE', data: {id: targetUserId.toString()}, }); await gatewayService.dispatchPresence({ userId: targetUserId, event: 'RELATIONSHIP_REMOVE', data: {id: userId.toString()}, }); return; } case 'blocked': { await userRepository.deleteRelationship(userId, targetUserId, RelationshipTypes.BLOCKED); await gatewayService.dispatchPresence({ userId, event: 'RELATIONSHIP_REMOVE', data: {id: targetUserId.toString()}, }); return; } } }
private async resolveUsers(userIds: Array<UserID>): Promise< Map< UserID, { id: string; username: string; discriminator: string; global_name: string | null; avatar: string | null; } > > { const {users: userRepository} = this.deps.apiContext.services; const results = new Map< UserID, { id: string; username: string; discriminator: string; global_name: string | null; avatar: string | null; } >(); const users = await Promise.all(userIds.map((id) => userRepository.findUnique(id))); for (let i = 0; i < userIds.length; i++) { const user = users[i]; if (user) { results.set(userIds[i], { id: user.id.toString(), username: user.username, discriminator: String(user.discriminator).padStart(4, '0'), global_name: user.globalName, avatar: user.avatarHash, }); } } return results; }}