feat(api): add managed guild invite pauses (#3419)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-12 15:14:03 +02:00
1 parent c890956e4f
commit 17746fcdc4
28 files changed
+1203 -60

No files matched your search

@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {applyPauseGuildInvites, type GuildInvitePauseDeps} from '@app/api/guild/services/GuildInvitePauseApplier';
import {emitActivity} from '@app/api/infrastructure/activity/ActivityEvents';
import {workerMeta} from '@app/api/infrastructure/activity/ActivityMeta';
import {
@@ -27,6 +28,7 @@ import {AckPolicy, type ConsumerMessages, type JetStreamClient, type JsMsg} from
export interface AccountActionDeps {
js: Pick<JetStreamClient, 'consumers'>;
state: AccountStateDeps;
guildInvites: GuildInvitePauseDeps;
publishOutcome?: (key: string, outcome: ActionOutcome) => Promise<void>;
now?: () => number;
retryDelayMs?: number;
@@ -79,6 +81,8 @@ export function applyAction(deps: AccountActionDeps, env: ActionEnvelope): Promi
return applyHideRecentMessages(deps.state, env);
case 'delete_user_messages':
return applyDeleteUserMessages(deps.state, env);
case 'pause_guild_invites':
return applyPauseGuildInvites(deps.guildInvites, env);
default:
return Promise.resolve(outcomeOf(env as ActionEnvelope, 'unsupported'));
}
@@ -11,6 +11,7 @@ import {DonationRepository} from '@app/api/donation/DonationRepository';
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';
import type {GuildInvitePauseDeps} from '@app/api/guild/services/GuildInvitePauseApplier';
import type {GuildService} from '@app/api/guild/services/GuildService';
import type {AvatarService} from '@app/api/infrastructure/AvatarService';
import type {IPurgeQueue} from '@app/api/infrastructure/CachePurgeQueue';
@@ -281,3 +282,13 @@ export async function initializeWorkerDependencies(snowflakeService: ISnowflakeS
},
};
}
export function guildInvitePauseDeps(deps: WorkerDependencies): GuildInvitePauseDeps {
return {
guilds: deps.guildRepository,
locks: deps.cacheService,
schedule: deps.kvClient,
gateway: deps.gatewayService,
auditLog: deps.guildAuditLogService,
};
}
@@ -59,6 +59,7 @@ const LANE_CONFIG = {
'bulkBanFileShas',
'bulkDeleteMessagesForUsers',
'drainActivitySpool',
'liftExpiredGuildInvitePauses',
] as const,
retiredTasks: ['sendScheduledMessage', 'finalizeNcmecAttachmentReport'],
concurrency: 8,
+7 -1
View File
@@ -40,7 +40,11 @@ import {queueBlocklistFeedStartupJobs} from '@app/api/worker/BlocklistFeedStartu
import {CronScheduler} from '@app/api/worker/CronScheduler';
import {JetStreamWorkerQueue, JOBS_STREAM_MAX_AGE_MS} from '@app/api/worker/JetStreamWorkerQueue';
import {clearWorkerDependencies, setWorkerDependencies} from '@app/api/worker/WorkerContext';
import {initializeWorkerDependencies, type WorkerDependencies} from '@app/api/worker/WorkerDependencies';
import {
guildInvitePauseDeps,
initializeWorkerDependencies,
type WorkerDependencies,
} from '@app/api/worker/WorkerDependencies';
import {WorkerHeartbeat} from '@app/api/worker/WorkerHeartbeat';
import {
resolveCronSchedulerEnabled,
@@ -108,6 +112,7 @@ function registerCronJobs(cron: CronScheduler, jobsStreamMaxAgeMs: number): void
}
cron.upsert('flushUserActivityBuffer', 'flushUserActivityBuffer', {}, '*/10 * * * * *', {ledger: false});
cron.upsert('drainActivitySpool', 'drainActivitySpool', {}, '*/5 * * * * *', {ledger: false});
cron.upsert('liftExpiredGuildInvitePauses', 'liftExpiredGuildInvitePauses', {}, '0 * * * * *', {ledger: false});
cron.upsert('archiveInactiveThreads', 'archiveInactiveThreads', {}, '0 * * * * *', {
ledger: false,
enabled: () => true,
@@ -328,6 +333,7 @@ export async function startWorkerMain(): Promise<void> {
},
dependencies.channelRepository,
),
guildInvites: guildInvitePauseDeps(dependencies),
});
Logger.info('Account action consumer started');
}
@@ -27,6 +27,7 @@ import harvestGuildData from '@app/api/worker/tasks/HarvestGuildData';
import harvestUserData from '@app/api/worker/tasks/HarvestUserData';
import indexChannelMessages from '@app/api/worker/tasks/IndexChannelMessages';
import indexGuildMembers from '@app/api/worker/tasks/IndexGuildMembers';
import liftExpiredGuildInvitePauses from '@app/api/worker/tasks/LiftExpiredGuildInvitePauses';
import messageShred from '@app/api/worker/tasks/MessageShred';
import pollAppStoreNotificationHistory from '@app/api/worker/tasks/PollAppStoreNotificationHistory';
import pollGooglePlayVoidedPurchases from '@app/api/worker/tasks/PollGooglePlayVoidedPurchases';
@@ -99,6 +100,7 @@ export const workerTasks: Record<WorkerTaskName, WorkerTaskHandler> = {
harvestUserData,
indexChannelMessages,
indexGuildMembers,
liftExpiredGuildInvitePauses,
messageShred,
processAssetDeletionQueue,
processCachePurgeQueue,
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {liftExpiredGuildInvitePauses} from '@app/api/guild/services/GuildInvitePauseApplier';
import {getWorkerDependencies} from '@app/api/worker/WorkerContext';
import {guildInvitePauseDeps} from '@app/api/worker/WorkerDependencies';
import type {WorkerTaskHandler} from '@pkgs/worker/src/contracts/WorkerTask';
const BATCH_SIZE = 100;
const liftExpiredGuildInvitePausesTask: WorkerTaskHandler = async (_payload, helpers) => {
const lifted = await liftExpiredGuildInvitePauses(guildInvitePauseDeps(getWorkerDependencies()), BATCH_SIZE);
if (lifted > 0) helpers.logger.info({lifted}, 'Lifted expired guild invite pauses');
};
export default liftExpiredGuildInvitePausesTask;
@@ -208,7 +208,12 @@ function harness(): Harness {
authored: authored as unknown as AccountStateDeps['authored'],
now: () => NOW,
};
h.deps = {js: {} as AccountActionDeps['js'], state, now: () => NOW};
h.deps = {
js: {} as AccountActionDeps['js'],
state,
guildInvites: {} as AccountActionDeps['guildInvites'],
now: () => NOW,
};
return h;
}