feat(threads): thread and forum API, admin, schemas and docs (#3251)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-07 15:25:12 +02:00
1 parent 5f4295e399
commit 3f4160b138
495 files changed
+39232 -1215

No files matched your search

@@ -12,6 +12,9 @@ import {ChannelOperationsService} from '@app/api/channel/services/channel_data/C
import {ChannelUtilsService} from '@app/api/channel/services/channel_data/ChannelUtilsService';
import {GroupDmUpdateService} from '@app/api/channel/services/channel_data/GroupDmUpdateService';
import type {MessagePersistenceService} from '@app/api/channel/services/message/MessagePersistenceService';
import {ThreadModifyService} from '@app/api/channel/services/thread/ThreadModifyService';
import {pickThreadParentSettings} from '@app/api/channel/services/thread/ThreadParentSettings';
import type {ThreadViewer} from '@app/api/experiment/ChannelThreadsGate';
import type {GuildAuditLogService} from '@app/api/guild/GuildAuditLogService';
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
import type {AvatarService} from '@app/api/infrastructure/AvatarService';
@@ -30,12 +33,16 @@ import type {IUserRepository} from '@app/api/user/IUserRepository';
import type {VoiceAvailabilityService} from '@app/api/voice/VoiceAvailabilityService';
import type {IWebhookRepository} from '@app/api/webhook/IWebhookRepository';
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
import type {ChannelUpdateRequest} from '@fluxer/schema/src/domains/channel/ChannelRequestSchemas';
import type {
ChannelUpdateGatedRequest,
ChannelUpdateNonThreadRequest,
ChannelUpdateThreadRequest,
} from '@fluxer/schema/src/domains/channel/ChannelRequestSchemas';
import type {ICacheService} from '@pkgs/cache/src/ICacheService';
import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
type GuildChannelUpdateRequest = Exclude<
ChannelUpdateRequest,
ChannelUpdateNonThreadRequest,
{
type: typeof ChannelTypes.GROUP_DM;
}
@@ -47,6 +54,7 @@ export class ChannelDataService {
public readonly operations: ChannelOperationsService;
public readonly groupDmUpdate: GroupDmUpdateService;
public readonly utils: ChannelUtilsService;
public readonly threadModify: ThreadModifyService;
constructor(
channelRepository: IChannelRepositoryAggregate,
@@ -93,7 +101,18 @@ export class ChannelDataService {
limitConfigService,
rateLimitService,
cacheService,
snowflakeService,
);
this.threadModify = new ThreadModifyService({
channelRepository,
gatewayService,
guildAuditLogService,
rateLimitService,
cacheService,
snowflakeService,
messagePersistence: messagePersistenceService,
utils: this.utils,
});
this.groupDmUpdate = new GroupDmUpdateService(
channelRepository,
userRepository,
@@ -104,8 +123,26 @@ export class ChannelDataService {
);
}
async deleteThread({
userId,
viewer,
channelId,
requestCache,
auditLogReason,
}: {
userId: UserID;
viewer: ThreadViewer;
channelId: ChannelID;
requestCache: RequestCache;
auditLogReason: string | null;
}): Promise<void> {
const authChannel = await this.auth.getChannelAuthenticated({userId, channelId, viewer, skipNsfwValidation: true});
await this.threadModify.deleteThread({authChannel, userId, requestCache, auditLogReason});
}
async editChannel({
userId,
viewer,
channelId,
data,
clientFeatures,
@@ -114,22 +151,34 @@ export class ChannelDataService {
typeConversion,
}: {
userId: UserID;
viewer: ThreadViewer;
channelId: ChannelID;
data: Omit<ChannelUpdateRequest, 'type'>;
data: Omit<ChannelUpdateGatedRequest, 'type'>;
clientFeatures: ReadonlySet<string>;
requestCache: RequestCache;
auditLogReason: string | null;
typeConversion?: ChannelTypeConversion | null;
}): Promise<Channel> {
const {channel} = await this.auth.getChannelAuthenticated({userId, channelId, skipNsfwValidation: true});
const authChannel = await this.auth.getChannelAuthenticated({userId, channelId, viewer, skipNsfwValidation: true});
const {channel} = authChannel;
if (authChannel.thread) {
return this.threadModify.updateThread({
authChannel,
userId,
data: data as Omit<ChannelUpdateThreadRequest, 'type'>,
requestCache,
auditLogReason,
});
}
if (channel.type === ChannelTypes.GROUP_DM) {
const groupDmData = data as Omit<Extract<ChannelUpdateNonThreadRequest, {type: 3}>, 'type'>;
return await this.groupDmUpdate.updateGroupDmChannel({
userId,
channelId,
name: data.name !== undefined ? data.name : undefined,
icon: data.icon !== undefined ? data.icon : undefined,
ownerId: data.owner_id ? createUserID(data.owner_id) : undefined,
nicks: data.nicks,
name: groupDmData.name !== undefined ? groupDmData.name : undefined,
icon: groupDmData.icon !== undefined ? groupDmData.icon : undefined,
ownerId: groupDmData.owner_id ? createUserID(groupDmData.owner_id) : undefined,
nicks: groupDmData.nicks,
requestCache,
});
}
@@ -188,8 +237,10 @@ export class ChannelDataService {
}
return this.operations.editChannel({
userId,
viewer,
channelId,
data: channelUpdateData,
threadParent: pickThreadParentSettings(channel.type, guildChannelData),
clientFeatures,
requestCache,
auditLogReason,