refactor(api): collapse single-impl interfaces and facades (#3382)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-10 23:04:42 +02:00
1 parent e7b13fd19f
commit c16d414d93
426 files changed
+2085 -6445

No files matched your search

@@ -8,7 +8,7 @@ import {resolveLimitSafe} from '@app/api/limits/LimitConfigUtils';
import {createLimitMatchContext} from '@app/api/limits/LimitMatchContextBuilder';
import type {User} from '@app/api/models/User';
import {isPremiumTieringActive} from '@app/api/stripe/BillingConfigCache';
import type {IUserRepository} from '@app/api/user/IUserRepository';
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {NON_SELF_HOSTED_RESERVED_DISCRIMINATORS} from '@fluxer/constants/src/DiscriminatorConstants';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
@@ -59,7 +59,7 @@ export class DiscriminatorService implements IDiscriminatorService {
private static readonly DISCRIM_CACHE_TTL_S = seconds('30 seconds');
constructor(
private userRepository: IUserRepository,
private userRepository: UserRepository,
private cacheService: ICacheService,
private limitConfigService: LimitConfigService,
) {}
@@ -7,7 +7,7 @@ import {
createGuildID,
createUserID,
} from '@app/api/BrandedTypes';
import {ChannelRepository} from '@app/api/channel/repositories/ChannelRepository';
import {ChannelRepository} from '@app/api/channel/ChannelRepository';
import {setCassandraQueryExecutorForTesting, upsertOne} from '@app/api/database/CassandraQueryExecution';
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
import {
@@ -1,43 +1,80 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import crypto from 'node:crypto';
import {createReadStream} from 'node:fs';
import path from 'node:path';
import {Config} from '@app/api/Config';
import {Logger} from '@app/api/Logger';
import type {ICacheService} from '@pkgs/cache/src/ICacheService';
import {VirusHashCache} from '@pkgs/virus_scan/src/cache/VirusHashCache';
import type {IVirusScanService} from '@pkgs/virus_scan/src/IVirusScanService';
import {ClamAVProvider} from '@pkgs/virus_scan/src/providers/ClamAVProvider';
import type {VirusScanProviderResult} from '@pkgs/virus_scan/src/VirusScanProviderResult';
import type {VirusScanResult} from '@pkgs/virus_scan/src/VirusScanResult';
import {VirusScanService as SharedVirusScanService} from '@pkgs/virus_scan/src/VirusScanService';
import {seconds} from 'itty-time';
function describeError(error: unknown): string {
if (typeof error === 'string') {
return error;
}
if (error instanceof Error) {
return error.message;
}
return 'Unknown error';
}
export class VirusScanService implements IVirusScanService {
readonly enabled = true;
private readonly service: SharedVirusScanService;
private readonly provider = new ClamAVProvider({host: Config.clamav.host, port: Config.clamav.port});
private readonly failOpen = Config.clamav.failOpen;
constructor(cacheService: ICacheService) {
const provider = new ClamAVProvider({
host: Config.clamav.host,
port: Config.clamav.port,
});
const virusHashCache = new VirusHashCache(cacheService);
this.service = new SharedVirusScanService({
provider,
virusHashCache,
logger: Logger,
config: {
failOpen: Config.clamav.failOpen,
},
});
}
constructor(private cacheService: ICacheService) {}
async initialize(): Promise<void> {
await this.service.initialize();
}
async initialize(): Promise<void> {}
async scanFile(filePath: string): Promise<VirusScanResult> {
return this.service.scanFile(filePath);
const hash = crypto.createHash('sha256');
const stream = createReadStream(filePath, {highWaterMark: 1024 * 1024});
try {
for await (const chunk of stream) {
hash.update(chunk as Buffer);
}
} catch (error) {
stream.destroy();
throw error;
}
return this.scan(hash.digest('hex'), path.basename(filePath), () => this.provider.scanFile(filePath));
}
async scanBuffer(buffer: Buffer, filename: string): Promise<VirusScanResult> {
return this.service.scanBuffer(buffer, filename);
const fileHash = crypto.createHash('sha256').update(buffer).digest('hex');
return this.scan(fileHash, filename, () => this.provider.scanBuffer(buffer));
}
private async scan(
fileHash: string,
filename: string,
run: () => Promise<VirusScanProviderResult>,
): Promise<VirusScanResult> {
const cacheKey = `virus:${fileHash}`;
if ((await this.cacheService.get(cacheKey)) != null) {
return {isClean: false, threat: 'Cached virus signature', fileHash};
}
try {
const scanResult = await run();
if (scanResult.isClean) {
return {isClean: true, fileHash};
}
if (!scanResult.threat) {
throw new Error('Virus scan provider returned infected status without threat name');
}
await this.cacheService.set(cacheKey, 'true', seconds('7 days'));
return {isClean: false, threat: scanResult.threat, fileHash};
} catch (error) {
Logger.error({error: describeError(error), filename, fileHash}, 'Virus scan failed');
if (this.failOpen) {
return {isClean: true, fileHash};
}
throw new Error(`Virus scan failed: ${describeError(error)}`);
}
}
}
@@ -7,7 +7,7 @@ import type {HonoEnv} from '@app/api/types/HonoEnv';
import {lookupGeoip} from '@app/api/utils/IpUtils';
import {getRequestClientIp} from '@app/api/utils/RequestClientIp';
import {stripApiPrefix} from '@app/api/utils/RequestPathUtils';
import {parseAcceptLanguage} from '@pkgs/locale/src/LocaleService';
import {parseAcceptLanguage} from '@pkgs/locale/src/resolution/AcceptLanguageNegotiation';
import type {Context} from 'hono';
import {createMiddleware} from 'hono/factory';
@@ -4,8 +4,7 @@ import type {GuildID, UserID} from '@app/api/BrandedTypes';
import {emitActivity} from '@app/api/infrastructure/activity/ActivityEvents';
import {anonymousActivityMeta} from '@app/api/infrastructure/activity/ActivityMeta';
import type {BanBy, ReportOutcome, ReportTarget, ResolvedBy} from '@app/api/infrastructure/activity/Contract.generated';
import type {IARSubmission} from '@app/api/report/IReportRepository';
import {ReportType} from '@app/api/report/IReportRepository';
import {type IARSubmission, ReportType} from '@app/api/report/ReportModels';
interface GuildBanFacts {
guildId: GuildID;
@@ -6,7 +6,7 @@ import type {ILiveKitService} from '@app/api/infrastructure/ILiveKitService';
import type {IVoiceRoomStore} from '@app/api/infrastructure/IVoiceRoomStore';
import {SERVER_MUTE_ATTRIBUTE} from '@app/api/infrastructure/LiveKitService';
import {LiveKitWebhookService} from '@app/api/infrastructure/LiveKitWebhookService';
import type {IVoiceRepository} from '@app/api/voice/IVoiceRepository';
import type {VoiceRepository} from '@app/api/voice/VoiceRepository';
import {VoiceTopology} from '@app/api/voice/VoiceTopology';
import {TrackSource, type WebhookEvent} from 'livekit-server-sdk';
import {describe, expect, it, vi} from 'vitest';
@@ -33,7 +33,7 @@ function harness(pinnedServerId: string | null) {
voiceRoomStore,
gatewayService,
liveKitService,
new VoiceTopology({} as unknown as IVoiceRepository, null),
new VoiceTopology({} as unknown as VoiceRepository, null),
);
return {service, deleteRoomServer};
}
@@ -92,7 +92,7 @@ function trackHarness() {
{} as unknown as IVoiceRoomStore,
{} as unknown as IGatewayService,
{muteMicrophoneTrack} as unknown as ILiveKitService,
new VoiceTopology({} as unknown as IVoiceRepository, null),
new VoiceTopology({} as unknown as VoiceRepository, null),
);
return {service, muteMicrophoneTrack};
}