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

@@ -57,6 +57,7 @@ export class AdminGuildService {
entityAssetService: deps.entityAssetService,
auditService: deps.auditService,
updatePropagator: this.updatePropagator,
guildService: deps.guildService,
});
this.vanityService = new AdminGuildVanityService({
guildRepository: deps.guildRepository,
@@ -6,6 +6,9 @@ import type {AdminGuildUpdatePropagator} from '@app/api/admin/services/guild/Adm
import {createGuildID, createUserID, type GuildID, type UserID} from '@app/api/BrandedTypes';
import type {GuildRow} from '@app/api/database/types/GuildTypes';
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
import {staffEditTouchesInvitePause} from '@app/api/guild/services/GuildInvitePauseApplier';
import type {GuildService} from '@app/api/guild/services/GuildService';
import {emitGuildFeaturesChanged} from '@app/api/infrastructure/activity/GuildEvents';
import type {EntityAssetService, PreparedAssetUpload} from '@app/api/infrastructure/EntityAssetService';
import {Logger} from '@app/api/Logger';
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
@@ -21,6 +24,7 @@ interface AdminGuildUpdateServiceDeps {
entityAssetService: EntityAssetService;
auditService: AdminAuditService;
updatePropagator: AdminGuildUpdatePropagator;
guildService: Pick<GuildService, 'withUpdateLock'>;
}
export class AdminGuildUpdateService {
@@ -39,23 +43,36 @@ export class AdminGuildUpdateService {
adminUserId: UserID;
auditLogReason: string | null;
}) {
const {guildRepository, auditService, updatePropagator} = this.deps;
const guild = await guildRepository.findUnique(guildId);
if (!guild) {
throw new UnknownGuildError();
}
const newFeatures = new Set(guild.features);
for (const feature of addFeatures) {
newFeatures.add(feature);
}
for (const feature of removeFeatures) {
newFeatures.delete(feature);
}
const updatedGuild = await guildRepository.upsertPartial(guildId, {features: newFeatures}, guild.toRow());
const {guildRepository, auditService, updatePropagator, guildService} = this.deps;
const {guild, newFeatures, updatedGuild} = await guildService.withUpdateLock(guildId, async () => {
const guild = await guildRepository.findUnique(guildId);
if (!guild) {
throw new UnknownGuildError();
}
const newFeatures = new Set(guild.features);
for (const feature of addFeatures) {
newFeatures.add(feature);
}
for (const feature of removeFeatures) {
newFeatures.delete(feature);
}
const patch: Partial<GuildRow> = {features: newFeatures};
if (staffEditTouchesInvitePause(guild, [...addFeatures, ...removeFeatures])) {
patch.invites_paused_until = null;
}
const updatedGuild = await guildRepository.upsertPartial(guildId, patch, guild.toRow());
return {guild, newFeatures, updatedGuild};
});
await updatePropagator.dispatchGuildUpdate(guildId, updatedGuild, {
adminUserId,
reconcileDiscoveryFeature: true,
});
await emitGuildFeaturesChanged({
previous: guild,
updated: updatedGuild,
actorId: adminUserId,
source: 'staff',
});
await auditService.createAuditLog({
adminUserId,
targetType: 'guild',