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

+9 -9
View File
@@ -1,24 +1,24 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {WorkerDependencies} from '@app/api/worker/WorkerDependencies';
import {
clearWorkerDependencies as clearWorkerDependenciesBase,
getWorkerDependencies as getWorkerDependenciesBase,
setWorkerDependencies as setWorkerDependenciesBase,
} from '@pkgs/worker/src/context/WorkerContext';
let workerDependencies: WorkerDependencies | null = null;
export function setWorkerDependencies(dependencies: WorkerDependencies): void {
setWorkerDependenciesBase(dependencies);
workerDependencies = dependencies;
}
export function setWorkerDependenciesForTest(dependencies: Partial<WorkerDependencies>): void {
setWorkerDependenciesBase(dependencies);
workerDependencies = dependencies as WorkerDependencies;
}
export function getWorkerDependencies(): WorkerDependencies {
return getWorkerDependenciesBase<WorkerDependencies>();
if (!workerDependencies) {
throw new Error('Worker dependencies have not been initialized. Call setWorkerDependencies() first.');
}
return workerDependencies;
}
export function clearWorkerDependencies(): void {
clearWorkerDependenciesBase();
workerDependencies = null;
}
@@ -10,7 +10,6 @@ import type {ChannelService} from '@app/api/channel/services/ChannelService';
import type {ConnectionRepository} from '@app/api/connection/ConnectionRepository';
import {ConnectionService} from '@app/api/connection/ConnectionService';
import {DonationRepository} from '@app/api/donation/DonationRepository';
import type {IDonationRepository} from '@app/api/donation/IDonationRepository';
import type {FavoriteMemeRepository} from '@app/api/favorite_meme/FavoriteMemeRepository';
import type {GuildAuditLogService} from '@app/api/guild/GuildAuditLogService';
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
@@ -165,7 +164,7 @@ export interface WorkerDependencies {
channelService: ChannelService;
guildAuditLogService: GuildAuditLogService;
contactChangeLogService: UserContactChangeLogService;
donationRepository: IDonationRepository;
donationRepository: DonationRepository;
guildService: GuildService;
billingRepository: BillingRepository;
storeEntitlementService: StoreEntitlementService;
+3 -3
View File
@@ -3,7 +3,7 @@
import {createHash, randomUUID} from 'node:crypto';
import {ArchiveAttemptSupersededError} from '@app/api/archive/ArchiveAttemptSupersededError';
import {ArchiveTaskDeferredError, ArchiveTerminalFailureError, isArchiveTask} from '@app/api/archive/ArchiveTask';
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {Logger} from '@app/api/Logger';
import {getWorkerService} from '@app/api/middleware/ServiceRegistry';
import {isJsonRecord, parseJsonRecord} from '@app/api/utils/JsonBoundaryUtils';
@@ -55,7 +55,7 @@ interface WorkerRunnerOptions {
queue: WorkerRunnerQueue;
consumerName: string;
laneName: string;
ledger: IJobLedgerRepository;
ledger: JobLedgerRepository;
workerId?: string;
concurrency?: number;
maxDeliver?: number;
@@ -74,7 +74,7 @@ export class WorkerRunner {
private readonly maxDeliver: number;
private readonly ackWaitMs: number;
private readonly workerService: IWorkerService;
private readonly ledger: IJobLedgerRepository;
private readonly ledger: JobLedgerRepository;
private readonly heartbeat: WorkerHeartbeat | null;
private heartbeatSignal: WorkerHeartbeatSignal | null = null;
private heartbeatTimer: NodeJS.Timeout | null = null;
+3 -3
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {Logger} from '@app/api/Logger';
import type {JetStreamWorkerQueue} from '@app/api/worker/JetStreamWorkerQueue';
import {findLaneForTask, type WorkerTaskName} from '@app/api/worker/WorkerLaneConfig';
@@ -12,9 +12,9 @@ import type {WorkerJobOptions, WorkerJobPayload} from '@pkgs/worker/src/contract
export class WorkerService implements IWorkerService<WorkerTaskName> {
private readonly queue: JetStreamWorkerQueue;
private readonly snowflake: ISnowflakeService;
private readonly ledger: IJobLedgerRepository;
private readonly ledger: JobLedgerRepository;
constructor(queue: JetStreamWorkerQueue, snowflake: ISnowflakeService, ledger: IJobLedgerRepository) {
constructor(queue: JetStreamWorkerQueue, snowflake: ISnowflakeService, ledger: JobLedgerRepository) {
this.queue = queue;
this.snowflake = snowflake;
this.ledger = ledger;
@@ -2,7 +2,7 @@
import type {ReportID} from '@app/api/BrandedTypes';
import {Logger} from '@app/api/Logger';
import type {IReportRepository} from '@app/api/report/IReportRepository';
import type {ReportRepository} from '@app/api/report/ReportRepository';
import {getWorkerDependencies} from '@app/api/worker/WorkerContext';
import type {WorkerTaskHandler} from '@pkgs/worker/src/contracts/WorkerTask';
@@ -16,7 +16,7 @@ export interface ClearAuthenticatedReporterEmailsSummary {
}
export async function clearAuthenticatedReporterEmails(
reportRepository: IReportRepository,
reportRepository: ReportRepository,
pageSize = DEFAULT_SCAN_PAGE_SIZE,
): Promise<ClearAuthenticatedReporterEmailsSummary> {
const summary: ClearAuthenticatedReporterEmailsSummary = {scanned: 0, cleared: 0, skipped: 0, failed: 0};
@@ -3,7 +3,6 @@
import type {ReportID} from '@app/api/BrandedTypes';
import {Config} from '@app/api/Config';
import {Logger} from '@app/api/Logger';
import {type IARSubmission, ReportStatus} from '@app/api/report/IReportRepository';
import {
deleteReportWithEvidence,
forEachStoredReport,
@@ -11,6 +10,7 @@ import {
type ReportDeletionDeps,
referencedObjectKeys,
} from '@app/api/report/ReportDeletion';
import {type IARSubmission, ReportStatus} from '@app/api/report/ReportModels';
import {getReportSearchService} from '@app/api/SearchFactory';
import {getWorkerDependencies} from '@app/api/worker/WorkerContext';
import type {WorkerTaskHandler} from '@pkgs/worker/src/contracts/WorkerTask';
@@ -203,7 +203,7 @@ async function readMessageForExpectedContent(
): Promise<Message | null> {
let lastMessage: Message | null = null;
for (let attempt = 0; attempt < MESSAGE_READ_RETRY_ATTEMPTS; attempt++) {
const message = await channelRepository.getMessage(channelId, messageId);
const message = await channelRepository.messages.getMessage(channelId, messageId);
if (message && messageMatchesExpectedContent(message, expectedContentHash)) {
return message;
}
@@ -383,7 +383,7 @@ async function updateMessageEmbeds(
...freshMessage.toRow(),
embeds: nextEmbeds.length > 0 ? nextEmbeds : null,
});
await channelRepository.updateEmbeds(messageWithEmbeds);
await channelRepository.messages.updateEmbeds(messageWithEmbeds);
return messageWithEmbeds;
}
@@ -438,7 +438,7 @@ const extractEmbeds: WorkerTaskHandler = async (payload, helpers) => {
Logger.info({messageId: messageId.toString()}, 'Skipping extractEmbeds: message not found or no content');
return;
}
const channel = await channelRepository.findUnique(channelId);
const channel = await channelRepository.channelData.findUnique(channelId);
if (!channel) {
Logger.info({channelId: channelId.toString()}, 'Skipping extractEmbeds: channel not found');
return;
@@ -465,7 +465,7 @@ const extractEmbeds: WorkerTaskHandler = async (payload, helpers) => {
const orderedEmbeds = buildOrderedEmbeds(urls, unfurledEmbedsByUrl);
let propagated: Message | null = null;
const handled = await withMessageWriteLock(cacheService, channelId, messageId, async () => {
const latestExpectedMessage = await channelRepository.getMessage(channelId, messageId);
const latestExpectedMessage = await channelRepository.messages.getMessage(channelId, messageId);
if (!latestExpectedMessage) {
Logger.debug({messageId: messageId.toString()}, 'Message no longer exists, skipping embed update');
return false;
@@ -494,7 +494,7 @@ const extractEmbeds: WorkerTaskHandler = async (payload, helpers) => {
},
'Content moderation blocked embed unfurl, deleting parent message',
);
await channelRepository.deleteMessage(
await channelRepository.messages.deleteMessage(
channelId,
messageId,
latestExpectedMessage.authorId || createUserID(0n),
@@ -10,7 +10,7 @@ import {
} from '@app/api/BrandedTypes';
import {guildActive} from '@app/api/experiment/ChannelThreadsGate';
import {Logger} from '@app/api/Logger';
import type {ReadStateMarker} from '@app/api/read_state/IReadStateRepository';
import type {ReadStateMarker} from '@app/api/read_state/ReadStateRepository';
import {mapWithConcurrency} from '@app/api/utils/ConcurrencyUtils';
import {getWorkerDependencies} from '@app/api/worker/WorkerContext';
import {ReadStateFlags} from '@fluxer/constants/src/ThreadConstants';
@@ -233,12 +233,12 @@ const handleMentions: WorkerTaskHandler = async (payload, helpers) => {
const messageId = createMessageID(BigInt(validated.messageId));
const payloadGuildId = validated.guildId ? createGuildID(BigInt(validated.guildId)) : null;
const mentionHere = validated.mentionHere ?? false;
const message = await channelRepository.getMessage(channelId, messageId);
const message = await channelRepository.messages.getMessage(channelId, messageId);
if (!message) {
Logger.debug({messageId}, 'handleMentions: Message not found, skipping');
return;
}
const channel = await channelRepository.findUnique(channelId);
const channel = await channelRepository.channelData.findUnique(channelId);
if (!channel) {
Logger.debug({channelId}, 'handleMentions: Channel not found, skipping');
return;
@@ -235,7 +235,7 @@ const harvestGuildData: ArchiveTaskHandler = async (payload, helpers, attempt) =
let beforeMessageId: MessageID | undefined;
let channelDownloads: Array<PendingAttachmentDownload> = [];
while (messages.length < MESSAGE_LIMIT_PER_CHANNEL) {
const batch = await channelRepository.listMessages(channel.id, beforeMessageId, MESSAGE_BATCH_SIZE);
const batch = await channelRepository.messages.listMessages(channel.id, beforeMessageId, MESSAGE_BATCH_SIZE);
if (batch.length === 0) break;
for (const msg of batch) {
if (msg.authorId == null) continue;
@@ -23,7 +23,7 @@ import {
type UserID,
} from '@app/api/BrandedTypes';
import {Config} from '@app/api/Config';
import type {IThreadRepository} from '@app/api/channel/repositories/IThreadRepository';
import type {ThreadRepository} from '@app/api/channel/repositories/ThreadRepository';
import {
isChannelEligible,
isTimestampInWindow,
@@ -417,7 +417,7 @@ export async function harvestMessages(
}
export async function collectThreadMemberships(
threads: Pick<IThreadRepository, 'listJoinedThreadIds' | 'getMember'>,
threads: Pick<ThreadRepository, 'listJoinedThreadIds' | 'getMember'>,
userId: UserID,
guildIds: ReadonlyArray<GuildID>,
): Promise<Array<ThreadMember>> {
@@ -848,13 +848,18 @@ const harvestUserData: ArchiveTaskHandler = async (payload, helpers, attempt) =>
filterArgs = {
filter,
context,
findChannel: (channelId) => channelRepository.findUnique(channelId),
findChannel: (channelId) => channelRepository.channelData.findUnique(channelId),
};
}
Logger.debug({userId, harvestId, elapsed: Date.now() - startTime}, 'Starting user data harvest');
await progressReporter.updateProgress(INITIAL_PROGRESS, 'Harvesting messages');
Logger.debug({elapsed: Date.now() - startTime}, 'Set progress to INITIAL_PROGRESS');
const {channelMessagesMap, totalMessages} = await harvestMessages(channelRepository, userId, startTime, filterArgs);
const {channelMessagesMap, totalMessages} = await harvestMessages(
channelRepository.messages,
userId,
startTime,
filterArgs,
);
if (totalMessages > 0) {
const progress = Math.min(INITIAL_PROGRESS + Math.floor((totalMessages / 10000) * 50), MESSAGES_PROGRESS_MAX);
await progressReporter.updateProgress(progress, `Harvested ${totalMessages} messages`);
@@ -40,7 +40,7 @@ const indexChannelMessages: WorkerTaskHandler = async (payload) => {
let totalIndexed = 0;
try {
while (true) {
const messages = await channelRepository.listMessages(channelId, cursor, BULK_BATCH_SIZE);
const messages = await channelRepository.messages.listMessages(channelId, cursor, BULK_BATCH_SIZE);
if (messages.length === 0) {
break;
}
@@ -81,11 +81,11 @@ const indexChannelMessages: WorkerTaskHandler = async (payload) => {
Logger.info({completionKey: validated.completionKey}, 'All channels indexed');
}
}
const channel = await channelRepository.findUnique(channelId);
const channel = await channelRepository.channelData.findUnique(channelId);
if (channel?.isThread()) {
await channelRepository.channelData.patchIndexedAt(channelId, new Date());
} else if (channel) {
await channelRepository.upsert({...channel.toRow(), indexed_at: new Date()});
await channelRepository.channelData.upsert({...channel.toRow(), indexed_at: new Date()});
}
Logger.info({channelId: channelId.toString(), totalIndexed}, 'Bulk channel indexing complete');
} catch (error) {
@@ -103,7 +103,7 @@ const messageShredTask: WorkerTaskHandler = async (payload, helpers) => {
for (const validationChunk of chunkArray(typedSlice, VALIDATION_CHUNK_SIZE)) {
const messageFetches = validationChunk.map(
({channelId, messageId}: {channelId: ChannelID; messageId: MessageID}) =>
channelRepository.getMessage(channelId, messageId),
channelRepository.messages.getMessage(channelId, messageId),
);
const fetchedMessages = await Promise.all(messageFetches);
const deletableChunk: Array<{
@@ -130,7 +130,7 @@ const messageShredTask: WorkerTaskHandler = async (payload, helpers) => {
deletionChunk.map(
async ({channelId, messageId, message}: {channelId: ChannelID; messageId: MessageID; message: Message}) => {
await purgeMessageAttachments(message, storageService, purgeQueue);
return channelRepository.deleteMessage(channelId, messageId, authorId);
return channelRepository.messages.deleteMessage(channelId, messageId, authorId);
},
),
);
@@ -2,7 +2,7 @@
import type {StorePurchaseRow} from '@app/api/database/types/StoreBillingTypes';
import {mapGuildMemberToResponse} from '@app/api/guild/GuildModel';
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
import type {PremiumStateReconciliationQueueService} from '@app/api/infrastructure/PremiumStateReconciliationQueueService';
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
@@ -18,7 +18,7 @@ import {resolveStoreAccessEnd} from '@app/api/store_billing/StoreBillingMappers'
import {isTerminalStorePurchaseState} from '@app/api/store_billing/StoreBillingTypes';
import type {StoreEntitlementService} from '@app/api/store_billing/StoreEntitlementService';
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 {checkIsPremium, createPremiumClearPatch, shouldStripExpiredPremium} from '@app/api/user/UserHelpers';
import {mapUserToPrivateResponse} from '@app/api/user/UserMappers';
import {getWorkerDependencies} from '@app/api/worker/WorkerContext';
@@ -43,8 +43,8 @@ interface SweepResult {
}
interface SweepDeps {
userRepository: IUserRepository;
guildRepository: IGuildRepositoryAggregate;
userRepository: UserRepository;
guildRepository: GuildRepository;
userCacheService: UserCacheService;
gatewayService: IGatewayService;
premiumStateReconciliationQueueService: PremiumStateReconciliationQueueService;
@@ -205,7 +205,7 @@ const refreshChannelMessages: IndexHandler = async (payload, helpers, kvClient,
const {channelRepository} = getWorkerDependencies();
const guildId = createGuildID(BigInt(payload.guild_id!));
const searchService = requireSearchService<IMessageSearchService>(getMessageSearchService());
const guildChannels = await channelRepository.listGuildChannels(guildId, 'complete');
const guildChannels = await channelRepository.channelData.listGuildChannels(guildId, 'complete');
const threadIds = await channelRepository.threads.listGuildThreadIds(guildId, {parents: guildChannels});
const channels = [...guildChannels.map((channel) => ({id: channel.id})), ...threadIds.map((id) => ({id}))];
if (channels.length === 0) {
@@ -16,7 +16,7 @@ const removeChannelFollowers: WorkerTaskHandler = async (payload, helpers) => {
const {channelRepository, webhookRepository, gatewayService} = getWorkerDependencies();
const sourceChannelId = createChannelID(BigInt(validated.sourceChannelId));
if (validated.reason === 'converted') {
const channel = await channelRepository.findUnique(sourceChannelId);
const channel = await channelRepository.channelData.findUnique(sourceChannelId);
if (channel?.type === ChannelTypes.GUILD_ANNOUNCEMENT) {
helpers.logger.info(
{sourceChannelId: validated.sourceChannelId},
@@ -2,7 +2,7 @@
import {createGuildID, type GuildID, guildIdToRoleId} from '@app/api/BrandedTypes';
import {mapChannelToResponse} from '@app/api/channel/ChannelMappers';
import type {IThreadRepository} from '@app/api/channel/repositories/IThreadRepository';
import type {ThreadRepository} from '@app/api/channel/repositories/ThreadRepository';
import {withThreadParentFieldsMany} from '@app/api/channel/services/thread/ThreadParentSettings';
import {getCompiledChannelThreadsConfig, guildActive} from '@app/api/experiment/ChannelThreadsGate';
import {mapGuildRoleToResponse} from '@app/api/guild/GuildModel';
@@ -41,7 +41,7 @@ async function enqueueThreadPermissionSeed(guildId: string): Promise<void> {
);
}
export async function ensureGuildThreadPermissionsSeeded(threads: IThreadRepository, guildId: GuildID): Promise<void> {
export async function ensureGuildThreadPermissionsSeeded(threads: ThreadRepository, guildId: GuildID): Promise<void> {
const key = guildId.toString();
if (seededGuildIds.has(key)) return;
const marker = await threads.getGuildMarker(guildId);
@@ -74,7 +74,7 @@ const seedThreadPermissions: WorkerTaskHandler = async (payload) => {
const guild = await guildRepository.findUnique(guildId);
if (!guild) return;
await channelRepository.threads.ensureGuildMarker(guildId);
const channels = await channelRepository.listGuildChannels(guildId, 'complete');
const channels = await channelRepository.channelData.listGuildChannels(guildId, 'complete');
const updatedChannels: Array<Channel> = [];
for (const channel of channels) {
if (channel.permissionOverwrites.size === 0) continue;
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {GuildID, MessageID, UserID} from '@app/api/BrandedTypes';
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
import type {
CreateThreadMember,
IThreadRepository,
ThreadMemberAddResult,
} from '@app/api/channel/repositories/IThreadRepository';
ThreadRepository,
} from '@app/api/channel/repositories/ThreadRepository';
import {buildBroadcastMessageData} from '@app/api/channel/services/message/MessageGatewayDispatch';
import {dispatchThreadEvents, threadMembersUpdateEvent} from '@app/api/channel/services/thread/ThreadDispatch';
import {loadThreadView} from '@app/api/channel/services/thread/ThreadViews';
@@ -21,7 +21,7 @@ import {
import type {GatewayMentionSourceEntry, IGatewayService} from '@app/api/infrastructure/IGatewayService';
import type {Channel} from '@app/api/models/Channel';
import type {ThreadState} from '@app/api/models/ThreadState';
import type {IUserRepository} from '@app/api/user/IUserRepository';
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
import {
MAX_ROLE_MENTION_THREAD_ADDS,
MAX_THREAD_MEMBERS,
@@ -30,13 +30,13 @@ import {
import {MaxThreadMembersError} from '@fluxer/errors/src/domains/channel/MaxThreadMembersError';
export interface ThreadMentionScopeDeps {
channelRepository: IChannelRepository;
channelRepository: ChannelRepository;
gatewayService: IGatewayService;
userRepository: IUserRepository;
userRepository: UserRepository;
}
export async function threadRecipients(
userRepository: IUserRepository,
userRepository: UserRepository,
guildId: GuildID,
userIds: Array<UserID>,
): Promise<Set<UserID>> {
@@ -51,7 +51,7 @@ export async function threadRecipients(
}
export async function addThreadMembersWithinCap(
threads: IThreadRepository,
threads: ThreadRepository,
state: ThreadState,
members: Array<CreateThreadMember>,
): Promise<ThreadMemberAddResult | null> {
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {type ChannelID, createChannelID, type MessageID} from '@app/api/BrandedTypes';
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
import {withThreadContext} from '@app/api/channel/services/ChannelGatewayDispatch';
import {decrementThreadMessageCount} from '@app/api/channel/services/message/MessageHelpers';
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
interface BulkDeleteDispatcherDeps {
channelRepository: IChannelRepository;
channelRepository: ChannelRepository;
gatewayService: IGatewayService;
batchSize: number;
}
@@ -30,7 +30,7 @@ export function createBulkDeleteDispatcher({channelRepository, gatewayService, b
continue;
}
const channelId = createChannelID(BigInt(channelIdStr));
const channel = await channelRepository.findUnique(channelId);
const channel = await channelRepository.channelData.findUnique(channelId);
if (channel) {
await decrementThreadMessageCount(channelRepository, channel, messageIdsBatch);
const payloadIds = messageIdsBatch.map((id) => id.toString());
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {AdminAuditLog} from '@app/api/admin/IAdminRepository';
import type {AdminAuditLog} from '@app/api/admin/AdminRepository';
import {createTestAccount, setUserACLs, type TestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {createGuildID, createUserID} from '@app/api/BrandedTypes';
import {createApiContext} from '@app/api/CreateApiContext';
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {AdminAuditLog} from '@app/api/admin/IAdminRepository';
import type {AdminAuditLog} from '@app/api/admin/AdminRepository';
import {createTestAccount, setUserACLs} from '@app/api/auth/tests/AuthTestUtils';
import {BANNED_FILE_SHAS_REFRESH_CHANNEL} from '@app/api/constants/ContentModeration';
import {getSnowflakeService, setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry';
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {AdminAuditLog} from '@app/api/admin/IAdminRepository';
import type {AdminAuditLog} from '@app/api/admin/AdminRepository';
import {sendChannelMessage, setupTestGuildWithMembers} from '@app/api/channel/tests/ChannelTestUtils';
import {getGatewayService, getSnowflakeService} from '@app/api/middleware/ServiceRegistry';
import {
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {AdminRepository} from '@app/api/admin/AdminRepository';
import type {AdminAuditLog} from '@app/api/admin/IAdminRepository';
import {type AdminAuditLog, AdminRepository} from '@app/api/admin/AdminRepository';
import {
clearTestEmails,
createTestAccount,
@@ -29,7 +28,7 @@ import {
getUserPermissionUtils,
getUserRepository,
} from '@app/api/middleware/ServiceSingletons';
import {ReportStatus} from '@app/api/report/IReportRepository';
import {ReportStatus} from '@app/api/report/ReportModels';
import {ReportRepository} from '@app/api/report/ReportRepository';
import {getReportSearchService} from '@app/api/SearchFactory';
import {drainSearchTasks} from '@app/api/search/SearchTaskTracker';
@@ -9,8 +9,8 @@ import {
type IARMessageContextRow,
type IARSubmissionRow,
} from '@app/api/database/types/ReportTypes';
import type {IARSubmission} from '@app/api/report/IReportRepository';
import type {ReportDeletionDeps} from '@app/api/report/ReportDeletion';
import type {IARSubmission} from '@app/api/report/ReportModels';
import {ReportRepository} from '@app/api/report/ReportRepository';
import type {IReportSearchService} from '@app/api/search/IReportSearchService';
import {MockStorageService} from '@app/api/test/mocks/MockStorageService';
@@ -12,7 +12,7 @@ import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
import {syncChannelThreadsConfig} from '@app/api/experiment/ChannelThreadsGate';
import type {GatewayMentionSourceEntry, IGatewayService} from '@app/api/infrastructure/IGatewayService';
import {ThreadState} from '@app/api/models/ThreadState';
import type {ReadStateMentionUpdate} from '@app/api/read_state/IReadStateRepository';
import type {ReadStateMentionUpdate} from '@app/api/read_state/ReadStateRepository';
import type {ReadStateService} from '@app/api/read_state/ReadStateService';
import {NoopLogger} from '@app/api/test/mocks/NoopLogger';
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
@@ -118,8 +118,6 @@ function createHarness(params: {
type: params.channelType ?? ChannelTypes.PUBLIC_THREAD,
};
const channelRepository = {
getMessage: async () => message,
findUnique: async () => channel,
threads: {
getState: async () => state,
getMembers: async (_threadId: ChannelID, userIds: Array<UserID>) =>
@@ -135,11 +133,10 @@ function createHarness(params: {
getParentConfig: async () => ({availableTags: (params.forumTags?.live ?? []).map((id) => ({id}))}),
},
channelData: {
findUnique: async () => ({
id: PARENT,
type: parentType,
isThreadOnly: () => parentType === ChannelTypes.GUILD_FORUM,
}),
findUnique: async (channelId: ChannelID) =>
channelId === THREAD
? channel
: {id: PARENT, type: parentType, isThreadOnly: () => parentType === ChannelTypes.GUILD_FORUM},
},
messages: {
getMessage: async () => ({...message, toRow: () => ({flags: message.flags})}),
@@ -35,8 +35,10 @@ function createHarness(messages: Array<Message> = []): {
setWorkerDependenciesForTest({
kvClient,
channelRepository: {
listMessages: async (_channelId: unknown, cursor: unknown) => (cursor === undefined ? messages : []),
findUnique: async () => null,
messages: {
listMessages: async (_channelId: unknown, cursor: unknown) => (cursor === undefined ? messages : []),
},
channelData: {findUnique: async () => null},
} as unknown as ChannelRepository,
userRepository: {listUsers: async () => []} as unknown as UserRepository,
});
@@ -94,8 +94,8 @@ describe('seedThreadPermissions', () => {
})
.execute();
const channelRepository = getChannelRepository();
const stored = await channelRepository.findUnique(createChannelID(BigInt(channel.id)));
await channelRepository.upsert({
const stored = await channelRepository.channelData.findUnique(createChannelID(BigInt(channel.id)));
await channelRepository.channelData.upsert({
...stored!.toRow(),
permission_overwrites: new Map([
[
@@ -118,9 +118,9 @@ describe('seedThreadPermissions', () => {
await seedThreadPermissions({guildId: guild.id}, helpers());
const everyone = await getGuildRepository().getRole(createRoleID(BigInt(guild.id)), guildId);
expect(everyone?.permissions).toBe(DEFAULT_PERMISSIONS | DEFAULT_THREAD_PERMISSIONS);
const seeded = (await channelRepository.findUnique(createChannelID(BigInt(channel.id))))!.permissionOverwrites.get(
createRoleID(BigInt(guild.id)),
);
const seeded = (await channelRepository.channelData.findUnique(
createChannelID(BigInt(channel.id)),
))!.permissionOverwrites.get(createRoleID(BigInt(guild.id)));
expect(seeded?.allow).toBe(0n);
expect(seeded?.deny).toBe(Permissions.SEND_MESSAGES | DEFAULT_THREAD_PERMISSIONS);
expect(dispatchGuild.mock.calls.map(([params]) => params.event)).toEqual([
@@ -166,7 +166,7 @@ describe('seedThreadPermissions', () => {
setConfig({...ALL_THREADS_ACTIVE, ever_enabled: true, enabled_guild_ids: [guild.id]});
await seedThreadPermissions({guildId: guild.id}, helpers());
const seeded = (await getChannelRepository().findUnique(
const seeded = (await getChannelRepository().channelData.findUnique(
createChannelID(BigInt(channel.id)),
))!.permissionOverwrites.get(createRoleID(BigInt(role.id)));
expect(seeded?.allow).toBe(Permissions.SEND_MESSAGES | DEFAULT_THREAD_PERMISSIONS);
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ChannelService} from '@app/api/channel/services/ChannelService';
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry';
import {NoopWorkerService} from '@app/api/test/NoopWorkerService';
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
@@ -44,7 +44,7 @@ function createLedgerStub(cancelAfter: number) {
cancelChecks.count += 1;
return cancelChecks.count > cancelAfter;
},
} as unknown as IJobLedgerRepository;
} as unknown as JobLedgerRepository;
return {ledger, markCancelled, markSucceeded};
}
@@ -180,7 +180,7 @@ describe('thread maintenance sweeps', () => {
it('includes thread messages in user and guild harvests', async () => {
const s = await setup();
const harvested = await harvestMessages(
getChannelRepository(),
getChannelRepository().messages,
createUserID(BigInt(s.member.userId)),
Date.now(),
null,
@@ -134,7 +134,7 @@ describe('thread maintenance tasks', () => {
const thread = await startThread(s.owner.token, s.channelId);
await deleteChannelThreads({guildId: s.guildId, parentId: s.channelId}, helpers());
expect(await state(thread.id)).toBeNull();
expect(await getChannelRepository().findUnique(createChannelID(BigInt(thread.id)))).toBeNull();
expect(await getChannelRepository().channelData.findUnique(createChannelID(BigInt(thread.id)))).toBeNull();
});
it('purges thread memberships of a removed guild member and keeps those of a rejoined one', async () => {
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry';
import {NoopWorkerService} from '@app/api/test/NoopWorkerService';
import {WorkerRunner} from '@app/api/worker/WorkerRunner';
@@ -32,7 +32,7 @@ function createRunner(task: () => Promise<void>): TestWorkerRunner {
queue: queueStub,
consumerName: 'workers_batch',
laneName: 'batch',
ledger: {} as IJobLedgerRepository,
ledger: {} as JobLedgerRepository,
concurrency: 12,
maxDeliver: LANE_MAX_DELIVER,
ackWaitMs: LANE_ACK_WAIT_MS,
@@ -149,7 +149,7 @@ describe('Worker ack heartbeat', () => {
queue: queueStub,
consumerName: 'workers_batch',
laneName: 'batch',
ledger: {} as IJobLedgerRepository,
ledger: {} as JobLedgerRepository,
concurrency: 12,
maxDeliver: LANE_MAX_DELIVER,
ackWaitMs: LANE_ACK_WAIT_MS,
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry';
import {NoopWorkerService} from '@app/api/test/NoopWorkerService';
import {CronScheduler} from '@app/api/worker/CronScheduler';
@@ -73,7 +73,7 @@ function createRunner(messages: FakeConsumerMessages, heartbeat: WorkerHeartbeat
},
consumerName: 'workers_batch',
laneName: 'batch',
ledger: {} as IJobLedgerRepository,
ledger: {} as JobLedgerRepository,
concurrency: 12,
maxDeliver: 25,
ackWaitMs: 120000,
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry';
import {NoopWorkerService} from '@app/api/test/NoopWorkerService';
import {WorkerRunner} from '@app/api/worker/WorkerRunner';
@@ -36,7 +36,7 @@ function createRunner(): TestWorkerRunner {
queue: queueStub,
consumerName: 'workers_lifecycle',
laneName: 'lifecycle',
ledger: ledgerStub as unknown as IJobLedgerRepository,
ledger: ledgerStub as unknown as JobLedgerRepository,
concurrency: 8,
maxDeliver: 25,
ackWaitMs: 60000,
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry';
import {NoopWorkerService} from '@app/api/test/NoopWorkerService';
import {WorkerRunner} from '@app/api/worker/WorkerRunner';
@@ -93,7 +93,7 @@ function createRunner(streams: Array<FakeConsumerMessages>): {runner: WorkerRunn
queue,
consumerName: 'workers_batch',
laneName: 'batch',
ledger: {} as IJobLedgerRepository,
ledger: {} as JobLedgerRepository,
concurrency: 12,
maxDeliver: 25,
ackWaitMs: 120000,
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import {setInjectedWorkerService} from '@app/api/middleware/ServiceRegistry';
import {NoopWorkerService} from '@app/api/test/NoopWorkerService';
import {WorkerRunner} from '@app/api/worker/WorkerRunner';
@@ -78,7 +78,7 @@ function createRunner(task: () => Promise<void>, messages: FakeConsumerMessages)
queue: createQueueStub(messages),
consumerName: 'workers_batch',
laneName: 'batch',
ledger: {} as IJobLedgerRepository,
ledger: {} as JobLedgerRepository,
concurrency: 12,
maxDeliver: 25,
ackWaitMs: 120000,
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
import type {CreateJobInput, IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
import type {CreateJobInput, JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
import type {JetStreamWorkerQueue} from '@app/api/worker/JetStreamWorkerQueue';
import {WorkerService} from '@app/api/worker/WorkerService';
import {describe, expect, test} from 'vitest';
@@ -47,7 +47,7 @@ function createHarness(options?: {
if (options?.discardError) throw options.discardError;
discarded.push({jobId, createdAt});
},
} as unknown as IJobLedgerRepository;
} as unknown as JobLedgerRepository;
const queue = {
enqueue: async (taskType: string, payload: Record<string, unknown>) => {
calls.push('enqueue');