mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
refactor(api): collapse single-impl interfaces and facades (#3382)
This commit is contained in:
1 parent
e7b13fd19f
commit
c16d414d93
426 files changed
+2085
-6445
No files matched your search
@@ -6,7 +6,7 @@ import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {IMediaService} from '@app/api/infrastructure/IMediaService';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {BotMfaMirrorService} from '@app/api/oauth/BotMfaMirrorService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {UserActivityBuffer} from '@app/api/user/services/UserActivityBuffer';
|
||||
import type {UserContactChangeLogService} from '@app/api/user/services/UserContactChangeLogService';
|
||||
import type {WorkerTaskName} from '@app/api/worker/WorkerLaneConfig';
|
||||
@@ -17,7 +17,7 @@ import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
|
||||
import type {IWorkerService} from '@pkgs/worker/src/contracts/IWorkerService';
|
||||
|
||||
export interface ApiServices {
|
||||
users: IUserRepository;
|
||||
users: UserRepository;
|
||||
cache: ICacheService;
|
||||
gateway: IGatewayService;
|
||||
kv: IKVProvider;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {AdminAuditLog, BannedIpEntry, BannedIpKind, IAdminRepository} from '@app/api/admin/IAdminRepository';
|
||||
import {createUserID} from '@app/api/BrandedTypes';
|
||||
import {createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import {isIpBanExempt} from '@app/api/ban/IpBanExemptions';
|
||||
import {ContentBlocklistCategory} from '@app/api/constants/ContentModeration';
|
||||
import {
|
||||
@@ -35,6 +34,27 @@ import {parseIpBanEntry, tryParseSingleIp} from '@app/api/utils/IpRangeUtils';
|
||||
import {canonicalizeStoredPhrase} from '@app/api/utils/PhraseBlocklistNormalization';
|
||||
import {getSameIpDecisionKey} from '@fluxer/ip_utils/src/IpAddress';
|
||||
|
||||
export interface AdminAuditLog {
|
||||
logId: bigint;
|
||||
adminUserId: UserID;
|
||||
targetType: string;
|
||||
targetId: bigint;
|
||||
action: string;
|
||||
auditLogReason: string | null;
|
||||
metadata: Map<string, string>;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export type BannedIpKind = 'permanent' | 'temporary_24h';
|
||||
|
||||
export interface BannedIpEntry {
|
||||
ip: string;
|
||||
kind: BannedIpKind;
|
||||
reason: string | null;
|
||||
expiresAt: Date | null;
|
||||
createdAt: Date | null;
|
||||
}
|
||||
|
||||
const FETCH_AUDIT_LOG_BY_ID_QUERY = AdminAuditLogs.select({
|
||||
where: AdminAuditLogs.where.eq('log_id'),
|
||||
});
|
||||
@@ -84,7 +104,7 @@ function canonicalizeBannedIpEntry(value: string): string {
|
||||
return parseIpBanEntry(value)?.canonical ?? value;
|
||||
}
|
||||
|
||||
export class AdminRepository implements IAdminRepository {
|
||||
export class AdminRepository {
|
||||
async createAuditLog(log: AdminAuditLogRow): Promise<AdminAuditLog> {
|
||||
await upsertOne(AdminAuditLogs.insert(log));
|
||||
return this.mapRowToAuditLog(log);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ApiContext} from '@app/api/ApiContext';
|
||||
import type {IAdminRepository} from '@app/api/admin/IAdminRepository';
|
||||
import type {AdminRepository} from '@app/api/admin/AdminRepository';
|
||||
import {AdminApplicationService} from '@app/api/admin/services/AdminApplicationService';
|
||||
import {AdminAssetPurgeService} from '@app/api/admin/services/AdminAssetPurgeService';
|
||||
import {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
@@ -18,9 +18,9 @@ import {AdminUserService} from '@app/api/admin/services/AdminUserService';
|
||||
import {AdminVoiceService} from '@app/api/admin/services/AdminVoiceService';
|
||||
import {ReporterResolutionNotifier} from '@app/api/admin/services/ReporterResolutionNotifier';
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {ChannelService} from '@app/api/channel/services/ChannelService';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {GuildService} from '@app/api/guild/services/GuildService';
|
||||
import type {IDiscriminatorService} from '@app/api/infrastructure/DiscriminatorService';
|
||||
import type {EntityAssetService} from '@app/api/infrastructure/EntityAssetService';
|
||||
@@ -29,10 +29,10 @@ import type {IStorageService} from '@app/api/infrastructure/IStorageService';
|
||||
import type {KVBulkMessageDeletionQueueService} from '@app/api/infrastructure/KVBulkMessageDeletionQueueService';
|
||||
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import type {InviteRepository} from '@app/api/invite/InviteRepository';
|
||||
import type {IJobLedgerRepository} from '@app/api/jobs/IJobLedgerRepository';
|
||||
import {JobAdminService} from '@app/api/jobs/JobAdminService';
|
||||
import type {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository';
|
||||
import {getGuildDiscoveryRepository, getKVAccountDeletionQueue} from '@app/api/middleware/ServiceSingletons';
|
||||
import type {IApplicationRepository} from '@app/api/oauth/repositories/IApplicationRepository';
|
||||
import type {ApplicationRepository} from '@app/api/oauth/repositories/ApplicationRepository';
|
||||
import type {ReportService} from '@app/api/report/ReportService';
|
||||
import type {StoreEntitlementService} from '@app/api/store_billing/StoreEntitlementService';
|
||||
import type {UserService} from '@app/api/user/services/UserService';
|
||||
@@ -59,9 +59,9 @@ export class AdminService {
|
||||
|
||||
constructor(
|
||||
private readonly apiContext: ApiContext,
|
||||
private readonly guildRepository: IGuildRepositoryAggregate,
|
||||
private readonly channelRepository: IChannelRepository,
|
||||
private readonly adminRepository: IAdminRepository,
|
||||
private readonly guildRepository: GuildRepository,
|
||||
private readonly channelRepository: ChannelRepository,
|
||||
private readonly adminRepository: AdminRepository,
|
||||
private readonly inviteRepository: InviteRepository,
|
||||
private readonly discriminatorService: IDiscriminatorService,
|
||||
private readonly guildService: GuildService,
|
||||
@@ -74,16 +74,16 @@ export class AdminService {
|
||||
private readonly reportService: ReportService,
|
||||
private readonly voiceRepository: VoiceRepository,
|
||||
private readonly bulkMessageDeletionQueue: KVBulkMessageDeletionQueueService,
|
||||
private readonly applicationRepository: IApplicationRepository,
|
||||
private readonly applicationRepository: ApplicationRepository,
|
||||
private readonly stripe: Stripe | null = null,
|
||||
private readonly jobLedger: IJobLedgerRepository,
|
||||
private readonly jobLedger: JobLedgerRepository,
|
||||
private readonly storeEntitlementService: StoreEntitlementService,
|
||||
) {
|
||||
const {users, gateway, worker, snowflake} = this.apiContext.services;
|
||||
this.auditService = new AdminAuditService(this.adminRepository, snowflake, {
|
||||
userRepository: users,
|
||||
guildRepository: this.guildRepository,
|
||||
channelRepository: this.channelRepository,
|
||||
channelRepository: this.channelRepository.channelData,
|
||||
});
|
||||
this.banManagementService = new AdminBanManagementService({
|
||||
apiContext: this.apiContext,
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import type {
|
||||
AdminAuditLogRow,
|
||||
BannedAvatarHashRow,
|
||||
BannedFileShaRow,
|
||||
BannedProfileSubstringRow,
|
||||
BannedProfileSubstringScope,
|
||||
BannedUrlDomainRow,
|
||||
BannedUrlRow,
|
||||
} from '@app/api/database/types/AdminArchiveTypes';
|
||||
|
||||
export interface AdminAuditLog {
|
||||
logId: bigint;
|
||||
adminUserId: UserID;
|
||||
targetType: string;
|
||||
targetId: bigint;
|
||||
action: string;
|
||||
auditLogReason: string | null;
|
||||
metadata: Map<string, string>;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export type BannedIpKind = 'permanent' | 'temporary_24h';
|
||||
|
||||
export interface BannedIpEntry {
|
||||
ip: string;
|
||||
kind: BannedIpKind;
|
||||
reason: string | null;
|
||||
expiresAt: Date | null;
|
||||
createdAt: Date | null;
|
||||
}
|
||||
|
||||
export abstract class IAdminRepository {
|
||||
abstract createAuditLog(log: AdminAuditLogRow): Promise<AdminAuditLog>;
|
||||
|
||||
abstract getAuditLog(logId: bigint): Promise<AdminAuditLog | null>;
|
||||
|
||||
abstract listAuditLogsByIds(logIds: Array<bigint>): Promise<Array<AdminAuditLog>>;
|
||||
|
||||
abstract listAllAuditLogsPaginated(limit: number, lastLogId?: bigint): Promise<Array<AdminAuditLog>>;
|
||||
|
||||
abstract isIpBanned(ip: string): Promise<boolean>;
|
||||
|
||||
abstract banIp(ip: string, ttlSeconds?: number | null): Promise<void>;
|
||||
|
||||
abstract banIpTemp(ip: string, ttlSeconds: number): Promise<void>;
|
||||
|
||||
abstract unbanIp(ip: string): Promise<void>;
|
||||
|
||||
abstract loadAllBannedIpEntries(): Promise<Array<BannedIpEntry>>;
|
||||
|
||||
abstract isEmailBanned(email: string): Promise<boolean>;
|
||||
|
||||
abstract banEmail(email: string): Promise<void>;
|
||||
|
||||
abstract unbanEmail(email: string): Promise<void>;
|
||||
|
||||
abstract loadAllBannedEmails(): Promise<Array<string>>;
|
||||
|
||||
abstract banPhrase(phrase: string): Promise<void>;
|
||||
|
||||
abstract unbanPhrase(phrase: string): Promise<void>;
|
||||
|
||||
abstract loadAllBannedPhrases(): Promise<Array<string>>;
|
||||
|
||||
abstract loadAllBannedIps(): Promise<Set<string>>;
|
||||
|
||||
abstract banUrl(row: BannedUrlRow): Promise<void>;
|
||||
|
||||
abstract unbanUrl(url: string): Promise<void>;
|
||||
|
||||
abstract loadAllBannedUrls(): Promise<Array<BannedUrlRow>>;
|
||||
|
||||
abstract banUrlDomain(row: BannedUrlDomainRow): Promise<void>;
|
||||
|
||||
abstract unbanUrlDomain(domain: string): Promise<void>;
|
||||
|
||||
abstract loadAllBannedUrlDomains(): Promise<Array<BannedUrlDomainRow>>;
|
||||
|
||||
abstract banFileSha(row: BannedFileShaRow): Promise<void>;
|
||||
|
||||
abstract unbanFileSha(sha256Hex: string): Promise<void>;
|
||||
|
||||
abstract unbanFeedFileSha(sha256Hex: string): Promise<boolean>;
|
||||
|
||||
abstract loadAllBannedFileShas(): Promise<Array<BannedFileShaRow>>;
|
||||
|
||||
abstract banAvatarHash(row: BannedAvatarHashRow): Promise<void>;
|
||||
|
||||
abstract unbanAvatarHash(hashShort: string): Promise<void>;
|
||||
|
||||
abstract loadAllBannedAvatarHashes(): Promise<Array<BannedAvatarHashRow>>;
|
||||
|
||||
abstract banProfileSubstring(row: BannedProfileSubstringRow): Promise<void>;
|
||||
|
||||
abstract unbanProfileSubstring(scope: BannedProfileSubstringScope, substring: string): Promise<void>;
|
||||
|
||||
abstract loadAllBannedProfileSubstrings(): Promise<Array<BannedProfileSubstringRow>>;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {RateLimitConfigs} from '@app/api/RateLimitConfig';
|
||||
import type {HonoApp} from '@app/api/types/HonoEnv';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {Validator} from '@app/api/Validator';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {DiscoveryApplicationStatus, DiscoveryCategoryLabels} from '@fluxer/constants/src/DiscoveryConstants';
|
||||
@@ -65,13 +65,13 @@ interface GuildEnrichment {
|
||||
async function enrichGuilds(
|
||||
rows: ReadonlyArray<GuildDiscoveryRow>,
|
||||
guildService: GuildService,
|
||||
userRepository: IUserRepository,
|
||||
userRepository: UserRepository,
|
||||
): Promise<Map<string, GuildEnrichment>> {
|
||||
const map = new Map<string, GuildEnrichment>();
|
||||
const guilds = await Promise.all(
|
||||
rows.map(async (row) => {
|
||||
try {
|
||||
return await guildService.data.getGuildSystem(row.guild_id);
|
||||
return await guildService.data.operations.getGuildSystem(row.guild_id);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function ThreadAdminController(app: HonoApp) {
|
||||
}),
|
||||
async (ctx) => {
|
||||
const channelId = createChannelID(ctx.req.valid('param').channel_id);
|
||||
const thread = await ctx.get('channelRepository').findUnique(channelId);
|
||||
const thread = await ctx.get('channelRepository').channelData.findUnique(channelId);
|
||||
if (!thread) throw new UnknownChannelError();
|
||||
if (!thread.isThread()) throw new InvalidChannelTypeError();
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {
|
||||
CreateAdminApiKeyData,
|
||||
IAdminApiKeyRepository,
|
||||
UpdateAdminApiKeyData,
|
||||
} from '@app/api/admin/repositories/IAdminApiKeyRepository';
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import {
|
||||
BatchBuilder,
|
||||
@@ -19,12 +14,23 @@ import {AdminApiKey} from '@app/api/models/AdminApiKey';
|
||||
import {AdminApiKeys, AdminApiKeysByCreator} from '@app/api/Tables';
|
||||
import {hashPassword} from '@app/api/utils/PasswordUtils';
|
||||
|
||||
export interface CreateAdminApiKeyData {
|
||||
name: string;
|
||||
expiresAt: Date | null;
|
||||
acls: Set<string>;
|
||||
}
|
||||
|
||||
export interface UpdateAdminApiKeyData {
|
||||
name?: string;
|
||||
acls?: Set<string>;
|
||||
}
|
||||
|
||||
function computeTtlSeconds(expiresAt: Date): number {
|
||||
const diffSeconds = Math.floor((expiresAt.getTime() - Date.now()) / 1000);
|
||||
return Math.max(diffSeconds, 1);
|
||||
}
|
||||
|
||||
export class AdminApiKeyRepository implements IAdminApiKeyRepository {
|
||||
export class AdminApiKeyRepository {
|
||||
async create(data: CreateAdminApiKeyData, createdBy: UserID, keyId: bigint, rawKey: string): Promise<AdminApiKey> {
|
||||
const keyHash = await hashPassword(rawKey);
|
||||
const createdAt = new Date();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import type {AdminApiKey} from '@app/api/models/AdminApiKey';
|
||||
|
||||
export interface CreateAdminApiKeyData {
|
||||
name: string;
|
||||
expiresAt: Date | null;
|
||||
acls: Set<string>;
|
||||
}
|
||||
|
||||
export interface UpdateAdminApiKeyData {
|
||||
name?: string;
|
||||
acls?: Set<string>;
|
||||
}
|
||||
|
||||
export interface IAdminApiKeyRepository {
|
||||
create(data: CreateAdminApiKeyData, createdBy: UserID, keyId: bigint, rawKey: string): Promise<AdminApiKey>;
|
||||
findById(keyId: bigint): Promise<AdminApiKey | null>;
|
||||
update(apiKey: AdminApiKey, data: UpdateAdminApiKeyData): Promise<AdminApiKey>;
|
||||
listByCreator(createdBy: UserID): Promise<Array<AdminApiKey>>;
|
||||
updateLastUsed(keyId: bigint, expiresAt: Date | null): Promise<void>;
|
||||
revoke(keyId: bigint, createdBy: UserID): Promise<void>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {randomInt} from 'node:crypto';
|
||||
import type {IAdminApiKeyRepository} from '@app/api/admin/repositories/IAdminApiKeyRepository';
|
||||
import type {AdminApiKeyRepository} from '@app/api/admin/repositories/AdminApiKeyRepository';
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {AdminApiKey} from '@app/api/models/AdminApiKey';
|
||||
@@ -39,7 +39,7 @@ export interface AdminApiKeyView {
|
||||
|
||||
export class AdminApiKeyService {
|
||||
constructor(
|
||||
private readonly adminApiKeyRepository: IAdminApiKeyRepository,
|
||||
private readonly adminApiKeyRepository: AdminApiKeyRepository,
|
||||
private readonly snowflakeService: ISnowflakeService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import type {ApiContext} from '@app/api/ApiContext';
|
||||
import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
import {type ApplicationID, createApplicationID, createUserID, type GuildID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {Application} from '@app/api/models/Application';
|
||||
import type {IApplicationRepository} from '@app/api/oauth/repositories/IApplicationRepository';
|
||||
import type {ApplicationRepository} from '@app/api/oauth/repositories/ApplicationRepository';
|
||||
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
|
||||
import {UnknownApplicationError} from '@fluxer/errors/src/domains/oauth/UnknownApplicationError';
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
@@ -18,9 +18,9 @@ import type {
|
||||
|
||||
interface AdminApplicationServiceDeps {
|
||||
apiContext: ApiContext;
|
||||
applicationRepository: IApplicationRepository;
|
||||
applicationRepository: ApplicationRepository;
|
||||
auditService: AdminAuditService;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
guildRepository: GuildRepository;
|
||||
}
|
||||
|
||||
interface UserDisplay {
|
||||
|
||||
@@ -4,10 +4,10 @@ import {AdminArchive} from '@app/api/admin/models/AdminArchiveModel';
|
||||
import type {AdminArchiveRepository} from '@app/api/admin/repositories/AdminArchiveRepository';
|
||||
import type {GuildID, UserID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {IStorageService} from '@app/api/infrastructure/IStorageService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {WorkerTaskName} from '@app/api/worker/WorkerLaneConfig';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
|
||||
@@ -35,8 +35,8 @@ interface ListArchivesParams {
|
||||
export class AdminArchiveService {
|
||||
constructor(
|
||||
private readonly adminArchiveRepository: AdminArchiveRepository,
|
||||
private readonly userRepository: IUserRepository,
|
||||
private readonly guildRepository: IGuildRepositoryAggregate,
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly guildRepository: GuildRepository,
|
||||
private readonly storageService: IStorageService,
|
||||
private readonly snowflakeService: ISnowflakeService,
|
||||
private readonly workerService: IWorkerService<WorkerTaskName>,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
import {createEmojiID, createStickerID, type GuildID, type UserID} from '@app/api/BrandedTypes';
|
||||
import {mapGuildEmojiToResponse, mapGuildStickerToResponse} from '@app/api/guild/GuildModel';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import {ExpressionAssetPurger} from '@app/api/guild/services/content/ExpressionAssetPurger';
|
||||
import type {IAssetDeletionQueue} from '@app/api/infrastructure/IAssetDeletionQueue';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
|
||||
interface AdminAssetPurgeServiceDeps {
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
guildRepository: GuildRepository;
|
||||
gatewayService: IGatewayService;
|
||||
assetDeletionQueue: IAssetDeletionQueue;
|
||||
auditService: AdminAuditService;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {ADMIN_AUDIT_READ_ACTIONS, getAdminAuditAccess} from '@app/api/admin/AdminAuditActions';
|
||||
import type {AdminAuditLog, IAdminRepository} from '@app/api/admin/IAdminRepository';
|
||||
import type {AdminAuditLog, AdminRepository} from '@app/api/admin/AdminRepository';
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import {createChannelID, createGuildID, createUserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {ChannelDataRepository} from '@app/api/channel/repositories/ChannelDataRepository';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Guild} from '@app/api/models/Guild';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {getAuditLogSearchService} from '@app/api/SearchFactory';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {AuditLogSearchFilters} from '@fluxer/schema/src/contracts/search/SearchDocumentTypes';
|
||||
import type {
|
||||
AdminAuditAccess,
|
||||
@@ -34,7 +34,7 @@ interface CreateAdminAuditLogParams {
|
||||
|
||||
export class AdminAuditService {
|
||||
constructor(
|
||||
private readonly adminRepository: IAdminRepository,
|
||||
private readonly adminRepository: AdminRepository,
|
||||
private readonly snowflakeService: ISnowflakeService,
|
||||
private readonly enrichmentDeps: AuditLogEnrichmentDeps = {},
|
||||
) {}
|
||||
@@ -341,9 +341,9 @@ function accessFilters(access: AdminAuditAccess | undefined): AuditLogSearchFilt
|
||||
}
|
||||
|
||||
interface AuditLogEnrichmentDeps {
|
||||
userRepository?: Pick<IUserRepository, 'findUnique'>;
|
||||
guildRepository?: Pick<IGuildRepositoryAggregate, 'findUnique'>;
|
||||
channelRepository?: Pick<IChannelRepository, 'findUnique'>;
|
||||
userRepository?: Pick<UserRepository, 'findUnique'>;
|
||||
guildRepository?: Pick<GuildRepository, 'findUnique'>;
|
||||
channelRepository?: Pick<ChannelDataRepository, 'findUnique'>;
|
||||
}
|
||||
|
||||
interface AuditLogEnrichment {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ApiContext} from '@app/api/ApiContext';
|
||||
import type {IAdminRepository} from '@app/api/admin/IAdminRepository';
|
||||
import type {AdminRepository} from '@app/api/admin/AdminRepository';
|
||||
import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
import {createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import {isIpBanExempt} from '@app/api/ban/IpBanExemptions';
|
||||
@@ -36,7 +36,7 @@ import type {AdminBlocklistListType} from '@fluxer/schema/src/domains/admin/Admi
|
||||
|
||||
interface AdminBanManagementServiceDeps {
|
||||
apiContext: ApiContext;
|
||||
adminRepository: IAdminRepository;
|
||||
adminRepository: AdminRepository;
|
||||
auditService: AdminAuditService;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import type {GiftCodeDurationType, GiftCodeRow} from '@app/api/database/types/PaymentTypes';
|
||||
import {mapGiftCodeDurationToMonths} from '@app/api/models/GiftCode';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import * as RandomUtils from '@app/api/utils/RandomUtils';
|
||||
|
||||
const CODE_LENGTH = 32;
|
||||
@@ -19,7 +19,7 @@ interface GenerateGiftCodesOptions {
|
||||
}
|
||||
|
||||
export class AdminCodeGenerationService {
|
||||
constructor(private readonly userRepository: IUserRepository) {}
|
||||
constructor(private readonly userRepository: UserRepository) {}
|
||||
|
||||
async generateGiftCodes(options: GenerateGiftCodesOptions): Promise<Array<string>> {
|
||||
const {count, durationType, durationQuantity, createdByUserId} = this.validateOptions(options);
|
||||
|
||||
@@ -8,27 +8,27 @@ import {AdminGuildUpdatePropagator} from '@app/api/admin/services/guild/AdminGui
|
||||
import {AdminGuildUpdateService} from '@app/api/admin/services/guild/AdminGuildUpdateService';
|
||||
import {AdminGuildVanityService} from '@app/api/admin/services/guild/AdminGuildVanityService';
|
||||
import {createGuildID, createUserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {IGuildDiscoveryRepository} from '@app/api/guild/repositories/GuildDiscoveryRepository';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {GuildDiscoveryRepository} from '@app/api/guild/repositories/GuildDiscoveryRepository';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {GuildService} from '@app/api/guild/services/GuildService';
|
||||
import type {EntityAssetService} from '@app/api/infrastructure/EntityAssetService';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {InviteRepository} from '@app/api/invite/InviteRepository';
|
||||
import {createRequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {ListGuildAuditLogsRequest} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas';
|
||||
|
||||
interface AdminGuildServiceDeps {
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
userRepository: IUserRepository;
|
||||
channelRepository: IChannelRepository;
|
||||
guildRepository: GuildRepository;
|
||||
userRepository: UserRepository;
|
||||
channelRepository: ChannelRepository;
|
||||
inviteRepository: InviteRepository;
|
||||
guildService: GuildService;
|
||||
gatewayService: IGatewayService;
|
||||
entityAssetService: EntityAssetService;
|
||||
auditService: AdminAuditService;
|
||||
discoveryRepository: IGuildDiscoveryRepository;
|
||||
discoveryRepository: GuildDiscoveryRepository;
|
||||
}
|
||||
|
||||
export class AdminGuildService {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService'
|
||||
import type {AdminMessageShredService} from '@app/api/admin/services/AdminMessageShredService';
|
||||
import type {ChannelID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createUserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {
|
||||
DeleteAllUserMessagesRequest,
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
} from '@fluxer/schema/src/domains/admin/AdminMessageSchemas';
|
||||
|
||||
interface AdminMessageDeletionServiceDeps {
|
||||
channelRepository: IChannelRepository;
|
||||
channelRepository: ChannelRepository;
|
||||
messageShredService: AdminMessageShredService;
|
||||
auditService: AdminAuditService;
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export class AdminMessageDeletionService {
|
||||
let messageCount = 0;
|
||||
let channelCount = 0;
|
||||
while (true) {
|
||||
const messageRefs = await this.deps.channelRepository.listMessagesByAuthor(
|
||||
const messageRefs = await this.deps.channelRepository.messages.listMessagesByAuthor(
|
||||
authorId,
|
||||
FETCH_CHUNK_SIZE,
|
||||
lastMessageId,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type MessageID,
|
||||
type UserID,
|
||||
} 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 {
|
||||
enqueueCrosspostFamilyPurgeFromCopies,
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
messageResponseAccessForGuild,
|
||||
} from '@app/api/channel/services/message/MessageResponseDataService';
|
||||
import {resolveNsfwScopeChannel} from '@app/api/channel/utils/ThreadNsfwScope';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import {getPurgeQueue, getStorageService} from '@app/api/middleware/ServiceSingletons';
|
||||
import {getMessageSearchService} from '@app/api/SearchFactory';
|
||||
import {deleteMessageSearchDocuments} from '@app/api/search/MessageSearchIndexCleanup';
|
||||
@@ -44,8 +44,8 @@ import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageRe
|
||||
|
||||
interface AdminMessageServiceDeps {
|
||||
apiContext: ApiContext;
|
||||
channelRepository: IChannelRepository;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
channelRepository: ChannelRepository;
|
||||
guildRepository: GuildRepository;
|
||||
auditService: AdminAuditService;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export class AdminMessageService {
|
||||
async lookupMessageByAttachment(data: LookupMessageByAttachmentRequest) {
|
||||
const channelId = createChannelID(data.channel_id);
|
||||
const attachmentId = createAttachmentID(data.attachment_id);
|
||||
const messageId = await this.deps.channelRepository.lookupAttachmentByChannelAndFilename(
|
||||
const messageId = await this.deps.channelRepository.messages.lookupAttachmentByChannelAndFilename(
|
||||
channelId,
|
||||
attachmentId,
|
||||
data.filename,
|
||||
@@ -113,11 +113,11 @@ export class AdminMessageService {
|
||||
const {gateway: gatewayService, worker: workerService} = this.deps.apiContext.services;
|
||||
const channelId = createChannelID(data.channel_id);
|
||||
const messageId = createMessageID(data.message_id);
|
||||
const channel = await channelRepository.findUnique(channelId);
|
||||
const message = await channelRepository.getMessage(channelId, messageId);
|
||||
const channel = await channelRepository.channelData.findUnique(channelId);
|
||||
const message = await channelRepository.messages.getMessage(channelId, messageId);
|
||||
if (message) {
|
||||
await purgeMessageAttachments(message, getStorageService(), getPurgeQueue());
|
||||
await channelRepository.deleteMessage(
|
||||
await channelRepository.messages.deleteMessage(
|
||||
channelId,
|
||||
messageId,
|
||||
message.authorId || createUserID(0n),
|
||||
@@ -227,7 +227,7 @@ export class AdminMessageService {
|
||||
}
|
||||
|
||||
private async getMessageResponseAccessForAdmin(channelId: ChannelID): Promise<MessageResponseAccessContext> {
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
|
||||
const access = channel ? messageResponseAccessForChannel(channel) : messageResponseAccessForGuild(null);
|
||||
return {...access, includeHidden: true};
|
||||
}
|
||||
@@ -283,7 +283,7 @@ export class AdminMessageService {
|
||||
|
||||
private async getChannelNsfwContext(channelId: ChannelID): Promise<ChannelNsfwContext> {
|
||||
const {channelRepository, guildRepository} = this.deps;
|
||||
const channel = await channelRepository.findUnique(channelId);
|
||||
const channel = await channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel) {
|
||||
return {
|
||||
channelNsfw: null,
|
||||
@@ -304,7 +304,7 @@ export class AdminMessageService {
|
||||
}
|
||||
const [guild, scope] = await Promise.all([
|
||||
guildRepository.findUnique(channel.guildId),
|
||||
resolveNsfwScopeChannel(channel, (id) => channelRepository.findUnique(id)),
|
||||
resolveNsfwScopeChannel(channel, (id) => channelRepository.channelData.findUnique(id)),
|
||||
]);
|
||||
return {
|
||||
channelNsfw: scope.isNsfw,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type UserID,
|
||||
} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {makeAttachmentCdnKey} from '@app/api/channel/services/message/MessageHelpers';
|
||||
import {
|
||||
createMessageResponseDataService,
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from '@app/api/channel/services/message/MessageResponseDataService';
|
||||
import {resolveNsfwScopeChannel} from '@app/api/channel/utils/ThreadNsfwScope';
|
||||
import type {MessageAttachment} from '@app/api/database/types/MessageTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {IStorageService} from '@app/api/infrastructure/IStorageService';
|
||||
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
@@ -33,7 +33,7 @@ import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import {createRequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import {describeReportFlowAnswers} from '@app/api/report/flows/ReportFlowRegistry';
|
||||
import {findReportReason, listReportReasons} from '@app/api/report/flows/ReportReasonCatalog';
|
||||
import type {IARMessageContext, IARSubmission} from '@app/api/report/IReportRepository';
|
||||
import type {IARMessageContext, IARSubmission} from '@app/api/report/ReportModels';
|
||||
import type {ReportService} from '@app/api/report/ReportService';
|
||||
import {getReportSearchService} from '@app/api/SearchFactory';
|
||||
import {isHiddenPartial} from '@app/api/user/ProfileVisibility';
|
||||
@@ -57,8 +57,8 @@ import {seconds} from 'itty-time';
|
||||
interface AdminReportServiceDeps {
|
||||
apiContext: ApiContext;
|
||||
reportService: ReportService;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
channelRepository: IChannelRepository;
|
||||
guildRepository: GuildRepository;
|
||||
channelRepository: ChannelRepository;
|
||||
storageService: IStorageService;
|
||||
auditService: AdminAuditService;
|
||||
userCacheService: UserCacheService;
|
||||
@@ -541,7 +541,7 @@ export class AdminReportService {
|
||||
}
|
||||
|
||||
private async getMessageResponseAccessForAdmin(channelId: ChannelID): Promise<MessageResponseAccessContext> {
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
|
||||
const access = channel ? messageResponseAccessForChannel(channel) : messageResponseAccessForGuild(null);
|
||||
return {...access, includeHidden: true};
|
||||
}
|
||||
@@ -628,9 +628,9 @@ export class AdminReportService {
|
||||
if (reportLookupCache.channelNsfwByChannelId.has(channelIdString)) {
|
||||
return reportLookupCache.channelNsfwByChannelId.get(channelIdString) ?? null;
|
||||
}
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
|
||||
const scope = channel
|
||||
? await resolveNsfwScopeChannel(channel, (id) => this.deps.channelRepository.findUnique(id))
|
||||
? await resolveNsfwScopeChannel(channel, (id) => this.deps.channelRepository.channelData.findUnique(id))
|
||||
: null;
|
||||
const channelNsfw = scope?.isNsfw ?? null;
|
||||
reportLookupCache.channelNsfwByChannelId.set(channelIdString, channelNsfw);
|
||||
@@ -665,7 +665,7 @@ export class AdminReportService {
|
||||
if (!channelId) {
|
||||
return null;
|
||||
}
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel?.guildId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService'
|
||||
import {createGuildID, createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import {isSyntheticUserId} from '@app/api/constants/Core';
|
||||
import {channelThreadsEnabled} from '@app/api/experiment/ChannelThreadsGate';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import {getGuildSearchService, getUserSearchService} from '@app/api/SearchFactory';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
@@ -36,7 +36,7 @@ interface RefreshSearchIndexJobPayload extends WorkerJobPayload {
|
||||
|
||||
interface AdminSearchServiceDeps {
|
||||
apiContext: ApiContext;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
guildRepository: GuildRepository;
|
||||
auditService: AdminAuditService;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {KVAccountDeletionQueueService} from '@app/api/infrastructure/KVAcco
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {OAuth2TokenRepository} from '@app/api/oauth/repositories/OAuth2TokenRepository';
|
||||
import {type IARSubmission, ReportStatus} from '@app/api/report/IReportRepository';
|
||||
import {type IARSubmission, ReportStatus} from '@app/api/report/ReportModels';
|
||||
import type {ReportService} from '@app/api/report/ReportService';
|
||||
import {getReportSearchService} from '@app/api/SearchFactory';
|
||||
import type {StoreEntitlementService} from '@app/api/store_billing/StoreEntitlementService';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {mapUserToAdminResponse} from '@app/api/admin/models/UserTypes';
|
||||
import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
import type {AdminUserUpdatePropagator} from '@app/api/admin/services/AdminUserUpdatePropagator';
|
||||
import {createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import {GuildMemberSearchIndexService} from '@app/api/guild/services/member/GuildMemberSearchIndexService';
|
||||
import type {IDiscriminatorService} from '@app/api/infrastructure/DiscriminatorService';
|
||||
import type {EntityAssetService, PreparedAssetUpload} from '@app/api/infrastructure/EntityAssetService';
|
||||
@@ -37,7 +37,7 @@ interface AdminUserProfileServiceDeps {
|
||||
entityAssetService: EntityAssetService;
|
||||
auditService: AdminAuditService;
|
||||
updatePropagator: AdminUserUpdatePropagator;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
guildRepository: GuildRepository;
|
||||
}
|
||||
|
||||
export class AdminUserProfileService {
|
||||
|
||||
@@ -12,8 +12,8 @@ import {AdminUserSecurityService} from '@app/api/admin/services/AdminUserSecurit
|
||||
import {AdminUserUpdatePropagator} from '@app/api/admin/services/AdminUserUpdatePropagator';
|
||||
import {ReporterResolutionNotifier} from '@app/api/admin/services/ReporterResolutionNotifier';
|
||||
import {createChannelID, createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {IDiscriminatorService} from '@app/api/infrastructure/DiscriminatorService';
|
||||
import type {EntityAssetService} from '@app/api/infrastructure/EntityAssetService';
|
||||
import type {KVAccountDeletionQueueService} from '@app/api/infrastructure/KVAccountDeletionQueueService';
|
||||
@@ -23,7 +23,7 @@ import {getBillingRepository} from '@app/api/middleware/ServiceRegistry';
|
||||
import {OAuth2TokenRepository} from '@app/api/oauth/repositories/OAuth2TokenRepository';
|
||||
import type {ReportService} from '@app/api/report/ReportService';
|
||||
import type {StoreEntitlementService} from '@app/api/store_billing/StoreEntitlementService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {UserContactChangeLogService} from '@app/api/user/services/UserContactChangeLogService';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
@@ -38,8 +38,8 @@ import type Stripe from 'stripe';
|
||||
|
||||
interface AdminUserServiceDeps {
|
||||
apiContext: ApiContext;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
channelRepository: IChannelRepository;
|
||||
guildRepository: GuildRepository;
|
||||
channelRepository: ChannelRepository;
|
||||
discriminatorService: IDiscriminatorService;
|
||||
entityAssetService: EntityAssetService;
|
||||
auditService: AdminAuditService;
|
||||
@@ -62,9 +62,9 @@ export class AdminUserService {
|
||||
private readonly updatePropagator: AdminUserUpdatePropagator;
|
||||
private readonly contactChangeLogService: UserContactChangeLogService;
|
||||
private readonly auditService: AdminAuditService;
|
||||
private readonly userRepository: IUserRepository;
|
||||
private readonly guildRepository: IGuildRepositoryAggregate;
|
||||
private readonly channelRepository: IChannelRepository;
|
||||
private readonly userRepository: UserRepository;
|
||||
private readonly guildRepository: GuildRepository;
|
||||
private readonly channelRepository: ChannelRepository;
|
||||
private readonly bulkMessageDeletionQueue: KVBulkMessageDeletionQueueService;
|
||||
private readonly cacheService: ICacheService;
|
||||
|
||||
@@ -132,7 +132,9 @@ export class AdminUserService {
|
||||
beforeChannelId: data.before ? createChannelID(data.before) : undefined,
|
||||
afterChannelId: data.after ? createChannelID(data.after) : undefined,
|
||||
});
|
||||
const channelModels = await this.channelRepository.listChannels(channels.map((channel) => channel.channelId));
|
||||
const channelModels = await this.channelRepository.channelData.listChannels(
|
||||
channels.map((channel) => channel.channelId),
|
||||
);
|
||||
const channelModelById = new Map(
|
||||
channelModels.filter((c): c is NonNullable<typeof c> => c != null).map((c) => [c.id, c]),
|
||||
);
|
||||
@@ -234,7 +236,9 @@ export class AdminUserService {
|
||||
if (groupDmSummaries.length === 0) {
|
||||
return {channels: []};
|
||||
}
|
||||
const channelModels = await this.channelRepository.listChannels(groupDmSummaries.map((s) => s.channelId));
|
||||
const channelModels = await this.channelRepository.channelData.listChannels(
|
||||
groupDmSummaries.map((s) => s.channelId),
|
||||
);
|
||||
const allUserIds = new Set<UserID>();
|
||||
for (const model of channelModels) {
|
||||
if (model) {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
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 {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {BaseUserUpdatePropagator} from '@app/api/user/services/BaseUserUpdatePropagator';
|
||||
import {propagatePartialUserChange} from '@app/api/user/services/PartialUserChangePropagation';
|
||||
import {hasPartialUserFieldsChanged} from '@app/api/user/UserMappers';
|
||||
|
||||
interface AdminUserUpdatePropagatorDeps {
|
||||
userCacheService: UserCacheService;
|
||||
userRepository: IUserRepository;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
userRepository: UserRepository;
|
||||
guildRepository: GuildRepository;
|
||||
gatewayService: IGatewayService;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {getInstanceProductName} from '@app/api/instance/ProductName';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import {createRequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {IARSubmission} from '@app/api/report/IReportRepository';
|
||||
import type {IARSubmission} from '@app/api/report/ReportModels';
|
||||
import type {UserChannelService} from '@app/api/user/services/UserChannelService';
|
||||
import {UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {getEmailTemplate} from '@pkgs/email/src/email_i18n/EmailI18n';
|
||||
|
||||
@@ -4,11 +4,11 @@ import {mapGuildsToAdminResponse} from '@app/api/admin/models/GuildTypes';
|
||||
import type {GuildID} from '@app/api/BrandedTypes';
|
||||
import {createGuildID, createUserID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {mapGuildFeatures} from '@app/api/guild/GuildFeatureUtils';
|
||||
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 {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {MEDIA_PROXY_ICON_SIZE_DEFAULT} from '@fluxer/constants/src/MediaProxyAssetSizes';
|
||||
import type {MediaProxyImageSize} from '@fluxer/constants/src/MediaProxyImageSizes';
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
@@ -22,9 +22,9 @@ import type {ListGuildEmojisResponse, ListGuildStickersResponse} from '@fluxer/s
|
||||
const ADMIN_STICKER_MEDIA_RUNG: MediaProxyImageSize = 320;
|
||||
|
||||
interface AdminGuildLookupServiceDeps {
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
userRepository: IUserRepository;
|
||||
channelRepository: IChannelRepository;
|
||||
guildRepository: GuildRepository;
|
||||
userRepository: UserRepository;
|
||||
channelRepository: ChannelRepository;
|
||||
gatewayService: IGatewayService;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class AdminGuildLookupService {
|
||||
return {guild: null};
|
||||
}
|
||||
const [channels, roles, ownerUser] = await Promise.all([
|
||||
channelRepository.listGuildChannels(guildId, 'maintenance'),
|
||||
channelRepository.channelData.listGuildChannels(guildId, 'maintenance'),
|
||||
guildRepository.listRoles(guildId),
|
||||
userRepository.findUnique(guild.ownerId),
|
||||
]);
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService';
|
||||
import {createGuildID, type GuildID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {GuildService} from '@app/api/guild/services/GuildService';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
|
||||
import type {SuccessResponse} from '@fluxer/schema/src/domains/common/CommonParamSchemas';
|
||||
|
||||
interface AdminGuildManagementServiceDeps {
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
guildRepository: GuildRepository;
|
||||
gatewayService: IGatewayService;
|
||||
guildService: GuildService;
|
||||
auditService: AdminAuditService;
|
||||
@@ -63,7 +63,7 @@ export class AdminGuildManagementService {
|
||||
async deleteGuild(guildIdRaw: bigint, adminUserId: UserID, auditLogReason: string | null): Promise<SuccessResponse> {
|
||||
const {guildService, auditService} = this.deps;
|
||||
const guildId = createGuildID(guildIdRaw);
|
||||
await guildService.data.deleteGuildForAdmin(guildId, auditLogReason);
|
||||
await guildService.data.operations.deleteGuildById(guildId);
|
||||
await auditService.createAuditLog({
|
||||
adminUserId,
|
||||
targetType: 'guild',
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService'
|
||||
import {createGuildID, createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {GuildService} from '@app/api/guild/services/GuildService';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {JoinSourceTypes} from '@fluxer/constants/src/GuildConstants';
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
import type {
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
import type {SuccessResponse} from '@fluxer/schema/src/domains/common/CommonParamSchemas';
|
||||
|
||||
interface AdminGuildMembershipServiceDeps {
|
||||
userRepository: IUserRepository;
|
||||
userRepository: UserRepository;
|
||||
guildService: GuildService;
|
||||
auditService: AdminAuditService;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {GuildID, UserID} from '@app/api/BrandedTypes';
|
||||
import {mapGuildToGuildResponse} from '@app/api/guild/GuildModel';
|
||||
import type {IGuildDiscoveryRepository} from '@app/api/guild/repositories/GuildDiscoveryRepository';
|
||||
import type {GuildDiscoveryRepository} from '@app/api/guild/repositories/GuildDiscoveryRepository';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {Guild} from '@app/api/models/Guild';
|
||||
@@ -13,7 +13,7 @@ import {GuildFeatures} from '@fluxer/constants/src/GuildConstants';
|
||||
|
||||
interface AdminGuildUpdatePropagatorDeps {
|
||||
gatewayService: IGatewayService;
|
||||
discoveryRepository: IGuildDiscoveryRepository;
|
||||
discoveryRepository: GuildDiscoveryRepository;
|
||||
}
|
||||
|
||||
interface AdminGuildUpdateDispatchOptions {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {AdminAuditService} from '@app/api/admin/services/AdminAuditService'
|
||||
import type {AdminGuildUpdatePropagator} from '@app/api/admin/services/guild/AdminGuildUpdatePropagator';
|
||||
import {createGuildID, createUserID, type GuildID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {GuildRow} from '@app/api/database/types/GuildTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {EntityAssetService, PreparedAssetUpload} from '@app/api/infrastructure/EntityAssetService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas';
|
||||
|
||||
interface AdminGuildUpdateServiceDeps {
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
guildRepository: GuildRepository;
|
||||
entityAssetService: EntityAssetService;
|
||||
auditService: AdminAuditService;
|
||||
updatePropagator: AdminGuildUpdatePropagator;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type UserID,
|
||||
vanityCodeToInviteCode,
|
||||
} from '@app/api/BrandedTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {InviteRepository} from '@app/api/invite/InviteRepository';
|
||||
import {InviteTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
@@ -19,7 +19,7 @@ import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildEr
|
||||
import type {UpdateGuildVanityRequest} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas';
|
||||
|
||||
interface AdminGuildVanityServiceDeps {
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
guildRepository: GuildRepository;
|
||||
inviteRepository: InviteRepository;
|
||||
auditService: AdminAuditService;
|
||||
updatePropagator: AdminGuildUpdatePropagator;
|
||||
|
||||
@@ -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 {decodeHeaderUtf8} from '@app/api/middleware/AuditLogMiddleware';
|
||||
import {getAdminRepository} from '@app/api/middleware/ServiceSingletons';
|
||||
|
||||
@@ -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 {createTestAccount, setUserACLs, type TestAccount} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {ipBanCache} from '@app/api/middleware/IpBanMiddleware';
|
||||
import {getAdminRepository} from '@app/api/middleware/ServiceSingletons';
|
||||
|
||||
@@ -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 {
|
||||
createAttachmentID,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createGuildID, createUserID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {CrosspostWorkerService} from '@app/api/channel/services/message/CrosspostPropagation';
|
||||
import {UserMessageDeletionService} from '@app/api/channel/services/message/UserMessageDeletionService';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import {GuildMemberOperationsService} from '@app/api/guild/services/member/GuildMemberOperationsService';
|
||||
import type {IPurgeQueue} from '@app/api/infrastructure/CachePurgeQueue';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
@@ -34,7 +34,7 @@ function unusableDependency(): object {
|
||||
function createMessageDeletionService(): UserMessageDeletionService {
|
||||
const unusable = unusableDependency();
|
||||
return new UserMessageDeletionService({
|
||||
channelRepository: unusable as IChannelRepository,
|
||||
channelRepository: unusable as ChannelRepository,
|
||||
gatewayService: unusable as IGatewayService,
|
||||
storageService: unusable as IStorageService,
|
||||
purgeQueue: unusable as IPurgeQueue,
|
||||
@@ -50,7 +50,7 @@ function createGuildMemberOperationsService(): GuildMemberOperationsService {
|
||||
async getMember() {
|
||||
return null;
|
||||
},
|
||||
} as unknown as IGuildRepositoryAggregate;
|
||||
} as unknown as GuildRepository;
|
||||
const unusable = unusableDependency();
|
||||
return new GuildMemberOperationsService(
|
||||
guildRepository,
|
||||
|
||||
@@ -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 type {TestAccount} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {createTestAccount, setUserACLs} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {setCassandraQueryExecutorForTesting} from '@app/api/database/CassandraQueryExecution';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {getAdminAuditAccess} from '@app/api/admin/AdminAuditActions';
|
||||
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 {getAdminRepository} from '@app/api/middleware/ServiceSingletons';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {requireSudoMode} from '@app/api/auth/services/SudoVerificationService';
|
||||
import {Config} from '@app/api/Config';
|
||||
import {
|
||||
onUsernameInstance,
|
||||
RequireEmailAccountIdentity,
|
||||
@@ -16,8 +15,8 @@ import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware';
|
||||
import {SudoModeMiddleware} from '@app/api/middleware/SudoModeMiddleware';
|
||||
import {RateLimitConfigs} from '@app/api/RateLimitConfig';
|
||||
import type {HonoApp} from '@app/api/types/HonoEnv';
|
||||
import {requireRequestClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {Validator} from '@app/api/Validator';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import {
|
||||
AuthLoginResponse,
|
||||
AuthorizeIpRequest,
|
||||
@@ -646,10 +645,7 @@ export function AuthController(app: HonoApp) {
|
||||
'Retrieve device and location information about a pending handoff request. Non-destructive – the code remains valid after this call.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
const response = await ctx.get('authRequestService').getHandoffInfo({
|
||||
code: ctx.req.valid('param').code,
|
||||
clientIp,
|
||||
@@ -672,10 +668,7 @@ export function AuthController(app: HonoApp) {
|
||||
'Complete the handoff process and authenticate on the target device using the handoff code. With the deep_link return method, responds with the deep link that hands the one-time grant back to the initiating app. Otherwise responds with no content.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
const response = await ctx.get('authRequestService').completeHandoff({
|
||||
data: ctx.req.valid('json'),
|
||||
clientIp,
|
||||
@@ -700,10 +693,7 @@ export function AuthController(app: HonoApp) {
|
||||
'Decline a handoff request after looking it up. The initiating device sees the denied status and the code can no longer be approved.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
await ctx.get('authRequestService').denyHandoff({code: ctx.req.valid('param').code, clientIp});
|
||||
return ctx.body(null, 204);
|
||||
},
|
||||
@@ -723,10 +713,7 @@ export function AuthController(app: HonoApp) {
|
||||
'Check the status of a handoff session. Returns whether the handoff has been completed or is still pending.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
const response = await ctx.get('authRequestService').getHandoffStatus({
|
||||
code: ctx.req.valid('param').code,
|
||||
clientIp,
|
||||
@@ -750,10 +737,7 @@ export function AuthController(app: HonoApp) {
|
||||
'Check the status of a handoff session using the poll secret from initiation. Returns the authentication token once the handoff is complete and the presented secret matches.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
const response = await ctx.get('authRequestService').getHandoffStatus({
|
||||
code: ctx.req.valid('param').code,
|
||||
clientIp,
|
||||
|
||||
@@ -8,9 +8,9 @@ import {createEmailRevertToken} from '@app/api/BrandedTypes';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {enqueueStripeCustomerEmailSync} from '@app/api/stripe/StripeCustomer';
|
||||
import {mapUserToPrivateResponse} from '@app/api/user/UserMappers';
|
||||
import {requireConfiguredClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
|
||||
interface IssueEmailRevertTokenParams {
|
||||
user: User;
|
||||
@@ -80,13 +80,7 @@ export async function revertEmailChange(
|
||||
await users.deleteAllWebAuthnCredentials(user.id);
|
||||
await users.deleteAllAuthorizedIps(user.id);
|
||||
await AuthSession.terminateAllUserSessions(ctx, user.id);
|
||||
await users.createAuthorizedIp(
|
||||
user.id,
|
||||
requireClientIp(request, {
|
||||
trustClientIpHeader: config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: config.proxy.client_ip_header,
|
||||
}),
|
||||
);
|
||||
await users.createAuthorizedIp(user.id, requireConfiguredClientIp(request, config.proxy));
|
||||
await gateway.dispatchPresence({
|
||||
userId: updatedUser.id,
|
||||
event: 'USER_UPDATE',
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {User} from '@app/api/models/User';
|
||||
import {findPersonByLoginHandle, type ParsedLoginHandle, parseLoginHandle} from '@app/api/user/UniqueUsernames';
|
||||
import {lookupGeoip} from '@app/api/utils/IpUtils';
|
||||
import {createRateLimitError} from '@app/api/utils/RateLimitUtils';
|
||||
import {requireConfiguredClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {AccountIdentityModes} from '@fluxer/constants/src/AccountIdentityConstants';
|
||||
import {UserAuthenticatorTypes, UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {type ValidationErrorCode, ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
@@ -42,7 +43,6 @@ import {RegistrationPendingApprovalError} from '@fluxer/errors/src/domains/auth/
|
||||
import {RegistrationRejectedError} from '@fluxer/errors/src/domains/auth/RegistrationRejectedError';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import {getSameIpDecisionKey} from '@fluxer/ip_utils/src/IpAddress';
|
||||
import type {LoginRequest} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
import {EmailType} from '@fluxer/schema/src/primitives/UserValidators';
|
||||
@@ -240,10 +240,7 @@ export async function login(
|
||||
}
|
||||
};
|
||||
await enforceRateLimits(identifier.rateLimits);
|
||||
const clientIp = requireClientIp(request, {
|
||||
trustClientIpHeader: config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireConfiguredClientIp(request, config.proxy);
|
||||
const sourceKey = getSameIpDecisionKey(clientIp) ?? clientIp;
|
||||
await enforceRateLimits([
|
||||
...identifier.sourceRateLimits(sourceKey),
|
||||
|
||||
@@ -17,12 +17,12 @@ import {EXTERNAL_RESPONSE_LIMITS} from '@app/api/utils/ExternalResponseLimits';
|
||||
import * as FetchUtils from '@app/api/utils/FetchUtils';
|
||||
import {hashPassword as hashPasswordUtil, verifyPassword as verifyPasswordUtil} from '@app/api/utils/PasswordUtils';
|
||||
import {createRateLimitError} from '@app/api/utils/RateLimitUtils';
|
||||
import {requireConfiguredClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {AccountIdentityModes} from '@fluxer/constants/src/AccountIdentityConstants';
|
||||
import {FLUXER_USER_AGENT} from '@fluxer/constants/src/Core';
|
||||
import {UserAuthenticatorTypes, UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import {getSameIpDecisionKey} from '@fluxer/ip_utils/src/IpAddress';
|
||||
import type {ForgotPasswordRequest, ResetPasswordRequest} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
import {ms} from 'itty-time';
|
||||
@@ -197,10 +197,7 @@ export async function isPasswordPwned(_ctx: ApiContext, password: string): Promi
|
||||
|
||||
export async function forgotPassword(ctx: ApiContext, {data, request}: ForgotPasswordParams): Promise<void> {
|
||||
const {users, email, rateLimit, emailDnsValidation, config} = ctx.services;
|
||||
const clientIp = requireClientIp(request, {
|
||||
trustClientIpHeader: config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireConfiguredClientIp(request, config.proxy);
|
||||
const ipRateLimit = await rateLimit.checkLimit({
|
||||
identifier: `password_reset:ip:${getSameIpDecisionKey(clientIp) ?? clientIp}`,
|
||||
maxAttempts: 20,
|
||||
|
||||
@@ -19,6 +19,7 @@ import {getInstanceConfigRepository} from '@app/api/middleware/ServiceSingletons
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {findPersonByLoginHandle, parseLoginHandle} from '@app/api/user/UniqueUsernames';
|
||||
import {createRateLimitError} from '@app/api/utils/RateLimitUtils';
|
||||
import {requireConfiguredClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {AccountIdentityModes} from '@fluxer/constants/src/AccountIdentityConstants';
|
||||
import {
|
||||
generateRecoveryKey,
|
||||
@@ -30,7 +31,6 @@ import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {RegistrationPendingApprovalError} from '@fluxer/errors/src/domains/auth/RegistrationPendingApprovalError';
|
||||
import {RegistrationRejectedError} from '@fluxer/errors/src/domains/auth/RegistrationRejectedError';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import {getSameIpDecisionKey} from '@fluxer/ip_utils/src/IpAddress';
|
||||
import type {RecoverAccountRequest} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
import type {
|
||||
@@ -101,10 +101,7 @@ function invalidRecoveryKeyError(): InputValidationError {
|
||||
|
||||
async function checkRecoverRateLimits(ctx: ApiContext, username: string | null, request: Request): Promise<void> {
|
||||
const {rateLimit, config} = ctx.services;
|
||||
const clientIp = requireClientIp(request, {
|
||||
trustClientIpHeader: config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireConfiguredClientIp(request, config.proxy);
|
||||
const sourceKey = getSameIpDecisionKey(clientIp) ?? clientIp;
|
||||
const ipRateLimit = await rateLimit.checkLimit({
|
||||
identifier: `recover:ip:${sourceKey}`,
|
||||
|
||||
@@ -42,6 +42,7 @@ import * as AgeUtils from '@app/api/utils/AgeUtils';
|
||||
import {extractEmailDomain} from '@app/api/utils/EmailDomainUtils';
|
||||
import {lookupGeoip} from '@app/api/utils/IpUtils';
|
||||
import {createRateLimitError} from '@app/api/utils/RateLimitUtils';
|
||||
import {requireConfiguredClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {generateRandomUsername} from '@app/api/utils/UsernameGenerator';
|
||||
import {deriveUsernameFromDisplayName} from '@app/api/utils/UsernameSuggestionUtils';
|
||||
import {inputValidationErrorFromZodIssues} from '@app/api/Validator';
|
||||
@@ -54,11 +55,10 @@ import {RegistrationClosedError} from '@fluxer/errors/src/domains/auth/Registrat
|
||||
import {RegistrationUrlInvalidError} from '@fluxer/errors/src/domains/auth/RegistrationUrlInvalidError';
|
||||
import {ContentBlockedError} from '@fluxer/errors/src/domains/content/ContentBlockedError';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import {getSameIpDecisionKey, getSubnet} from '@fluxer/ip_utils/src/IpAddress';
|
||||
import type {RegisterRequest} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
import {UsernameType} from '@fluxer/schema/src/primitives/UserValidators';
|
||||
import {parseAcceptLanguage} from '@pkgs/locale/src/LocaleService';
|
||||
import {parseAcceptLanguage} from '@pkgs/locale/src/resolution/AcceptLanguageNegotiation';
|
||||
import {types} from 'cassandra-driver';
|
||||
import {ms} from 'itty-time';
|
||||
|
||||
@@ -143,10 +143,7 @@ async function registerAccount(
|
||||
}
|
||||
const now = new Date();
|
||||
const registrationAccess = await resolveRegistrationAccess(instanceConfigRepository, data.registration_url_code);
|
||||
const clientIp = requireClientIp(request, {
|
||||
trustClientIpHeader: config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireConfiguredClientIp(request, config.proxy);
|
||||
const geoipResult = await lookupGeoip(clientIp);
|
||||
const countryCode = geoipResult.countryCode;
|
||||
const collectDateOfBirth = appPublicConfig.registration.collect_date_of_birth;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {getKVAccountDeletionQueue} from '@app/api/middleware/ServiceSingletons';
|
||||
import type {AuthSession} from '@app/api/models/AuthSession';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {lookupGeoip} from '@app/api/utils/IpUtils';
|
||||
import {requireConfiguredClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {isFluxerNativeUserAgent, parseReportedClientOs} from '@app/api/utils/SessionClientIdentity';
|
||||
import {BotUserAuthSessionCreationDeniedError} from '@fluxer/errors/src/domains/auth/BotUserAuthSessionCreationDeniedError';
|
||||
import {RegistrationPendingApprovalError} from '@fluxer/errors/src/domains/auth/RegistrationPendingApprovalError';
|
||||
@@ -21,7 +22,6 @@ import {RegistrationRejectedError} from '@fluxer/errors/src/domains/auth/Registr
|
||||
import {SessionTokenMismatchError} from '@fluxer/errors/src/domains/auth/SessionTokenMismatchError';
|
||||
import {InvalidTokenError} from '@fluxer/errors/src/domains/core/InvalidTokenError';
|
||||
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import type {AuthSessionResponse} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
|
||||
export interface SessionOrigin {
|
||||
@@ -67,10 +67,7 @@ interface CreateAdditionalAuthSessionFromTokenParams {
|
||||
|
||||
export function resolveSessionOrigin(ctx: ApiContext, request: Request): SessionOrigin {
|
||||
const {config} = ctx.services;
|
||||
const ip = requireClientIp(request, {
|
||||
trustClientIpHeader: config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: config.proxy.client_ip_header,
|
||||
});
|
||||
const ip = requireConfiguredClientIp(request, config.proxy);
|
||||
const userAgent = request.headers.get('user-agent')?.trim() || null;
|
||||
const clientOs = isFluxerNativeUserAgent(userAgent)
|
||||
? parseReportedClientOs(request.headers.get('x-fluxer-client-properties'))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {IUserRepositoryAggregate} from '@app/api/user/repositories/IUserRepositoryAggregate';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {runAllInOrder} from '@app/api/utils/ConcurrencyUtils';
|
||||
|
||||
interface SessionRevocationDependencies {
|
||||
users: IUserRepositoryAggregate;
|
||||
users: UserRepository;
|
||||
gateway: IGatewayService;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import {createAuthHarness, createTestAccount, loginAccount, type TestAccount} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {
|
||||
createAuthHarness,
|
||||
createTestAccount,
|
||||
createTotpSecret,
|
||||
loginAccount,
|
||||
loginWithTotp,
|
||||
type TestAccount,
|
||||
totpCodeNow,
|
||||
} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import type {ApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {TotpGenerator} from '@app/api/utils/TotpGenerator';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import type {AuthSessionResponse} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
@@ -25,65 +31,6 @@ interface OAuth2ApplicationResponse {
|
||||
};
|
||||
}
|
||||
|
||||
function generateTotpSecret(): string {
|
||||
const buffer = randomBytes(20);
|
||||
const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
let result = '';
|
||||
for (let i = 0; i < buffer.length; i += 5) {
|
||||
const bytes = [buffer[i]!, buffer[i + 1]!, buffer[i + 2]!, buffer[i + 3]!, buffer[i + 4]!];
|
||||
const n = (bytes[0]! << 24) | (bytes[1]! << 16) | (bytes[2]! << 8) | bytes[3]!;
|
||||
const indices = [
|
||||
(n >> 3) & 0x1f,
|
||||
((n >> 11) | ((bytes[4]! << 4) & 0xf)) & 0x1f,
|
||||
((n >> 19) | ((bytes[4]! << 2) & 0x3c)) & 0x1f,
|
||||
(bytes[4]! >> 1) & 0x1f,
|
||||
];
|
||||
result += base32Chars[indices[0]!];
|
||||
result += base32Chars[indices[1]!];
|
||||
result += base32Chars[indices[2]!];
|
||||
result += base32Chars[indices[3]!];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function generateTotpCode(secret: string): Promise<string> {
|
||||
const totp = new TotpGenerator(secret);
|
||||
const codes = await totp.generateTotp();
|
||||
return codes[0]!;
|
||||
}
|
||||
|
||||
async function loginWithTotp(harness: ApiTestHarness, account: TestAccount, secret: string): Promise<TestAccount> {
|
||||
const loginResp = await createBuilderWithoutAuth<
|
||||
| {
|
||||
mfa: true;
|
||||
ticket: string;
|
||||
}
|
||||
| {
|
||||
mfa: false;
|
||||
token: string;
|
||||
}
|
||||
>(harness)
|
||||
.post('/auth/login')
|
||||
.body({
|
||||
email: account.email,
|
||||
password: account.password,
|
||||
})
|
||||
.execute();
|
||||
if (!loginResp.mfa) {
|
||||
throw new Error('Expected MFA login');
|
||||
}
|
||||
const mfaLoginResp = await createBuilderWithoutAuth<{
|
||||
token: string;
|
||||
}>(harness)
|
||||
.post('/auth/login/mfa/totp')
|
||||
.body({
|
||||
ticket: loginResp.ticket,
|
||||
code: await generateTotpCode(secret),
|
||||
})
|
||||
.execute();
|
||||
return {...account, token: mfaLoginResp.token};
|
||||
}
|
||||
|
||||
async function createOAuth2BotApplication(
|
||||
harness: ApiTestHarness,
|
||||
account: TestAccount,
|
||||
@@ -177,8 +124,8 @@ describe('Auth sudo required operations', () => {
|
||||
});
|
||||
it('requires sudo for disable TOTP MFA', async () => {
|
||||
let account = await createTestAccount(harness);
|
||||
const secret = generateTotpSecret();
|
||||
const code = await generateTotpCode(secret);
|
||||
const secret = createTotpSecret();
|
||||
const code = totpCodeNow(secret);
|
||||
const enableResp = await createBuilder<BackupCodesResponse>(harness, account.token)
|
||||
.post('/users/@me/mfa/totp/enable')
|
||||
.body({
|
||||
@@ -202,15 +149,15 @@ describe('Auth sudo required operations', () => {
|
||||
.body({
|
||||
code: backupCode,
|
||||
mfa_method: 'totp',
|
||||
mfa_code: await generateTotpCode(secret),
|
||||
mfa_code: totpCodeNow(secret),
|
||||
})
|
||||
.expect(204)
|
||||
.execute();
|
||||
});
|
||||
it('requires sudo for enable TOTP MFA', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const secret = generateTotpSecret();
|
||||
const code = await generateTotpCode(secret);
|
||||
const secret = createTotpSecret();
|
||||
const code = totpCodeNow(secret);
|
||||
await createBuilder(harness, account.token)
|
||||
.post('/users/@me/mfa/totp/enable')
|
||||
.body({
|
||||
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
createAuthHarness,
|
||||
createTestAccount,
|
||||
createTotpSecret,
|
||||
type TestAccount,
|
||||
loginWithTotp,
|
||||
totpCodeNow,
|
||||
} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import type {ApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import type {AuthSessionResponse} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
@@ -21,38 +21,6 @@ interface BackupCodesResponse {
|
||||
|
||||
const SUDO_MODE_HEADER = 'X-Fluxer-Sudo-Mode-JWT';
|
||||
|
||||
async function loginWithTotp(harness: ApiTestHarness, account: TestAccount, secret: string): Promise<TestAccount> {
|
||||
const loginResp = await createBuilderWithoutAuth<
|
||||
| {
|
||||
mfa: true;
|
||||
ticket: string;
|
||||
}
|
||||
| {
|
||||
mfa: false;
|
||||
token: string;
|
||||
}
|
||||
>(harness)
|
||||
.post('/auth/login')
|
||||
.body({
|
||||
email: account.email,
|
||||
password: account.password,
|
||||
})
|
||||
.execute();
|
||||
if (!loginResp.mfa) {
|
||||
throw new Error('Expected MFA login');
|
||||
}
|
||||
const mfaLoginResp = await createBuilderWithoutAuth<{
|
||||
token: string;
|
||||
}>(harness)
|
||||
.post('/auth/login/mfa/totp')
|
||||
.body({
|
||||
ticket: loginResp.ticket,
|
||||
code: totpCodeNow(secret),
|
||||
})
|
||||
.execute();
|
||||
return {...account, token: mfaLoginResp.token};
|
||||
}
|
||||
|
||||
describe('Auth sudo TOTP verification', () => {
|
||||
let harness: ApiTestHarness;
|
||||
beforeAll(async () => {
|
||||
|
||||
@@ -260,6 +260,22 @@ export function totpCodeNow(secret: string): string {
|
||||
return generateTotpCode(secret, Date.now());
|
||||
}
|
||||
|
||||
export async function loginWithTotp(
|
||||
harness: ApiTestHarness,
|
||||
account: TestAccount,
|
||||
secret: string,
|
||||
): Promise<TestAccount> {
|
||||
const login = await loginUser(harness, {email: account.email, password: account.password});
|
||||
if (!('mfa' in login)) {
|
||||
throw new Error('Expected MFA login');
|
||||
}
|
||||
const mfaLogin = await createBuilder<{token: string}>(harness, '')
|
||||
.post('/auth/login/mfa/totp')
|
||||
.body({ticket: login.ticket, code: totpCodeNow(secret)})
|
||||
.execute();
|
||||
return {...account, token: mfaLogin.token};
|
||||
}
|
||||
|
||||
export async function seedMfaTicket(
|
||||
harness: ApiTestHarness,
|
||||
ticket: string,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
createTestAccount,
|
||||
createTotpSecret,
|
||||
generateTotpCode,
|
||||
loginWithTotp,
|
||||
type TestAccount,
|
||||
} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {
|
||||
@@ -51,27 +52,6 @@ interface SudoModeRequiredResponse {
|
||||
};
|
||||
}
|
||||
|
||||
async function loginWithTotp(harness: ApiTestHarness, account: TestAccount, secret: string): Promise<TestAccount> {
|
||||
const login = await createBuilderWithoutAuth<LoginMfaResponse>(harness)
|
||||
.post('/auth/login')
|
||||
.body({
|
||||
email: account.email,
|
||||
password: account.password,
|
||||
})
|
||||
.execute();
|
||||
expect(login.mfa).toBe(true);
|
||||
const mfaLogin = await createBuilderWithoutAuth<{
|
||||
token: string;
|
||||
}>(harness)
|
||||
.post('/auth/login/mfa/totp')
|
||||
.body({
|
||||
code: generateTotpCode(secret),
|
||||
ticket: login.ticket,
|
||||
})
|
||||
.execute();
|
||||
return {...account, token: mfaLogin.token};
|
||||
}
|
||||
|
||||
async function setupWebAuthnOnlyUser(
|
||||
harness: ApiTestHarness,
|
||||
account: TestAccount,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import {createAuthHarness, createTestAccount, loginUser, type TestAccount} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {
|
||||
createAuthHarness,
|
||||
createTestAccount,
|
||||
createTotpSecret,
|
||||
loginUser,
|
||||
loginWithTotp,
|
||||
type TestAccount,
|
||||
totpCodeNow,
|
||||
} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import type {ApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {TotpGenerator} from '@app/api/utils/TotpGenerator';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
const SUDO_MODE_HEADER = 'X-Fluxer-Sudo-Mode-JWT';
|
||||
@@ -22,35 +28,8 @@ interface BackupCodesResponse {
|
||||
}>;
|
||||
}
|
||||
|
||||
function generateTotpSecret(): string {
|
||||
const buffer = randomBytes(20);
|
||||
const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
let result = '';
|
||||
for (let i = 0; i < buffer.length; i += 5) {
|
||||
const bytes = [buffer[i]!, buffer[i + 1]!, buffer[i + 2]!, buffer[i + 3]!, buffer[i + 4]!];
|
||||
const n = (bytes[0]! << 24) | (bytes[1]! << 16) | (bytes[2]! << 8) | bytes[3]!;
|
||||
const indices = [
|
||||
(n >> 3) & 0x1f,
|
||||
((n >> 11) | ((bytes[4]! << 4) & 0xf)) & 0x1f,
|
||||
((n >> 19) | ((bytes[4]! << 2) & 0x3c)) & 0x1f,
|
||||
(bytes[4]! >> 1) & 0x1f,
|
||||
];
|
||||
result += base32Chars[indices[0]!];
|
||||
result += base32Chars[indices[1]!];
|
||||
result += base32Chars[indices[2]!];
|
||||
result += base32Chars[indices[3]!];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function generateTotpCode(secret: string): Promise<string> {
|
||||
const totp = new TotpGenerator(secret);
|
||||
const codes = await totp.generateTotp();
|
||||
return codes[0]!;
|
||||
}
|
||||
|
||||
async function enableTotpForAccount(harness: ApiTestHarness, account: TestAccount, secret: string): Promise<void> {
|
||||
const code = await generateTotpCode(secret);
|
||||
const code = totpCodeNow(secret);
|
||||
await createBuilder(harness, account.token)
|
||||
.post('/users/@me/mfa/totp/enable')
|
||||
.body({
|
||||
@@ -61,29 +40,12 @@ async function enableTotpForAccount(harness: ApiTestHarness, account: TestAccoun
|
||||
.execute();
|
||||
}
|
||||
|
||||
async function loginWithTotp(harness: ApiTestHarness, account: TestAccount, secret: string): Promise<TestAccount> {
|
||||
const login = await loginUser(harness, {email: account.email, password: account.password});
|
||||
if (!('mfa' in login)) {
|
||||
throw new Error('Expected MFA login');
|
||||
}
|
||||
const mfaLoginResp = await createBuilderWithoutAuth<{
|
||||
token: string;
|
||||
}>(harness)
|
||||
.post('/auth/login/mfa/totp')
|
||||
.body({
|
||||
ticket: login.ticket,
|
||||
code: await generateTotpCode(secret),
|
||||
})
|
||||
.execute();
|
||||
return {...account, token: mfaLoginResp.token};
|
||||
}
|
||||
|
||||
async function getSudoTokenViaMfa(harness: ApiTestHarness, token: string, secret: string): Promise<string> {
|
||||
const {response} = await createBuilder<BackupCodesResponse>(harness, token)
|
||||
.post('/users/@me/mfa/backup-codes')
|
||||
.body({
|
||||
mfa_method: 'totp',
|
||||
mfa_code: await generateTotpCode(secret),
|
||||
mfa_code: totpCodeNow(secret),
|
||||
regenerate: false,
|
||||
})
|
||||
.executeWithResponse();
|
||||
@@ -108,7 +70,7 @@ describe('Sudo mode negative cases', () => {
|
||||
describe('invalid sudo token rejected', () => {
|
||||
it('rejects malformed sudo token', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const secret = generateTotpSecret();
|
||||
const secret = createTotpSecret();
|
||||
await enableTotpForAccount(harness, account, secret);
|
||||
const loggedIn = await loginWithTotp(harness, account, secret);
|
||||
const invalidTokens = [
|
||||
@@ -128,7 +90,7 @@ describe('Sudo mode negative cases', () => {
|
||||
});
|
||||
it('rejects empty sudo token header', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const secret = generateTotpSecret();
|
||||
const secret = createTotpSecret();
|
||||
await enableTotpForAccount(harness, account, secret);
|
||||
const loggedIn = await loginWithTotp(harness, account, secret);
|
||||
const {json: errResp} = await createBuilder<ErrorResponse>(harness, loggedIn.token)
|
||||
@@ -166,7 +128,7 @@ describe('Sudo mode negative cases', () => {
|
||||
describe('wrong MFA code rejected', () => {
|
||||
it('rejects incorrect TOTP codes', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const secret = generateTotpSecret();
|
||||
const secret = createTotpSecret();
|
||||
await enableTotpForAccount(harness, account, secret);
|
||||
const loggedIn = await loginWithTotp(harness, account, secret);
|
||||
const wrongCodes = ['000000', '123456', '999999', '12345', '1234567', 'abcdef'];
|
||||
@@ -185,12 +147,12 @@ describe('Sudo mode negative cases', () => {
|
||||
describe('sudo token for wrong user rejected', () => {
|
||||
it('rejects sudo token from different user', async () => {
|
||||
const account1 = await createTestAccount(harness);
|
||||
const secret1 = generateTotpSecret();
|
||||
const secret1 = createTotpSecret();
|
||||
await enableTotpForAccount(harness, account1, secret1);
|
||||
const loggedIn1 = await loginWithTotp(harness, account1, secret1);
|
||||
const user1SudoToken = await getSudoTokenViaMfa(harness, loggedIn1.token, secret1);
|
||||
const account2 = await createTestAccount(harness);
|
||||
const secret2 = generateTotpSecret();
|
||||
const secret2 = createTotpSecret();
|
||||
await enableTotpForAccount(harness, account2, secret2);
|
||||
const loggedIn2 = await loginWithTotp(harness, account2, secret2);
|
||||
const {json: errResp} = await createBuilder<ErrorResponse>(harness, loggedIn2.token)
|
||||
@@ -243,7 +205,7 @@ describe('Sudo mode negative cases', () => {
|
||||
describe('MFA registration without token fails', () => {
|
||||
it('WebAuthn registration options do not issue sudo token', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const secret = generateTotpSecret();
|
||||
const secret = createTotpSecret();
|
||||
await enableTotpForAccount(harness, account, secret);
|
||||
const loggedIn = await loginWithTotp(harness, account, secret);
|
||||
const sudoToken = await getSudoTokenViaMfa(harness, loggedIn.token, secret);
|
||||
@@ -259,7 +221,7 @@ describe('Sudo mode negative cases', () => {
|
||||
describe('existing MFA token allows skipping MFA', () => {
|
||||
it('sudo token from MFA allows skipping MFA verification on sensitive endpoint', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const secret = generateTotpSecret();
|
||||
const secret = createTotpSecret();
|
||||
await enableTotpForAccount(harness, account, secret);
|
||||
const loggedIn = await loginWithTotp(harness, account, secret);
|
||||
const sudoToken = await getSudoTokenViaMfa(harness, loggedIn.token, secret);
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
createHash,
|
||||
createHmac,
|
||||
createPrivateKey,
|
||||
createPublicKey,
|
||||
createSign,
|
||||
generateKeyPairSync,
|
||||
randomBytes,
|
||||
} from 'node:crypto';
|
||||
import {createHash, createPrivateKey, createPublicKey, createSign, generateKeyPairSync, randomBytes} from 'node:crypto';
|
||||
import type {ApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {decode as base32Decode, encode as base32Encode} from 'hi-base32';
|
||||
|
||||
export {createTotpSecret, generateTotpCode} from '@app/api/auth/tests/AuthTestUtils';
|
||||
|
||||
export interface WebAuthnDevice {
|
||||
privateKey: string;
|
||||
@@ -103,30 +96,6 @@ interface WebAuthnAuthenticationResponse {
|
||||
response: AuthenticatorAssertionResponse;
|
||||
}
|
||||
|
||||
export function createTotpSecret(): string {
|
||||
const buf = randomBytes(20);
|
||||
return base32Encode(buf).replace(/=/g, '');
|
||||
}
|
||||
|
||||
export function generateTotpCode(secret: string, time = Date.now()): string {
|
||||
const key = Buffer.from(base32Decode.asBytes(secret.toUpperCase()));
|
||||
const epoch = Math.floor(time / 1000);
|
||||
const counter = Math.floor(epoch / 30);
|
||||
const counterBuf = Buffer.alloc(8);
|
||||
counterBuf.writeBigUInt64BE(BigInt(counter));
|
||||
const hmac = createHmac('sha1', key);
|
||||
hmac.update(counterBuf);
|
||||
const hash = hmac.digest();
|
||||
const offset = hash[hash.length - 1] & 0x0f;
|
||||
const binary =
|
||||
((hash[offset] & 0x7f) << 24) |
|
||||
((hash[offset + 1] & 0xff) << 16) |
|
||||
((hash[offset + 2] & 0xff) << 8) |
|
||||
(hash[offset + 3] & 0xff);
|
||||
const otp = binary % 1000000;
|
||||
return otp.toString().padStart(6, '0');
|
||||
}
|
||||
|
||||
function resolveWebAuthnOrigin(): {
|
||||
rpId: string;
|
||||
origin: string;
|
||||
|
||||
@@ -1,124 +1,24 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {AttachmentID, ChannelID, GuildID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import {ChannelRepository as NewChannelRepository} from '@app/api/channel/repositories/ChannelRepository';
|
||||
import type {GuildChannelListMode} from '@app/api/channel/repositories/IChannelDataRepository';
|
||||
import type {UpsertMessageOptions} from '@app/api/channel/repositories/IMessageRepository';
|
||||
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
|
||||
import type {MessageRow} from '@app/api/database/types/MessageTypes';
|
||||
import {ChannelDataRepository} from '@app/api/channel/repositories/ChannelDataRepository';
|
||||
import {CrosspostedMessageRepository} from '@app/api/channel/repositories/CrosspostedMessageRepository';
|
||||
import {MessageInteractionRepository} from '@app/api/channel/repositories/MessageInteractionRepository';
|
||||
import {MessageRepository} from '@app/api/channel/repositories/MessageRepository';
|
||||
import {ThreadRepository} from '@app/api/channel/repositories/ThreadRepository';
|
||||
import {enqueueRepairThreadIndexes} from '@app/api/channel/threads/ThreadJobs';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
|
||||
export class ChannelRepository extends IChannelRepository {
|
||||
private repository: NewChannelRepository;
|
||||
export class ChannelRepository {
|
||||
readonly channelData: ChannelDataRepository;
|
||||
readonly messages: MessageRepository;
|
||||
readonly messageInteractions: MessageInteractionRepository;
|
||||
readonly threads: ThreadRepository;
|
||||
readonly crossposts = new CrosspostedMessageRepository();
|
||||
|
||||
constructor(requestCache?: RequestCache) {
|
||||
super();
|
||||
this.repository = new NewChannelRepository(requestCache);
|
||||
}
|
||||
|
||||
get channelData() {
|
||||
return this.repository.channelData;
|
||||
}
|
||||
|
||||
get messages() {
|
||||
return this.repository.messages;
|
||||
}
|
||||
|
||||
get messageInteractions() {
|
||||
return this.repository.messageInteractions;
|
||||
}
|
||||
|
||||
get threads() {
|
||||
return this.repository.threads;
|
||||
}
|
||||
|
||||
get crossposts() {
|
||||
return this.repository.crossposts;
|
||||
}
|
||||
|
||||
async findUnique(channelId: ChannelID): Promise<Channel | null> {
|
||||
return this.repository.channelData.findUnique(channelId);
|
||||
}
|
||||
|
||||
async upsert(data: ChannelRow): Promise<Channel> {
|
||||
return this.repository.channelData.upsert(data);
|
||||
}
|
||||
|
||||
async delete(channelId: ChannelID, guildId?: GuildID, type?: number): Promise<void> {
|
||||
return this.repository.channelData.delete(channelId, guildId, type);
|
||||
}
|
||||
|
||||
async listMessages(
|
||||
channelId: ChannelID,
|
||||
beforeMessageId?: MessageID,
|
||||
limit?: number,
|
||||
afterMessageId?: MessageID,
|
||||
): Promise<Array<Message>> {
|
||||
return this.repository.messages.listMessages(channelId, beforeMessageId, limit, afterMessageId);
|
||||
}
|
||||
|
||||
async getMessage(channelId: ChannelID, messageId: MessageID): Promise<Message | null> {
|
||||
return this.repository.messages.getMessage(channelId, messageId);
|
||||
}
|
||||
|
||||
async upsertMessage(data: MessageRow, oldData?: MessageRow | null, opts?: UpsertMessageOptions): Promise<Message> {
|
||||
return this.repository.messages.upsertMessage(data, oldData, opts);
|
||||
}
|
||||
|
||||
async deleteMessage(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
authorId: UserID,
|
||||
pinnedTimestamp?: Date,
|
||||
): Promise<void> {
|
||||
return this.repository.messages.deleteMessage(channelId, messageId, authorId, pinnedTimestamp);
|
||||
}
|
||||
|
||||
async bulkDeleteMessages(channelId: ChannelID, messageIds: Array<MessageID>): Promise<void> {
|
||||
return this.repository.messages.bulkDeleteMessages(channelId, messageIds);
|
||||
}
|
||||
|
||||
async listGuildChannels(guildId: GuildID, mode: GuildChannelListMode): Promise<Array<Channel>> {
|
||||
return this.repository.channelData.listGuildChannels(guildId, mode);
|
||||
}
|
||||
|
||||
async listChannels(channelIds: Array<ChannelID>): Promise<Array<Channel>> {
|
||||
return this.repository.channelData.listChannels(channelIds);
|
||||
}
|
||||
|
||||
async lookupAttachmentByChannelAndFilename(
|
||||
channelId: ChannelID,
|
||||
attachmentId: AttachmentID,
|
||||
filename: string,
|
||||
): Promise<MessageID | null> {
|
||||
return this.repository.messages.lookupAttachmentByChannelAndFilename(channelId, attachmentId, filename);
|
||||
}
|
||||
|
||||
async listMessagesByAuthor(
|
||||
authorId: UserID,
|
||||
limit?: number,
|
||||
lastMessageId?: MessageID,
|
||||
): Promise<
|
||||
Array<{
|
||||
channelId: ChannelID;
|
||||
messageId: MessageID;
|
||||
}>
|
||||
> {
|
||||
return this.repository.messages.listMessagesByAuthor(authorId, limit, lastMessageId);
|
||||
}
|
||||
|
||||
async anonymizeMessage(channelId: ChannelID, messageId: MessageID, newAuthorId: UserID): Promise<void> {
|
||||
return this.repository.messages.anonymizeMessage(channelId, messageId, newAuthorId);
|
||||
}
|
||||
|
||||
async deleteAllChannelMessages(channelId: ChannelID): Promise<void> {
|
||||
return this.repository.messages.deleteAllChannelMessages(channelId);
|
||||
}
|
||||
|
||||
async updateEmbeds(message: Message): Promise<void> {
|
||||
return this.repository.messages.updateEmbeds(message);
|
||||
this.channelData = new ChannelDataRepository(requestCache);
|
||||
this.messages = new MessageRepository(this.channelData);
|
||||
this.messageInteractions = new MessageInteractionRepository(this.messages);
|
||||
this.threads = new ThreadRepository(this.channelData, this.messages, enqueueRepairThreadIndexes);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {AttachmentID, ChannelID, GuildID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {GuildChannelListMode} from '@app/api/channel/repositories/IChannelDataRepository';
|
||||
import {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {UpsertMessageOptions} from '@app/api/channel/repositories/IMessageRepository';
|
||||
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
|
||||
import type {MessageRow} from '@app/api/database/types/MessageTypes';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
|
||||
export abstract class IChannelRepository extends IChannelRepositoryAggregate {
|
||||
abstract findUnique(channelId: ChannelID): Promise<Channel | null>;
|
||||
|
||||
abstract upsert(data: ChannelRow): Promise<Channel>;
|
||||
|
||||
abstract delete(channelId: ChannelID, guildId?: GuildID, type?: number): Promise<void>;
|
||||
|
||||
abstract listGuildChannels(guildId: GuildID, mode: GuildChannelListMode): Promise<Array<Channel>>;
|
||||
|
||||
abstract listChannels(channelIds: Array<ChannelID>): Promise<Array<Channel>>;
|
||||
|
||||
abstract listMessages(
|
||||
channelId: ChannelID,
|
||||
beforeMessageId?: MessageID,
|
||||
limit?: number,
|
||||
afterMessageId?: MessageID,
|
||||
): Promise<Array<Message>>;
|
||||
|
||||
abstract getMessage(channelId: ChannelID, messageId: MessageID): Promise<Message | null>;
|
||||
|
||||
abstract upsertMessage(data: MessageRow, oldData?: MessageRow | null, opts?: UpsertMessageOptions): Promise<Message>;
|
||||
|
||||
abstract deleteMessage(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
authorId: UserID,
|
||||
pinnedTimestamp?: Date,
|
||||
): Promise<void>;
|
||||
|
||||
abstract bulkDeleteMessages(channelId: ChannelID, messageIds: Array<MessageID>): Promise<void>;
|
||||
|
||||
abstract lookupAttachmentByChannelAndFilename(
|
||||
channelId: ChannelID,
|
||||
attachmentId: AttachmentID,
|
||||
filename: string,
|
||||
): Promise<MessageID | null>;
|
||||
|
||||
abstract listMessagesByAuthor(
|
||||
authorId: UserID,
|
||||
limit?: number,
|
||||
lastMessageId?: MessageID,
|
||||
): Promise<
|
||||
Array<{
|
||||
channelId: ChannelID;
|
||||
messageId: MessageID;
|
||||
}>
|
||||
>;
|
||||
|
||||
abstract anonymizeMessage(channelId: ChannelID, messageId: MessageID, newAuthorId: UserID): Promise<void>;
|
||||
|
||||
abstract deleteAllChannelMessages(channelId: ChannelID): Promise<void>;
|
||||
|
||||
abstract updateEmbeds(message: Message): Promise<void>;
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {requireSudoMode} from '@app/api/auth/services/SudoVerificationService';
|
||||
import {createAttachmentID, createChannelID, createMessageID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {MessageRequest, MessageUpdateRequest} from '@app/api/channel/MessageTypes';
|
||||
import {normalizeMessageRequestPayload} from '@app/api/channel/services/message/MessageRequestCompatibility';
|
||||
import {parseMultipartMessageData} from '@app/api/channel/services/message/MessageRequestParser';
|
||||
@@ -16,10 +15,10 @@ import {readStateCapable} from '@app/api/read_state/ReadStateChannelMeta';
|
||||
import type {HonoApp} from '@app/api/types/HonoEnv';
|
||||
import {assertAccountNotLimited} from '@app/api/user/AccountLimit';
|
||||
import {parseJsonPreservingLargeIntegers} from '@app/api/utils/LosslessJsonParser';
|
||||
import {requireRequestClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {Validator} from '@app/api/Validator';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import {SudoVerificationSchema} from '@fluxer/schema/src/domains/auth/AuthSchemas';
|
||||
import {
|
||||
ChannelIdMessageIdAttachmentIdParam,
|
||||
@@ -227,10 +226,7 @@ export function MessageController(app: HonoApp) {
|
||||
'Returns presigned upload URLs for message attachments in the target channel. Small files (<=10MB) return a singlepart PUT URL; larger files return a multipart plan (upload_id + per-part URLs) that the client should complete via the matching /attachments/complete endpoint. Requires message send and attachment permissions in guild channels.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
const channelId = createChannelID(ctx.req.valid('param').channel_id);
|
||||
const {attachments} = ctx.req.valid('json');
|
||||
return ctx.json({
|
||||
@@ -261,10 +257,7 @@ export function MessageController(app: HonoApp) {
|
||||
'Finalizes one or more multipart attachment uploads. Called after all chunks have been PUT to their presigned URLs. The server lists the uploaded parts and issues S3 CompleteMultipartUpload. Returns the finalized upload keys, which can be referenced in a subsequent message create request.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
const channelId = createChannelID(ctx.req.valid('param').channel_id);
|
||||
const {uploads} = ctx.req.valid('json');
|
||||
return ctx.json({
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createChannelID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import {viewerFromCtx} from '@app/api/experiment/ChannelThreadsGate';
|
||||
import {DefaultUserOnly, LoginRequired} from '@app/api/middleware/AuthMiddleware';
|
||||
import {RateLimitMiddleware} from '@app/api/middleware/RateLimitMiddleware';
|
||||
import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware';
|
||||
import {RateLimitConfigs} from '@app/api/RateLimitConfig';
|
||||
import type {HonoApp} from '@app/api/types/HonoEnv';
|
||||
import {requireRequestClientIp} from '@app/api/utils/RequestClientIp';
|
||||
import {Validator} from '@app/api/Validator';
|
||||
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import {
|
||||
StreamPreviewResponse,
|
||||
StreamPreviewUploadBodySchema,
|
||||
@@ -102,10 +101,7 @@ export function StreamController(app: HonoApp) {
|
||||
const user = ctx.get('user');
|
||||
const {channel_id, content_type} = ctx.req.valid('json');
|
||||
const streamKey = ctx.req.valid('param').stream_key;
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const clientIp = requireRequestClientIp(ctx);
|
||||
const response = await ctx.get('streamService').createPreviewUploadUrl({
|
||||
viewer: viewerFromCtx(ctx),
|
||||
streamKey,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
privateChannelLastMessageIdPatch,
|
||||
privateChannelMetadataPatch,
|
||||
} from '@app/api/channel/PrivateChannelSnapshot';
|
||||
import {type GuildChannelListMode, IChannelDataRepository} from '@app/api/channel/repositories/IChannelDataRepository';
|
||||
import {
|
||||
BatchBuilder,
|
||||
executeConditional,
|
||||
@@ -27,6 +26,8 @@ import {Channel} from '@app/api/models/Channel';
|
||||
import {Channels, ChannelsByGuild, PrivateChannels, ThreadOnlyChannelsByGuild, ThreadStats} from '@app/api/Tables';
|
||||
import {THREAD_CHANNEL_TYPES, THREAD_ONLY_CHANNEL_TYPES} from '@fluxer/constants/src/ThreadConstants';
|
||||
|
||||
export type GuildChannelListMode = 'enrolled' | 'maintenance' | 'complete';
|
||||
|
||||
const FETCH_CHANNEL_BY_ID = Channels.select({
|
||||
where: [Channels.where.eq('channel_id'), Channels.where.eq('soft_deleted')],
|
||||
limit: 1,
|
||||
@@ -48,10 +49,8 @@ const FETCH_OPEN_PRIVATE_CHANNEL_TARGET = PrivateChannels.selectCql({
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
export class ChannelDataRepository extends IChannelDataRepository {
|
||||
constructor(private readonly requestCache?: RequestCache) {
|
||||
super();
|
||||
}
|
||||
export class ChannelDataRepository {
|
||||
constructor(private readonly requestCache?: RequestCache) {}
|
||||
|
||||
async findUnique(channelId: ChannelID): Promise<Channel | null> {
|
||||
const prefetched = this.requestCache?.takeChannel(channelId);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {ChannelDataRepository} from '@app/api/channel/repositories/ChannelDataRepository';
|
||||
import {CrosspostedMessageRepository} from '@app/api/channel/repositories/CrosspostedMessageRepository';
|
||||
import {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import {MessageInteractionRepository} from '@app/api/channel/repositories/MessageInteractionRepository';
|
||||
import {MessageRepository} from '@app/api/channel/repositories/MessageRepository';
|
||||
import {ThreadRepository} from '@app/api/channel/repositories/ThreadRepository';
|
||||
import {enqueueRepairThreadIndexes} from '@app/api/channel/threads/ThreadJobs';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
|
||||
export class ChannelRepository extends IChannelRepositoryAggregate {
|
||||
readonly channelData: ChannelDataRepository;
|
||||
readonly messages: MessageRepository;
|
||||
readonly messageInteractions: MessageInteractionRepository;
|
||||
readonly threads: ThreadRepository;
|
||||
readonly crossposts: CrosspostedMessageRepository;
|
||||
|
||||
constructor(requestCache?: RequestCache) {
|
||||
super();
|
||||
this.channelData = new ChannelDataRepository(requestCache);
|
||||
this.messages = new MessageRepository(this.channelData);
|
||||
this.messageInteractions = new MessageInteractionRepository(this.messages);
|
||||
this.threads = new ThreadRepository(this.channelData, this.messages, enqueueRepairThreadIndexes);
|
||||
this.crossposts = new CrosspostedMessageRepository();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, MessageID, WebhookID} from '@app/api/BrandedTypes';
|
||||
import {
|
||||
type CrosspostedMessageKey,
|
||||
type CrosspostSource,
|
||||
type CrosspostSyncState,
|
||||
ICrosspostedMessageRepository,
|
||||
} from '@app/api/channel/repositories/ICrosspostedMessageRepository';
|
||||
import {
|
||||
deleteOneOrMany,
|
||||
executeConditional,
|
||||
@@ -18,6 +12,21 @@ import {Db} from '@app/api/database/CassandraTypes';
|
||||
import type {CrosspostedMessageRow, CrosspostSourceByChannelRow} from '@app/api/database/types/ChannelTypes';
|
||||
import {CrosspostedMessages, CrosspostSourcesByChannel} from '@app/api/Tables';
|
||||
|
||||
export interface CrosspostedMessageKey {
|
||||
sourceMessageId: MessageID;
|
||||
webhookId: WebhookID;
|
||||
}
|
||||
|
||||
export interface CrosspostSyncState {
|
||||
targetMessageId: MessageID;
|
||||
sourceFingerprint: string | null;
|
||||
}
|
||||
|
||||
export interface CrosspostSource {
|
||||
sourceChannelId: ChannelID;
|
||||
sourceMessageId: MessageID;
|
||||
}
|
||||
|
||||
const FETCH_CROSSPOSTED_MESSAGE_CQL = CrosspostedMessages.selectCql({
|
||||
where: [CrosspostedMessages.where.eq('source_message_id'), CrosspostedMessages.where.eq('webhook_id')],
|
||||
limit: 1,
|
||||
@@ -64,7 +73,7 @@ function toPk(key: CrosspostedMessageKey): Pick<CrosspostedMessageRow, 'source_m
|
||||
return {source_message_id: key.sourceMessageId, webhook_id: key.webhookId};
|
||||
}
|
||||
|
||||
export class CrosspostedMessageRepository extends ICrosspostedMessageRepository {
|
||||
export class CrosspostedMessageRepository {
|
||||
async get(sourceMessageId: MessageID, webhookId: WebhookID): Promise<CrosspostedMessageRow | null> {
|
||||
return fetchOne<CrosspostedMessageRow>(FETCH_CROSSPOSTED_MESSAGE_CQL, {
|
||||
source_message_id: sourceMessageId,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, GuildID, MessageID} from '@app/api/BrandedTypes';
|
||||
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
|
||||
export type GuildChannelListMode = 'enrolled' | 'maintenance' | 'complete';
|
||||
|
||||
export abstract class IChannelDataRepository {
|
||||
abstract findUnique(channelId: ChannelID): Promise<Channel | null>;
|
||||
|
||||
abstract upsert(data: ChannelRow, oldData?: ChannelRow | null): Promise<Channel>;
|
||||
|
||||
abstract updateLastMessageId(channelId: ChannelID, messageId: MessageID, opts?: {isInsert?: boolean}): Promise<void>;
|
||||
|
||||
abstract delete(channelId: ChannelID, guildId?: GuildID, type?: number): Promise<void>;
|
||||
|
||||
abstract listGuildChannels(guildId: GuildID, mode: GuildChannelListMode): Promise<Array<Channel>>;
|
||||
|
||||
abstract listChannels(channelIds: Array<ChannelID>): Promise<Array<Channel>>;
|
||||
|
||||
abstract countGuildChannels(guildId: GuildID): Promise<number>;
|
||||
|
||||
abstract patchIndexedAt(channelId: ChannelID, indexedAt: Date): Promise<void>;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {IChannelDataRepository} from '@app/api/channel/repositories/IChannelDataRepository';
|
||||
import type {ICrosspostedMessageRepository} from '@app/api/channel/repositories/ICrosspostedMessageRepository';
|
||||
import type {IMessageInteractionRepository} from '@app/api/channel/repositories/IMessageInteractionRepository';
|
||||
import type {IMessageRepository} from '@app/api/channel/repositories/IMessageRepository';
|
||||
import type {IThreadRepository} from '@app/api/channel/repositories/IThreadRepository';
|
||||
|
||||
export abstract class IChannelRepositoryAggregate {
|
||||
abstract readonly channelData: IChannelDataRepository;
|
||||
abstract readonly messages: IMessageRepository;
|
||||
abstract readonly messageInteractions: IMessageInteractionRepository;
|
||||
abstract readonly threads: IThreadRepository;
|
||||
abstract readonly crossposts: ICrosspostedMessageRepository;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, MessageID, WebhookID} from '@app/api/BrandedTypes';
|
||||
import type {CrosspostedMessageRow} from '@app/api/database/types/ChannelTypes';
|
||||
|
||||
export interface CrosspostedMessageKey {
|
||||
sourceMessageId: MessageID;
|
||||
webhookId: WebhookID;
|
||||
}
|
||||
|
||||
export interface CrosspostSyncState {
|
||||
targetMessageId: MessageID;
|
||||
sourceFingerprint: string | null;
|
||||
}
|
||||
|
||||
export interface CrosspostSource {
|
||||
sourceChannelId: ChannelID;
|
||||
sourceMessageId: MessageID;
|
||||
}
|
||||
|
||||
export abstract class ICrosspostedMessageRepository {
|
||||
abstract get(sourceMessageId: MessageID, webhookId: WebhookID): Promise<CrosspostedMessageRow | null>;
|
||||
|
||||
abstract insertPending(row: CrosspostedMessageRow): Promise<boolean>;
|
||||
|
||||
abstract reclaimPending(
|
||||
key: CrosspostedMessageKey,
|
||||
data: {
|
||||
fromTargetMessageId: MessageID;
|
||||
toTargetMessageId: MessageID;
|
||||
reservedAt: Date;
|
||||
},
|
||||
): Promise<boolean>;
|
||||
|
||||
abstract markDelivered(key: CrosspostedMessageKey, data: CrosspostSyncState): Promise<boolean>;
|
||||
|
||||
abstract updateSynced(key: CrosspostedMessageKey, data: CrosspostSyncState): Promise<boolean>;
|
||||
|
||||
abstract listBySourceMessage(
|
||||
sourceMessageId: MessageID,
|
||||
options: {afterWebhookId?: WebhookID; limit: number},
|
||||
): Promise<Array<CrosspostedMessageRow>>;
|
||||
|
||||
abstract delete(
|
||||
key: CrosspostedMessageKey,
|
||||
expected?: Partial<Pick<CrosspostedMessageRow, 'state' | 'target_message_id'>>,
|
||||
): Promise<boolean>;
|
||||
|
||||
abstract addSource(source: CrosspostSource): Promise<void>;
|
||||
|
||||
abstract listSourcesByChannel(
|
||||
sourceChannelId: ChannelID,
|
||||
options: {afterMessageId?: MessageID; limit: number},
|
||||
): Promise<Array<MessageID>>;
|
||||
|
||||
abstract deleteSource(source: CrosspostSource): Promise<void>;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, EmojiID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import type {MessageReaction} from '@app/api/models/MessageReaction';
|
||||
|
||||
export abstract class IMessageInteractionRepository {
|
||||
abstract listChannelPins(channelId: ChannelID, beforePinnedTimestamp: Date, limit?: number): Promise<Array<Message>>;
|
||||
|
||||
abstract addChannelPin(channelId: ChannelID, messageId: MessageID, pinnedTimestamp: Date): Promise<void>;
|
||||
|
||||
abstract removeChannelPin(channelId: ChannelID, messageId: MessageID): Promise<void>;
|
||||
|
||||
abstract listMessageReactions(channelId: ChannelID, messageId: MessageID): Promise<Array<MessageReaction>>;
|
||||
|
||||
abstract listReactionUsers(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
emojiName: string,
|
||||
limit?: number,
|
||||
after?: UserID,
|
||||
emojiId?: EmojiID,
|
||||
): Promise<Array<MessageReaction>>;
|
||||
|
||||
abstract addReaction(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
userId: UserID,
|
||||
emojiName: string,
|
||||
emojiId?: EmojiID,
|
||||
emojiAnimated?: boolean,
|
||||
): Promise<MessageReaction>;
|
||||
|
||||
abstract removeReaction(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
userId: UserID,
|
||||
emojiName: string,
|
||||
emojiId?: EmojiID,
|
||||
): Promise<void>;
|
||||
|
||||
abstract removeAllReactions(channelId: ChannelID, messageId: MessageID): Promise<void>;
|
||||
|
||||
abstract removeAllReactionsForEmoji(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
emojiName: string,
|
||||
emojiId?: EmojiID,
|
||||
): Promise<void>;
|
||||
|
||||
abstract countReactionUsers(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
emojiName: string,
|
||||
emojiId?: EmojiID,
|
||||
): Promise<number>;
|
||||
|
||||
abstract countUniqueReactions(channelId: ChannelID, messageId: MessageID): Promise<number>;
|
||||
|
||||
abstract checkUserReactionExists(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
userId: UserID,
|
||||
emojiName: string,
|
||||
emojiId?: EmojiID,
|
||||
): Promise<boolean>;
|
||||
|
||||
abstract setHasReaction(channelId: ChannelID, messageId: MessageID, hasReaction: boolean): Promise<void>;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {AttachmentID, ChannelID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {MessageRow} from '@app/api/database/types/MessageTypes';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
|
||||
export interface ListMessagesOptions {
|
||||
restrictToBeforeBucket?: boolean;
|
||||
immediateAfter?: boolean;
|
||||
}
|
||||
|
||||
export interface UpsertMessageOptions {
|
||||
isInsert?: boolean;
|
||||
skipParentLastMessageId?: boolean;
|
||||
}
|
||||
|
||||
export abstract class IMessageRepository {
|
||||
abstract listMessages(
|
||||
channelId: ChannelID,
|
||||
beforeMessageId?: MessageID,
|
||||
limit?: number,
|
||||
afterMessageId?: MessageID,
|
||||
options?: ListMessagesOptions,
|
||||
): Promise<Array<Message>>;
|
||||
|
||||
abstract getMessage(channelId: ChannelID, messageId: MessageID): Promise<Message | null>;
|
||||
|
||||
abstract upsertMessage(data: MessageRow, oldData?: MessageRow | null, opts?: UpsertMessageOptions): Promise<Message>;
|
||||
|
||||
abstract updateEmbeds(message: Message): Promise<void>;
|
||||
|
||||
abstract deleteMessage(
|
||||
channelId: ChannelID,
|
||||
messageId: MessageID,
|
||||
authorId: UserID,
|
||||
pinnedTimestamp?: Date,
|
||||
): Promise<void>;
|
||||
|
||||
abstract bulkDeleteMessages(channelId: ChannelID, messageIds: Array<MessageID>): Promise<void>;
|
||||
|
||||
abstract deleteAllChannelMessages(channelId: ChannelID): Promise<void>;
|
||||
|
||||
abstract listMessagesByAuthor(
|
||||
authorId: UserID,
|
||||
limit?: number,
|
||||
lastMessageId?: MessageID,
|
||||
): Promise<
|
||||
Array<{
|
||||
channelId: ChannelID;
|
||||
messageId: MessageID;
|
||||
}>
|
||||
>;
|
||||
|
||||
abstract anonymizeMessage(channelId: ChannelID, messageId: MessageID, newAuthorId: UserID): Promise<void>;
|
||||
|
||||
abstract lookupAttachmentByChannelAndFilename(
|
||||
channelId: ChannelID,
|
||||
attachmentId: AttachmentID,
|
||||
filename: string,
|
||||
): Promise<MessageID | null>;
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, GuildID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
|
||||
import type {GuildThreadStateRow, ThreadParentConfigRow, ThreadStateRow} from '@app/api/database/types/ThreadTypes';
|
||||
import type {MuteConfig} from '@app/api/database/types/UserTypes';
|
||||
import type {ThreadMember} from '@app/api/models/ThreadMember';
|
||||
import type {ThreadParentConfig} from '@app/api/models/ThreadParentConfig';
|
||||
import type {ThreadState} from '@app/api/models/ThreadState';
|
||||
import type {ThreadStats} from '@app/api/models/ThreadStats';
|
||||
|
||||
export interface CreateThreadMember {
|
||||
userId: UserID;
|
||||
flags: number;
|
||||
}
|
||||
|
||||
export interface CreateThreadParams {
|
||||
channel: ChannelRow;
|
||||
parentType: number;
|
||||
autoArchiveDuration: number;
|
||||
invitable: boolean | null;
|
||||
flags: number;
|
||||
appliedTags: Array<bigint>;
|
||||
hasStarter: boolean;
|
||||
createdAt: Date;
|
||||
members: Array<CreateThreadMember>;
|
||||
}
|
||||
|
||||
export type ThreadStatePatch = Partial<
|
||||
Pick<
|
||||
ThreadStateRow,
|
||||
'archived' | 'locked' | 'invitable' | 'auto_archive_duration' | 'archive_timestamp' | 'flags' | 'applied_tags'
|
||||
>
|
||||
>;
|
||||
|
||||
export interface ThreadStateTransition {
|
||||
previous: ThreadState;
|
||||
state: ThreadState;
|
||||
}
|
||||
|
||||
export interface ArchivedThreadPage {
|
||||
threads: Array<ThreadState>;
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export interface ThreadMemberSettingsPatch {
|
||||
flags?: number;
|
||||
muted?: boolean;
|
||||
muteConfig?: MuteConfig | null;
|
||||
}
|
||||
|
||||
export interface ThreadMemberAddResult {
|
||||
added: Array<ThreadMember>;
|
||||
state: ThreadState;
|
||||
}
|
||||
|
||||
export interface ThreadMemberRemoveResult {
|
||||
removed: Array<ThreadMember>;
|
||||
state: ThreadState | null;
|
||||
}
|
||||
|
||||
export type ThreadParentConfigPatch = Partial<Omit<ThreadParentConfigRow, 'guild_id' | 'channel_id'>>;
|
||||
|
||||
export abstract class IThreadRepository {
|
||||
abstract getState(threadId: ChannelID): Promise<ThreadState | null>;
|
||||
|
||||
abstract getStates(threadIds: Array<ChannelID>): Promise<Array<ThreadState>>;
|
||||
|
||||
abstract getStats(threadId: ChannelID): Promise<ThreadStats>;
|
||||
|
||||
abstract getStatsMany(threadIds: Array<ChannelID>): Promise<Map<ChannelID, ThreadStats>>;
|
||||
|
||||
abstract adjustMessageCount(threadId: ChannelID, delta: number): Promise<void>;
|
||||
|
||||
abstract create(params: CreateThreadParams): Promise<ThreadState>;
|
||||
|
||||
abstract updateState(
|
||||
threadId: ChannelID,
|
||||
mutate: (current: ThreadState) => ThreadStatePatch | null,
|
||||
): Promise<ThreadStateTransition | null>;
|
||||
|
||||
abstract claimForumPin(parentId: ChannelID, threadId: ChannelID): Promise<boolean>;
|
||||
|
||||
abstract releaseForumPin(parentId: ChannelID, threadId: ChannelID): Promise<void>;
|
||||
|
||||
abstract getForumPin(parentId: ChannelID): Promise<ChannelID | null>;
|
||||
|
||||
abstract countActiveThreads(guildId: GuildID): Promise<number>;
|
||||
|
||||
abstract listActiveThreads(guildId: GuildID): Promise<Array<ThreadState>>;
|
||||
|
||||
abstract listArchivedThreads(
|
||||
parentId: ChannelID,
|
||||
isPrivate: boolean,
|
||||
opts: {before?: Date; limit: number},
|
||||
): Promise<ArchivedThreadPage>;
|
||||
|
||||
abstract listJoinedPrivateArchivedThreads(
|
||||
userId: UserID,
|
||||
guildId: GuildID,
|
||||
parentId: ChannelID,
|
||||
opts: {before?: ChannelID; limit: number},
|
||||
): Promise<ArchivedThreadPage>;
|
||||
|
||||
abstract listJoinedThreadIds(userId: UserID, guildId: GuildID): Promise<Array<ChannelID>>;
|
||||
|
||||
abstract listJoinedPrivateThreadIds(
|
||||
userId: UserID,
|
||||
guildId: GuildID,
|
||||
parentId: ChannelID,
|
||||
limit: number,
|
||||
): Promise<Array<ChannelID>>;
|
||||
|
||||
abstract repairThreadIndexes(threadIds: Array<ChannelID>): Promise<void>;
|
||||
|
||||
abstract getMember(threadId: ChannelID, userId: UserID): Promise<ThreadMember | null>;
|
||||
|
||||
abstract getMembers(threadId: ChannelID, userIds: Array<UserID>): Promise<Array<ThreadMember>>;
|
||||
|
||||
abstract listMembers(threadId: ChannelID, opts: {after?: UserID; limit: number}): Promise<Array<ThreadMember>>;
|
||||
|
||||
abstract addMembers(
|
||||
threadId: ChannelID,
|
||||
members: Array<CreateThreadMember>,
|
||||
opts?: {joinTimestamp?: Date},
|
||||
): Promise<ThreadMemberAddResult | null>;
|
||||
|
||||
abstract removeMembers(threadId: ChannelID, userIds: Array<UserID>): Promise<ThreadMemberRemoveResult>;
|
||||
|
||||
abstract updateMemberSettings(expected: ThreadMember, patch: ThreadMemberSettingsPatch): Promise<ThreadMember | null>;
|
||||
|
||||
abstract listThreadIdsByParent(
|
||||
parentId: ChannelID,
|
||||
opts: {after?: ChannelID; limit: number},
|
||||
): Promise<Array<ChannelID>>;
|
||||
|
||||
abstract listParentThreads(parentId: ChannelID): Promise<Array<{threadId: ChannelID; type: number}>>;
|
||||
|
||||
abstract setThreadType(threadId: ChannelID, type: number): Promise<ThreadState | null>;
|
||||
|
||||
abstract listGuildThreadIds(
|
||||
guildId: GuildID,
|
||||
opts?: {activeSince?: Date; parents?: ReadonlyArray<{id: ChannelID; type: number}>},
|
||||
): Promise<Array<ChannelID>>;
|
||||
|
||||
abstract purgeThread(threadId: ChannelID): Promise<void>;
|
||||
|
||||
abstract revertParentLastMessageId(
|
||||
parentId: ChannelID,
|
||||
threadId: ChannelID,
|
||||
previous: MessageID | null,
|
||||
): Promise<void>;
|
||||
|
||||
abstract purgeGuild(guildId: GuildID): Promise<void>;
|
||||
|
||||
abstract getGuildMarker(guildId: GuildID): Promise<GuildThreadStateRow | null>;
|
||||
|
||||
abstract ensureGuildMarker(guildId: GuildID, opts?: {permsSeededAt?: Date}): Promise<void>;
|
||||
|
||||
abstract markGuildPermsSeeded(guildId: GuildID, at: Date): Promise<void>;
|
||||
|
||||
abstract markGuildSearchBackfilled(guildId: GuildID, at: Date): Promise<void>;
|
||||
abstract clearGuildSearchBackfilled(guildId: GuildID): Promise<void>;
|
||||
|
||||
abstract getParentConfig(guildId: GuildID, channelId: ChannelID): Promise<ThreadParentConfig | null>;
|
||||
|
||||
abstract listParentConfigs(guildId: GuildID): Promise<Array<ThreadParentConfig>>;
|
||||
|
||||
abstract patchParentConfig(guildId: GuildID, channelId: ChannelID, patch: ThreadParentConfigPatch): Promise<void>;
|
||||
|
||||
abstract deleteParentConfig(guildId: GuildID, channelId: ChannelID): Promise<void>;
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import type {ChannelID, EmojiID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createEmojiID} from '@app/api/BrandedTypes';
|
||||
import {IMessageInteractionRepository} from '@app/api/channel/repositories/IMessageInteractionRepository';
|
||||
import type {MessageRepository} from '@app/api/channel/repositories/MessageRepository';
|
||||
import {deleteOneOrMany, fetchMany, fetchOne, upsertOne} from '@app/api/database/CassandraQueryExecution';
|
||||
import {Db} from '@app/api/database/CassandraTypes';
|
||||
@@ -66,11 +65,10 @@ const CHECK_USER_REACTION_EXISTS_QUERY = MessageReactions.selectCql({
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
export class MessageInteractionRepository extends IMessageInteractionRepository {
|
||||
export class MessageInteractionRepository {
|
||||
private messageRepository: MessageRepository;
|
||||
|
||||
constructor(messageRepository: MessageRepository) {
|
||||
super();
|
||||
this.messageRepository = messageRepository;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
import type {AttachmentID, ChannelID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {ChannelDataRepository} from '@app/api/channel/repositories/ChannelDataRepository';
|
||||
import {
|
||||
IMessageRepository,
|
||||
type ListMessagesOptions,
|
||||
type UpsertMessageOptions,
|
||||
} from '@app/api/channel/repositories/IMessageRepository';
|
||||
import {MessageAttachmentRepository} from '@app/api/channel/repositories/message/MessageAttachmentRepository';
|
||||
import {MessageAuthorRepository} from '@app/api/channel/repositories/message/MessageAuthorRepository';
|
||||
import {MessageDataRepository} from '@app/api/channel/repositories/message/MessageDataRepository';
|
||||
@@ -14,7 +9,17 @@ import {MessageDeletionRepository} from '@app/api/channel/repositories/message/M
|
||||
import type {MessageRow} from '@app/api/database/types/MessageTypes';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
|
||||
export class MessageRepository extends IMessageRepository {
|
||||
export interface ListMessagesOptions {
|
||||
restrictToBeforeBucket?: boolean;
|
||||
immediateAfter?: boolean;
|
||||
}
|
||||
|
||||
export interface UpsertMessageOptions {
|
||||
isInsert?: boolean;
|
||||
skipParentLastMessageId?: boolean;
|
||||
}
|
||||
|
||||
export class MessageRepository {
|
||||
private dataRepo: MessageDataRepository;
|
||||
private deletionRepo: MessageDeletionRepository;
|
||||
private attachmentRepo: MessageAttachmentRepository;
|
||||
@@ -22,7 +27,6 @@ export class MessageRepository extends IMessageRepository {
|
||||
private channelDataRepo: ChannelDataRepository;
|
||||
|
||||
constructor(channelDataRepo: ChannelDataRepository) {
|
||||
super();
|
||||
this.dataRepo = new MessageDataRepository();
|
||||
this.deletionRepo = new MessageDeletionRepository(this.dataRepo);
|
||||
this.attachmentRepo = new MessageAttachmentRepository();
|
||||
|
||||
@@ -13,9 +13,8 @@ import {
|
||||
createUserID,
|
||||
type GuildID,
|
||||
} from '@app/api/BrandedTypes';
|
||||
import {ChannelRepository} from '@app/api/channel/repositories/ChannelRepository';
|
||||
import type {CreateThreadParams} from '@app/api/channel/repositories/IThreadRepository';
|
||||
import {ThreadRepository} from '@app/api/channel/repositories/ThreadRepository';
|
||||
import {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {type CreateThreadParams, ThreadRepository} from '@app/api/channel/repositories/ThreadRepository';
|
||||
import {
|
||||
type CassandraQueryExecutorForTesting,
|
||||
deleteOneOrMany,
|
||||
|
||||
@@ -2,19 +2,7 @@
|
||||
|
||||
import {type ChannelID, channelIdToMessageId, type GuildID, type MessageID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {ChannelDataRepository} from '@app/api/channel/repositories/ChannelDataRepository';
|
||||
import type {IMessageRepository} from '@app/api/channel/repositories/IMessageRepository';
|
||||
import {
|
||||
type ArchivedThreadPage,
|
||||
type CreateThreadMember,
|
||||
type CreateThreadParams,
|
||||
IThreadRepository,
|
||||
type ThreadMemberAddResult,
|
||||
type ThreadMemberRemoveResult,
|
||||
type ThreadMemberSettingsPatch,
|
||||
type ThreadParentConfigPatch,
|
||||
type ThreadStatePatch,
|
||||
type ThreadStateTransition,
|
||||
} from '@app/api/channel/repositories/IThreadRepository';
|
||||
import type {MessageRepository} from '@app/api/channel/repositories/MessageRepository';
|
||||
import {
|
||||
BatchBuilder,
|
||||
deleteOneOrMany,
|
||||
@@ -37,6 +25,7 @@ import type {
|
||||
ThreadStatsRow,
|
||||
ThreadsByParentRow,
|
||||
} from '@app/api/database/types/ThreadTypes';
|
||||
import type {MuteConfig} from '@app/api/database/types/UserTypes';
|
||||
import {insertGuildThreadMarker, isTainted} from '@app/api/experiment/ChannelThreadsGate';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import {ThreadMember} from '@app/api/models/ThreadMember';
|
||||
@@ -70,6 +59,58 @@ import {ThreadAlreadyCreatedForMessageError} from '@fluxer/errors/src/domains/ch
|
||||
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
|
||||
import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake';
|
||||
|
||||
export interface CreateThreadMember {
|
||||
userId: UserID;
|
||||
flags: number;
|
||||
}
|
||||
|
||||
export interface CreateThreadParams {
|
||||
channel: ChannelRow;
|
||||
parentType: number;
|
||||
autoArchiveDuration: number;
|
||||
invitable: boolean | null;
|
||||
flags: number;
|
||||
appliedTags: Array<bigint>;
|
||||
hasStarter: boolean;
|
||||
createdAt: Date;
|
||||
members: Array<CreateThreadMember>;
|
||||
}
|
||||
|
||||
export type ThreadStatePatch = Partial<
|
||||
Pick<
|
||||
ThreadStateRow,
|
||||
'archived' | 'locked' | 'invitable' | 'auto_archive_duration' | 'archive_timestamp' | 'flags' | 'applied_tags'
|
||||
>
|
||||
>;
|
||||
|
||||
export interface ThreadStateTransition {
|
||||
previous: ThreadState;
|
||||
state: ThreadState;
|
||||
}
|
||||
|
||||
export interface ArchivedThreadPage {
|
||||
threads: Array<ThreadState>;
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export interface ThreadMemberSettingsPatch {
|
||||
flags?: number;
|
||||
muted?: boolean;
|
||||
muteConfig?: MuteConfig | null;
|
||||
}
|
||||
|
||||
export interface ThreadMemberAddResult {
|
||||
added: Array<ThreadMember>;
|
||||
state: ThreadState;
|
||||
}
|
||||
|
||||
export interface ThreadMemberRemoveResult {
|
||||
removed: Array<ThreadMember>;
|
||||
state: ThreadState | null;
|
||||
}
|
||||
|
||||
export type ThreadParentConfigPatch = Partial<Omit<ThreadParentConfigRow, 'guild_id' | 'channel_id'>>;
|
||||
|
||||
const STATE_CAS_ATTEMPTS = 3;
|
||||
const DANGLING_CREATE_REPAIR_MS = 30_000;
|
||||
const ENUMERATION_PAGE_SIZE = 1000;
|
||||
@@ -197,14 +238,12 @@ function toStateOps(patch: ThreadStatePatch): StatePatchOps {
|
||||
return ops;
|
||||
}
|
||||
|
||||
export class ThreadRepository extends IThreadRepository {
|
||||
export class ThreadRepository {
|
||||
constructor(
|
||||
private readonly channelData: ChannelDataRepository,
|
||||
private readonly messages: IMessageRepository,
|
||||
private readonly messages: MessageRepository,
|
||||
private readonly onIndexDrift?: (threadIds: Array<ChannelID>) => void,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
) {}
|
||||
|
||||
async getState(threadId: ChannelID): Promise<ThreadState | null> {
|
||||
const row = await fetchOne<ThreadStateRow>(FETCH_STATE.bind({thread_id: threadId}));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, MessageID} from '@app/api/BrandedTypes';
|
||||
import type {ListMessagesOptions} from '@app/api/channel/repositories/IMessageRepository';
|
||||
import type {ListMessagesOptions} from '@app/api/channel/repositories/MessageRepository';
|
||||
import {BucketScanDirection, scanBucketsWithIndex} from '@app/api/channel/repositories/message/BucketScanEngine';
|
||||
import {BatchBuilder, deleteOneOrMany, fetchMany, fetchOne, upsertOne} from '@app/api/database/CassandraQueryExecution';
|
||||
import {Db, type QueryTemplate} from '@app/api/database/CassandraTypes';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type {AttachmentID, ChannelID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {UploadedAttachment} from '@app/api/channel/AttachmentDTOs';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {
|
||||
AttachmentUploadMode,
|
||||
AttachmentUploadTraceRepository,
|
||||
@@ -33,7 +33,7 @@ import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Attachment} from '@app/api/models/Attachment';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {mapWithConcurrency} from '@app/api/utils/ConcurrencyUtils';
|
||||
import {assertGuildMemberCanCommunicate} from '@app/api/utils/GuildCommunicationUtils';
|
||||
import {Permissions, TEXT_BASED_CHANNEL_TYPES} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -111,8 +111,8 @@ const FORM_DATA_UPLOAD_CONCURRENCY = 2;
|
||||
|
||||
export class AttachmentUploadService {
|
||||
constructor(
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private userRepository: IUserRepository,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
private storageService: IStorageService,
|
||||
private attachmentUploadTraceRepository: AttachmentUploadTraceRepository,
|
||||
private purgeQueue: IPurgeQueue,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, GuildID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AuthenticatedChannel, AuthenticatedThread} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
import {DMPermissionValidator} from '@app/api/channel/services/DMPermissionValidator';
|
||||
import {
|
||||
@@ -26,13 +26,13 @@ import {
|
||||
viewerActive,
|
||||
} from '@app/api/experiment/ChannelThreadsGate';
|
||||
import {isGuildMemberTimedOut} from '@app/api/guild/GuildModel';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import {createGuildMfaEnforcer} from '@app/api/guild/services/GuildMfaEnforcement';
|
||||
import type {GuildChannelAuthContext, IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {GuildMember} from '@app/api/models/GuildMember';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {canUserAccessNsfwContent} from '@app/api/utils/AgeUtils';
|
||||
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {
|
||||
@@ -80,9 +80,9 @@ export abstract class BaseChannelAuthService {
|
||||
protected dmPermissionValidator: DMPermissionValidator;
|
||||
|
||||
constructor(
|
||||
protected channelRepository: IChannelRepositoryAggregate,
|
||||
protected userRepository: IUserRepository,
|
||||
protected guildRepository: IGuildRepositoryAggregate,
|
||||
protected channelRepository: ChannelRepository,
|
||||
protected userRepository: UserRepository,
|
||||
protected guildRepository: GuildRepository,
|
||||
protected gatewayService: IGatewayService,
|
||||
) {
|
||||
this.dmPermissionValidator = new DMPermissionValidator({
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import {type ChannelID, createMessageID, type UserID} from '@app/api/BrandedTypes';
|
||||
import {mapChannelToResponse} from '@app/api/channel/ChannelMappers';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {DMPermissionValidator} from '@app/api/channel/services/DMPermissionValidator';
|
||||
import {buildBroadcastMessageData} from '@app/api/channel/services/message/MessageGatewayDispatch';
|
||||
import {incrementDmMentionCounts} from '@app/api/channel/services/message/ReadStateHelpers';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {CallData, IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {IVoiceRoomStore} from '@app/api/infrastructure/IVoiceRoomStore';
|
||||
@@ -14,8 +14,8 @@ import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {ReadStateService} from '@app/api/read_state/ReadStateService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import {assertMayStartConversation} from '@app/api/user/NewConversationLimit';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {VoiceAccessContext, VoiceAvailabilityService} from '@app/api/voice/VoiceAvailabilityService';
|
||||
import {AUTOMATIC_VOICE_REGION_ID, ChannelTypes, MessageTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {IncomingCallFlags, RelationshipTypes} from '@fluxer/constants/src/UserConstants';
|
||||
@@ -31,9 +31,9 @@ export class CallService {
|
||||
private dmPermissionValidator: DMPermissionValidator;
|
||||
|
||||
constructor(
|
||||
private channelRepository: IChannelRepository,
|
||||
private userRepository: IUserRepository,
|
||||
private guildRepository: IGuildRepositoryAggregate,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
private guildRepository: GuildRepository,
|
||||
private gatewayService: IGatewayService,
|
||||
private userCacheService: UserCacheService,
|
||||
private snowflakeService: ISnowflakeService,
|
||||
@@ -51,7 +51,7 @@ export class CallService {
|
||||
ringable: boolean;
|
||||
silent?: boolean;
|
||||
}> {
|
||||
const channel = await this.channelRepository.findUnique(channelId);
|
||||
const channel = await this.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel) throw new UnknownChannelError();
|
||||
if (channel.type !== ChannelTypes.DM && channel.type !== ChannelTypes.GROUP_DM) {
|
||||
throw new InvalidChannelTypeForCallError();
|
||||
@@ -148,7 +148,7 @@ export class CallService {
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
}): Promise<CallData> {
|
||||
const channel = await this.channelRepository.findUnique(channelId);
|
||||
const channel = await this.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel) throw new UnknownChannelError();
|
||||
if (channel.type !== ChannelTypes.DM && channel.type !== ChannelTypes.GROUP_DM) {
|
||||
throw new InvalidChannelTypeForCallError();
|
||||
@@ -175,7 +175,7 @@ export class CallService {
|
||||
user: caller,
|
||||
targetId: dmRecipientIds[0]!,
|
||||
users: this.userRepository,
|
||||
messages: this.channelRepository,
|
||||
messages: this.channelRepository.messages,
|
||||
channel,
|
||||
});
|
||||
}
|
||||
@@ -191,7 +191,7 @@ export class CallService {
|
||||
const selectedRegion = region && region !== AUTOMATIC_VOICE_REGION_ID ? region : AUTOMATIC_VOICE_REGION_ID;
|
||||
const allRecipients = Array.from(new Set([userId, ...Array.from(channel.recipientIds)]));
|
||||
const messageId = createMessageID(await this.snowflakeService.generateForChannel(channelId));
|
||||
await this.channelRepository.upsertMessage({
|
||||
await this.channelRepository.messages.upsertMessage({
|
||||
channel_id: channelId,
|
||||
bucket: BucketUtils.makeBucket(messageId),
|
||||
message_id: messageId,
|
||||
@@ -258,7 +258,7 @@ export class CallService {
|
||||
emitGateway: false,
|
||||
});
|
||||
}
|
||||
const message = await this.channelRepository.getMessage(channelId, messageId);
|
||||
const message = await this.channelRepository.messages.getMessage(channelId, messageId);
|
||||
if (message) {
|
||||
const messageResponse = await buildBroadcastMessageData({
|
||||
channel,
|
||||
@@ -286,7 +286,7 @@ export class CallService {
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
}): Promise<void> {
|
||||
const channel = await this.channelRepository.findUnique(channelId);
|
||||
const channel = await this.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel) throw new UnknownChannelError();
|
||||
if (channel.type !== ChannelTypes.DM && channel.type !== ChannelTypes.GROUP_DM) {
|
||||
throw new InvalidChannelTypeForCallError();
|
||||
@@ -326,7 +326,7 @@ export class CallService {
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
}): Promise<void> {
|
||||
const channel = await this.channelRepository.findUnique(channelId);
|
||||
const channel = await this.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel) throw new UnknownChannelError();
|
||||
if (channel.type !== ChannelTypes.DM && channel.type !== ChannelTypes.GROUP_DM) {
|
||||
throw new InvalidChannelTypeForCallError();
|
||||
@@ -353,7 +353,7 @@ export class CallService {
|
||||
user: caller,
|
||||
targetId: dmRecipientIds[0]!,
|
||||
users: this.userRepository,
|
||||
messages: this.channelRepository,
|
||||
messages: this.channelRepository.messages,
|
||||
channel,
|
||||
});
|
||||
}
|
||||
@@ -472,7 +472,7 @@ export class CallService {
|
||||
channelId: ChannelID;
|
||||
recipients?: Array<UserID>;
|
||||
}): Promise<void> {
|
||||
const channel = await this.channelRepository.findUnique(channelId);
|
||||
const channel = await this.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel) throw new UnknownChannelError();
|
||||
if (channel.type !== ChannelTypes.DM && channel.type !== ChannelTypes.GROUP_DM) {
|
||||
throw new InvalidChannelTypeForCallError();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {ChannelID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createUserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {ChannelAuthService} from '@app/api/channel/services/channel_data/ChannelAuthService';
|
||||
import type {
|
||||
ChannelTypeConversion,
|
||||
@@ -16,7 +16,7 @@ import {ThreadModifyService} from '@app/api/channel/services/thread/ThreadModify
|
||||
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 {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {AvatarService} from '@app/api/infrastructure/AvatarService';
|
||||
import type {IPurgeQueue} from '@app/api/infrastructure/CachePurgeQueue';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
@@ -25,13 +25,13 @@ import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService'
|
||||
import type {IStorageService} from '@app/api/infrastructure/IStorageService';
|
||||
import type {IVoiceRoomStore} from '@app/api/infrastructure/IVoiceRoomStore';
|
||||
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import type {IInviteRepository} from '@app/api/invite/IInviteRepository';
|
||||
import type {InviteRepository} from '@app/api/invite/InviteRepository';
|
||||
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {VoiceAvailabilityService} from '@app/api/voice/VoiceAvailabilityService';
|
||||
import type {IWebhookRepository} from '@app/api/webhook/IWebhookRepository';
|
||||
import type {WebhookRepository} from '@app/api/webhook/WebhookRepository';
|
||||
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import type {
|
||||
ChannelUpdateGatedRequest,
|
||||
@@ -57,9 +57,9 @@ export class ChannelDataService {
|
||||
public readonly threadModify: ThreadModifyService;
|
||||
|
||||
constructor(
|
||||
channelRepository: IChannelRepositoryAggregate,
|
||||
userRepository: IUserRepository,
|
||||
guildRepository: IGuildRepositoryAggregate,
|
||||
channelRepository: ChannelRepository,
|
||||
userRepository: UserRepository,
|
||||
guildRepository: GuildRepository,
|
||||
userCacheService: UserCacheService,
|
||||
storageService: IStorageService,
|
||||
gatewayService: IGatewayService,
|
||||
@@ -71,8 +71,8 @@ export class ChannelDataService {
|
||||
voiceAvailabilityService: VoiceAvailabilityService | null,
|
||||
messagePersistenceService: MessagePersistenceService,
|
||||
guildAuditLogService: GuildAuditLogService,
|
||||
inviteRepository: IInviteRepository,
|
||||
webhookRepository: IWebhookRepository,
|
||||
inviteRepository: InviteRepository,
|
||||
webhookRepository: WebhookRepository,
|
||||
limitConfigService: LimitConfigService,
|
||||
rateLimitService: IRateLimitService,
|
||||
cacheService: ICacheService,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID} from '@app/api/BrandedTypes';
|
||||
import type {ICrosspostedMessageRepository} from '@app/api/channel/repositories/ICrosspostedMessageRepository';
|
||||
import type {CrosspostedMessageRepository} from '@app/api/channel/repositories/CrosspostedMessageRepository';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import {getSnowflakeService, getWorkerService} from '@app/api/middleware/ServiceRegistry';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
@@ -89,7 +89,7 @@ export async function enqueueChannelFollowerRemoval(params: ChannelFollowerRemov
|
||||
|
||||
async function channelMayHaveFollowerCopies(
|
||||
channel: Pick<Channel, 'id' | 'type'>,
|
||||
crossposts: Pick<ICrosspostedMessageRepository, 'listSourcesByChannel'>,
|
||||
crossposts: Pick<CrosspostedMessageRepository, 'listSourcesByChannel'>,
|
||||
): Promise<boolean> {
|
||||
if (channel.type === ChannelTypes.GUILD_ANNOUNCEMENT) return true;
|
||||
const sources = await crossposts.listSourcesByChannel(channel.id, {limit: 1});
|
||||
@@ -98,7 +98,7 @@ async function channelMayHaveFollowerCopies(
|
||||
|
||||
export async function scheduleDeletedChannelFollowerRemoval(params: {
|
||||
channel: Pick<Channel, 'id' | 'type'>;
|
||||
crossposts: Pick<ICrosspostedMessageRepository, 'listSourcesByChannel'>;
|
||||
crossposts: Pick<CrosspostedMessageRepository, 'listSourcesByChannel'>;
|
||||
copyMode: ChannelFollowerRemovalCopyMode;
|
||||
}): Promise<void> {
|
||||
if (!(await channelMayHaveFollowerCopies(params.channel, params.crossposts))) return;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {ApiContext} from '@app/api/ApiContext';
|
||||
import type {ChannelID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AttachmentUploadTraceRepository} from '@app/api/channel/repositories/message/AttachmentUploadTraceRepository';
|
||||
import {AttachmentUploadService} from '@app/api/channel/services/AttachmentUploadService';
|
||||
import type {AuthenticatedChannel} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
@@ -14,9 +14,9 @@ import {MessageService} from '@app/api/channel/services/MessageService';
|
||||
import {MessagePersistenceService} from '@app/api/channel/services/message/MessagePersistenceService';
|
||||
import {UserMessageDeletionService} from '@app/api/channel/services/message/UserMessageDeletionService';
|
||||
import {type ThreadViewer, viewerActive} from '@app/api/experiment/ChannelThreadsGate';
|
||||
import type {IFavoriteMemeRepository} from '@app/api/favorite_meme/IFavoriteMemeRepository';
|
||||
import type {FavoriteMemeRepository} from '@app/api/favorite_meme/FavoriteMemeRepository';
|
||||
import type {GuildAuditLogService} from '@app/api/guild/GuildAuditLogService';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {AvatarService} from '@app/api/infrastructure/AvatarService';
|
||||
import type {IPurgeQueue} from '@app/api/infrastructure/CachePurgeQueue';
|
||||
import type {EmbedService} from '@app/api/infrastructure/EmbedService';
|
||||
@@ -24,13 +24,13 @@ import type {ILiveKitService} from '@app/api/infrastructure/ILiveKitService';
|
||||
import type {IStorageService} from '@app/api/infrastructure/IStorageService';
|
||||
import type {IVoiceRoomStore} from '@app/api/infrastructure/IVoiceRoomStore';
|
||||
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import type {IInviteRepository} from '@app/api/invite/IInviteRepository';
|
||||
import type {InviteRepository} from '@app/api/invite/InviteRepository';
|
||||
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {ReadStateService} from '@app/api/read_state/ReadStateService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {VoiceAvailabilityService} from '@app/api/voice/VoiceAvailabilityService';
|
||||
import type {IWebhookRepository} from '@app/api/webhook/IWebhookRepository';
|
||||
import type {WebhookRepository} from '@app/api/webhook/WebhookRepository';
|
||||
import {Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {TEXT_THREAD_PARENT_CHANNEL_TYPES} from '@fluxer/constants/src/ThreadConstants';
|
||||
import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
|
||||
@@ -60,9 +60,9 @@ export class ChannelService {
|
||||
|
||||
constructor(
|
||||
apiContext: ApiContext,
|
||||
channelRepository: IChannelRepository,
|
||||
userRepository: IUserRepository,
|
||||
guildRepository: IGuildRepositoryAggregate,
|
||||
channelRepository: ChannelRepository,
|
||||
userRepository: UserRepository,
|
||||
guildRepository: GuildRepository,
|
||||
userCacheService: UserCacheService,
|
||||
embedService: EmbedService,
|
||||
readStateService: ReadStateService,
|
||||
@@ -71,12 +71,12 @@ export class ChannelService {
|
||||
avatarService: AvatarService,
|
||||
virusScanService: IVirusScanService,
|
||||
purgeQueue: IPurgeQueue,
|
||||
favoriteMemeRepository: IFavoriteMemeRepository,
|
||||
favoriteMemeRepository: FavoriteMemeRepository,
|
||||
guildAuditLogService: GuildAuditLogService,
|
||||
voiceRoomStore: IVoiceRoomStore,
|
||||
liveKitService: ILiveKitService,
|
||||
inviteRepository: IInviteRepository,
|
||||
webhookRepository: IWebhookRepository,
|
||||
inviteRepository: InviteRepository,
|
||||
webhookRepository: WebhookRepository,
|
||||
limitConfigService: LimitConfigService,
|
||||
voiceAvailabilityService: VoiceAvailabilityService | null,
|
||||
) {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import {SYSTEM_USER_ID} from '@app/api/constants/Core';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {Guild} from '@app/api/models/Guild';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {UserSettings} from '@app/api/models/UserSettings';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {isBugHunterBotUser} from '@app/api/user/UserHelpers';
|
||||
import {checkGuildVerificationWithGuildModel} from '@app/api/utils/GuildVerificationUtils';
|
||||
import {getMutualGuildsForDmAccess} from '@app/api/utils/MutualGuildDmAccess';
|
||||
@@ -15,8 +15,8 @@ import {CannotSendMessagesToUserError} from '@fluxer/errors/src/domains/channel/
|
||||
import {UnclaimedAccountCannotSendDirectMessagesError} from '@fluxer/errors/src/domains/channel/UnclaimedAccountCannotSendDirectMessagesError';
|
||||
|
||||
interface DMPermissionValidatorDeps {
|
||||
userRepository: IUserRepository;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
userRepository: UserRepository;
|
||||
guildRepository: GuildRepository;
|
||||
}
|
||||
|
||||
export class DMPermissionValidator {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AuthenticatedChannel} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
import {MessageInteractionAuthService} from '@app/api/channel/services/interaction/MessageInteractionAuthService';
|
||||
import {MessagePinAuthService} from '@app/api/channel/services/interaction/MessagePinAuthService';
|
||||
@@ -14,19 +14,19 @@ import {maskThreadArtifactsFor} from '@app/api/channel/services/message/ThreadMe
|
||||
import {assertThreadAllowed} from '@app/api/channel/services/thread/ThreadDenials';
|
||||
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 {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import {
|
||||
assertMayStartConversation,
|
||||
getNewConversationLimit,
|
||||
oneToOneDmRecipient,
|
||||
} from '@app/api/user/NewConversationLimit';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {assertGuildMemberCanCommunicate} from '@app/api/utils/GuildCommunicationUtils';
|
||||
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {threadWriteBlock} from '@fluxer/constants/src/ThreadPermissionUtils';
|
||||
@@ -43,9 +43,9 @@ export class MessageInteractionService {
|
||||
private reactionService: MessageReactionService;
|
||||
|
||||
constructor(
|
||||
private channelRepository: IChannelRepository,
|
||||
private userRepository: IUserRepository,
|
||||
guildRepository: IGuildRepositoryAggregate,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
guildRepository: GuildRepository,
|
||||
private gatewayService: IGatewayService,
|
||||
snowflakeService: ISnowflakeService,
|
||||
messagePersistenceService: MessagePersistenceService,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AttachmentUploadTraceRepository} from '@app/api/channel/repositories/message/AttachmentUploadTraceRepository';
|
||||
import {CrosspostPropagation} from '@app/api/channel/services/message/CrosspostPropagation';
|
||||
import {MessageAnonymizationService} from '@app/api/channel/services/message/MessageAnonymizationService';
|
||||
@@ -21,9 +21,9 @@ import {MessageSystemService} from '@app/api/channel/services/message/MessageSys
|
||||
import {MessageValidationService} from '@app/api/channel/services/message/MessageValidationService';
|
||||
import {MessageWriteLock} from '@app/api/channel/services/message/MessageWriteLock';
|
||||
import {ThreadMessageActivity} from '@app/api/channel/services/message/ThreadMessageActivity';
|
||||
import type {IFavoriteMemeRepository} from '@app/api/favorite_meme/IFavoriteMemeRepository';
|
||||
import type {FavoriteMemeRepository} from '@app/api/favorite_meme/FavoriteMemeRepository';
|
||||
import type {GuildAuditLogService} from '@app/api/guild/GuildAuditLogService';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {IPurgeQueue} from '@app/api/infrastructure/CachePurgeQueue';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {IMediaService} from '@app/api/infrastructure/IMediaService';
|
||||
@@ -32,7 +32,7 @@ import type {IStorageService} from '@app/api/infrastructure/IStorageService';
|
||||
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import type {ReadStateService} from '@app/api/read_state/ReadStateService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {WorkerTaskName} from '@app/api/worker/WorkerLaneConfig';
|
||||
import type {ICacheService} from '@pkgs/cache/src/ICacheService';
|
||||
import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
|
||||
@@ -58,9 +58,9 @@ export class MessageService {
|
||||
public readonly threadActivity: ThreadMessageActivity;
|
||||
|
||||
constructor(
|
||||
channelRepository: IChannelRepositoryAggregate,
|
||||
userRepository: IUserRepository,
|
||||
guildRepository: IGuildRepositoryAggregate,
|
||||
channelRepository: ChannelRepository,
|
||||
userRepository: UserRepository,
|
||||
guildRepository: GuildRepository,
|
||||
userCacheService: UserCacheService,
|
||||
readStateService: ReadStateService,
|
||||
cacheService: ICacheService,
|
||||
@@ -71,7 +71,7 @@ export class MessageService {
|
||||
snowflakeService: ISnowflakeService,
|
||||
rateLimitService: IRateLimitService,
|
||||
purgeQueue: IPurgeQueue,
|
||||
favoriteMemeRepository: IFavoriteMemeRepository,
|
||||
favoriteMemeRepository: FavoriteMemeRepository,
|
||||
guildAuditLogService: GuildAuditLogService,
|
||||
persistenceService: MessagePersistenceService,
|
||||
attachmentUploadTraceRepository: AttachmentUploadTraceRepository,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {ChannelID, GuildID, RoleID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createChannelID, createGuildID, createRoleID, createUserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {
|
||||
enqueueChannelFollowerRemoval,
|
||||
scheduleDeletedChannelFollowerRemoval,
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
import {resolveExperimentTargeting} from '@app/api/experiment/ExperimentTargeting';
|
||||
import type {GuildAuditLogService} from '@app/api/guild/GuildAuditLogService';
|
||||
import {mapGuildToGuildResponse} from '@app/api/guild/GuildModel';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import {ChannelHelpers} from '@app/api/guild/services/channel/ChannelHelpers';
|
||||
import {createGuildMfaEnforcer} from '@app/api/guild/services/GuildMfaEnforcement';
|
||||
import {hasThreadPermissionBits, resolveProtectedBitActor} from '@app/api/guild/services/ThreadPermissionBits';
|
||||
@@ -44,7 +44,7 @@ import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {ILiveKitService} from '@app/api/infrastructure/ILiveKitService';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {IVoiceRoomStore} from '@app/api/infrastructure/IVoiceRoomStore';
|
||||
import type {IInviteRepository} from '@app/api/invite/IInviteRepository';
|
||||
import type {InviteRepository} from '@app/api/invite/InviteRepository';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import {createLimitMatchContext} from '@app/api/limits/LimitMatchContextBuilder';
|
||||
@@ -54,13 +54,13 @@ import type {Channel} from '@app/api/models/Channel';
|
||||
import {ChannelPermissionOverwrite} from '@app/api/models/ChannelPermissionOverwrite';
|
||||
import type {ThreadState} from '@app/api/models/ThreadState';
|
||||
import {deleteChannelMessageSearchDocuments} from '@app/api/search/MessageSearchIndexCleanup';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {serializeChannelForAudit} from '@app/api/utils/AuditSerializationUtils';
|
||||
import {applyProtectedOverwriteBits, permissionWriteMask, protectedThreadBits} from '@app/api/utils/featureUtils';
|
||||
import {overwriteGrantedBits} from '@app/api/utils/PermissionUtils';
|
||||
import type {VoiceAvailabilityService} from '@app/api/voice/VoiceAvailabilityService';
|
||||
import type {VoiceRegionAvailability} from '@app/api/voice/VoiceModel';
|
||||
import type {IWebhookRepository} from '@app/api/webhook/IWebhookRepository';
|
||||
import type {WebhookRepository} from '@app/api/webhook/WebhookRepository';
|
||||
import {AuditLogActionType} from '@fluxer/constants/src/AuditLogActionType';
|
||||
import {
|
||||
ANNOUNCEMENT_CONVERTIBLE_CHANNEL_TYPES,
|
||||
@@ -123,8 +123,8 @@ function assertOverwriteTarget(channel: Channel, viewer: ThreadViewer | undefine
|
||||
|
||||
export class ChannelOperationsService {
|
||||
constructor(
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private userRepository: IUserRepository,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
private gatewayService: IGatewayService,
|
||||
private channelAuthService: ChannelAuthService,
|
||||
private channelUtilsService: ChannelUtilsService,
|
||||
@@ -132,9 +132,9 @@ export class ChannelOperationsService {
|
||||
private liveKitService: ILiveKitService,
|
||||
private voiceAvailabilityService: VoiceAvailabilityService | null,
|
||||
private readonly guildAuditLogService: GuildAuditLogService,
|
||||
private inviteRepository: IInviteRepository,
|
||||
private webhookRepository: IWebhookRepository,
|
||||
private guildRepository: IGuildRepositoryAggregate,
|
||||
private inviteRepository: InviteRepository,
|
||||
private webhookRepository: WebhookRepository,
|
||||
private guildRepository: GuildRepository,
|
||||
private limitConfigService: LimitConfigService,
|
||||
private rateLimitService: IRateLimitService,
|
||||
private cacheService: ICacheService,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {mapChannelToResponse} from '@app/api/channel/ChannelMappers';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {dispatchChannelEvent} from '@app/api/channel/services/ChannelGatewayDispatch';
|
||||
import {dispatchMessageCreateBroadcast} from '@app/api/channel/services/message/MessageGatewayDispatch';
|
||||
import {purgeMessageAttachments} from '@app/api/channel/services/message/MessageHelpers';
|
||||
@@ -17,7 +17,7 @@ import type {Message} from '@app/api/models/Message';
|
||||
|
||||
export class ChannelUtilsService {
|
||||
constructor(
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userCacheService: UserCacheService,
|
||||
private storageService: IStorageService,
|
||||
private gatewayService: IGatewayService,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {ChannelID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createMessageID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {ChannelUtilsService} from '@app/api/channel/services/channel_data/ChannelUtilsService';
|
||||
import type {MessagePersistenceService} from '@app/api/channel/services/message/MessagePersistenceService';
|
||||
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
|
||||
@@ -11,7 +11,7 @@ import {contentModerationService} from '@app/api/infrastructure/ContentModeratio
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {canUserAccessNsfwContent} from '@app/api/utils/AgeUtils';
|
||||
import {ChannelTypes, MessageTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {GroupDmMatureContentIneligibleError} from '@fluxer/errors/src/domains/channel/GroupDmMatureContentIneligibleError';
|
||||
@@ -24,8 +24,8 @@ import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError
|
||||
|
||||
export class GroupDmUpdateService {
|
||||
constructor(
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private userRepository: IUserRepository,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
private avatarService: AvatarService,
|
||||
private snowflakeService: ISnowflakeService,
|
||||
private channelUtilsService: ChannelUtilsService,
|
||||
|
||||
@@ -4,11 +4,11 @@ import {randomInt} from 'node:crypto';
|
||||
import type {ChannelID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createMessageID} from '@app/api/BrandedTypes';
|
||||
import {mapChannelToResponse} from '@app/api/channel/ChannelMappers';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {dispatchChannelDelete} from '@app/api/channel/services/group_dm/GroupDmHelpers';
|
||||
import {dispatchMessageCreateBroadcast} from '@app/api/channel/services/message/MessageGatewayDispatch';
|
||||
import type {MessagePersistenceService} from '@app/api/channel/services/message/MessagePersistenceService';
|
||||
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 {ILiveKitService} from '@app/api/infrastructure/ILiveKitService';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
@@ -23,7 +23,7 @@ import type {Channel} from '@app/api/models/Channel';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {deleteChannelMessageSearchDocuments} from '@app/api/search/MessageSearchIndexCleanup';
|
||||
import {assertAccountNotLimited} from '@app/api/user/AccountLimit';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {canUserAccessNsfwContent} from '@app/api/utils/AgeUtils';
|
||||
import {UserPermissionUtils} from '@app/api/utils/UserPermissionUtils';
|
||||
import {ChannelTypes, MessageTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -44,9 +44,9 @@ export class GroupDmOperationsService {
|
||||
private readonly userPermissionUtils: UserPermissionUtils;
|
||||
|
||||
constructor(
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private userRepository: IUserRepository,
|
||||
guildRepository: IGuildRepositoryAggregate,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
guildRepository: GuildRepository,
|
||||
private userCacheService: UserCacheService,
|
||||
private gatewayService: IGatewayService,
|
||||
private snowflakeService: ISnowflakeService,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {ChannelID, MessageID} from '@app/api/BrandedTypes';
|
||||
import {createChannelID, createGuildID, createMessageID, createUserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AuthenticatedChannel} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
import {MessagePinService} from '@app/api/channel/services/interaction/MessagePinService';
|
||||
import type {MessagePersistenceService} from '@app/api/channel/services/message/MessagePersistenceService';
|
||||
@@ -69,7 +69,7 @@ function createPinService({canReadHistory, pins}: {canReadHistory: boolean; pins
|
||||
.slice(0, limit);
|
||||
},
|
||||
},
|
||||
} as unknown as IChannelRepositoryAggregate;
|
||||
} as unknown as ChannelRepository;
|
||||
const channel = {id: CHANNEL_ID, type: ChannelTypes.GUILD_TEXT, guildId: GUILD_ID};
|
||||
const authChannel: AuthenticatedChannel = {
|
||||
channel: channel as AuthenticatedChannel['channel'],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createMessageID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AuthenticatedChannel} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
import {dispatchChannelEvent} from '@app/api/channel/services/ChannelGatewayDispatch';
|
||||
import {MessageInteractionBase} from '@app/api/channel/services/interaction/MessageInteractionBase';
|
||||
@@ -35,7 +35,7 @@ const PIN_LIST_UNBOUNDED_TIMESTAMP = new Date('9999-12-31T23:59:59.999Z');
|
||||
export class MessagePinService extends MessageInteractionBase {
|
||||
constructor(
|
||||
gatewayService: IGatewayService,
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private channelRepository: ChannelRepository,
|
||||
private snowflakeService: ISnowflakeService,
|
||||
private messagePersistenceService: MessagePersistenceService,
|
||||
private readonly guildAuditLogService: GuildAuditLogService,
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import {requireEmailVerified} from '@app/api/auth/EmailVerificationUtils';
|
||||
import {createEmojiID, type MessageID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AuthenticatedChannel} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
import {dispatchChannelEvent} from '@app/api/channel/services/ChannelGatewayDispatch';
|
||||
import {MessageInteractionBase, type ParsedEmoji} from '@app/api/channel/services/interaction/MessageInteractionBase';
|
||||
import {assertThreadInteractionAllowed} from '@app/api/channel/services/thread/ThreadInteractionGuards';
|
||||
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 {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import {resolveLimitSafe} from '@app/api/limits/LimitConfigUtils';
|
||||
@@ -16,7 +16,7 @@ import type {Channel} from '@app/api/models/Channel';
|
||||
import type {MessageReaction} from '@app/api/models/MessageReaction';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import {assertAccountNotLimited} from '@app/api/user/AccountLimit';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {mapUserToPartialResponse} from '@app/api/user/UserMappers';
|
||||
import {assertGuildMemberCanCommunicate} from '@app/api/utils/GuildCommunicationUtils';
|
||||
import {Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -40,9 +40,9 @@ const REACTION_CUSTOM_EMOJI_REGEX = /^(.+):(\d+)$/;
|
||||
export class MessageReactionService extends MessageInteractionBase {
|
||||
constructor(
|
||||
gatewayService: IGatewayService,
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private userRepository: IUserRepository,
|
||||
private guildRepository: IGuildRepositoryAggregate,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
private guildRepository: GuildRepository,
|
||||
private limitConfigService: LimitConfigService,
|
||||
) {
|
||||
super(gatewayService);
|
||||
|
||||
@@ -4,7 +4,7 @@ import {createHash} from 'node:crypto';
|
||||
import type {ChannelID, GuildID, MessageID, UserID, WebhookID} from '@app/api/BrandedTypes';
|
||||
import {createMessageID, createUserID} from '@app/api/BrandedTypes';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {CrosspostedMessageKey} from '@app/api/channel/repositories/ICrosspostedMessageRepository';
|
||||
import type {CrosspostedMessageKey} from '@app/api/channel/repositories/CrosspostedMessageRepository';
|
||||
import {dispatchChannelEvent} from '@app/api/channel/services/ChannelGatewayDispatch';
|
||||
import {
|
||||
collectEmbedContentHashes,
|
||||
@@ -36,7 +36,7 @@ import type {
|
||||
MessageEmbedChild,
|
||||
MessageStickerItem,
|
||||
} from '@app/api/database/types/MessageTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {AvatarService} from '@app/api/infrastructure/AvatarService';
|
||||
import type {IPurgeQueue} from '@app/api/infrastructure/CachePurgeQueue';
|
||||
import {contentModerationService, type ModerationContext} from '@app/api/infrastructure/ContentModerationService';
|
||||
@@ -49,8 +49,8 @@ import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import type {Webhook} from '@app/api/models/Webhook';
|
||||
import {deleteMessageSearchDocuments} from '@app/api/search/MessageSearchIndexCleanup';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {IWebhookRepository} from '@app/api/webhook/IWebhookRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {WebhookRepository} from '@app/api/webhook/WebhookRepository';
|
||||
import {
|
||||
CROSSPOST_PENDING_RECLAIM_AFTER_MS,
|
||||
CROSSPOST_SOURCE_DELETED_CONTENT,
|
||||
@@ -109,9 +109,9 @@ type CrosspostCopyPayloadResult = {kind: 'blocked'} | {kind: 'ready'; payload: C
|
||||
|
||||
export interface CrosspostDeliveryDeps {
|
||||
channelRepository: ChannelRepository;
|
||||
webhookRepository: IWebhookRepository;
|
||||
userRepository: IUserRepository;
|
||||
guildRepository: IGuildRepositoryAggregate;
|
||||
webhookRepository: WebhookRepository;
|
||||
userRepository: UserRepository;
|
||||
guildRepository: GuildRepository;
|
||||
gatewayService: IGatewayService;
|
||||
storageService: IStorageService;
|
||||
avatarService: AvatarService;
|
||||
@@ -222,7 +222,7 @@ export class CrosspostDeliveryService {
|
||||
async loadSource(channelId: ChannelID, messageId: MessageID): Promise<CrosspostSourceContext | null> {
|
||||
const message = await this.deps.channelRepository.messages.getMessage(channelId, messageId);
|
||||
if (!message) return null;
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel?.guildId) return null;
|
||||
const guild = await this.loadGuild(channel.guildId);
|
||||
if (!guild) return null;
|
||||
@@ -316,7 +316,7 @@ export class CrosspostDeliveryService {
|
||||
const copyChannel =
|
||||
existing.target_channel_id === target.channel.id
|
||||
? target.channel
|
||||
: await this.deps.channelRepository.findUnique(existing.target_channel_id);
|
||||
: await this.deps.channelRepository.channelData.findUnique(existing.target_channel_id);
|
||||
if (copyChannel) {
|
||||
await this.announceCopy(copyChannel, copy);
|
||||
}
|
||||
@@ -370,7 +370,7 @@ export class CrosspostDeliveryService {
|
||||
sourceFingerprint: context.fingerprint,
|
||||
});
|
||||
if (!marked) {
|
||||
await this.deps.channelRepository.deleteMessage(target.channel.id, messageId, createUserID(0n));
|
||||
await this.deps.channelRepository.messages.deleteMessage(target.channel.id, messageId, createUserID(0n));
|
||||
return 'skipped';
|
||||
}
|
||||
await this.announceCopy(target.channel, copy);
|
||||
@@ -417,7 +417,7 @@ export class CrosspostDeliveryService {
|
||||
});
|
||||
return message;
|
||||
} catch (error) {
|
||||
await this.deps.channelRepository.deleteMessage(target.channel.id, messageId, createUserID(0n));
|
||||
await this.deps.channelRepository.messages.deleteMessage(target.channel.id, messageId, createUserID(0n));
|
||||
await this.deps.channelRepository.crossposts.delete(
|
||||
{sourceMessageId: context.message.id, webhookId: webhook.id},
|
||||
{state: 'pending', target_message_id: messageId},
|
||||
@@ -604,7 +604,7 @@ export class CrosspostDeliveryService {
|
||||
},
|
||||
);
|
||||
if (!updated) return;
|
||||
const channel = await this.deps.channelRepository.findUnique(row.target_channel_id);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(row.target_channel_id);
|
||||
if (!channel) return;
|
||||
await dispatchMessageUpdateBroadcast({gatewayService: this.deps.gatewayService, channel, message: updated});
|
||||
if (channel.indexedAt != null) {
|
||||
@@ -663,7 +663,7 @@ export class CrosspostDeliveryService {
|
||||
return;
|
||||
}
|
||||
if (outcome.kind === 'marked') {
|
||||
const channel = await this.deps.channelRepository.findUnique(row.target_channel_id);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(row.target_channel_id);
|
||||
if (channel) {
|
||||
await dispatchMessageUpdateBroadcast({
|
||||
gatewayService: this.deps.gatewayService,
|
||||
@@ -686,7 +686,7 @@ export class CrosspostDeliveryService {
|
||||
row.target_message_id,
|
||||
async (fresh) => {
|
||||
if (!fresh) return null;
|
||||
await this.deps.channelRepository.deleteMessage(
|
||||
await this.deps.channelRepository.messages.deleteMessage(
|
||||
fresh.channelId,
|
||||
fresh.id,
|
||||
fresh.authorId ?? createUserID(0n),
|
||||
@@ -701,7 +701,7 @@ export class CrosspostDeliveryService {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const channel = await this.deps.channelRepository.findUnique(row.target_channel_id);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(row.target_channel_id);
|
||||
if (channel) {
|
||||
await dispatchChannelEvent({
|
||||
gatewayService: this.deps.gatewayService,
|
||||
@@ -717,7 +717,7 @@ export class CrosspostDeliveryService {
|
||||
async deleteSourceMessage(channelId: ChannelID, messageId: MessageID): Promise<void> {
|
||||
const removed = await this.writeLock.withFreshMessage(channelId, messageId, async (fresh) => {
|
||||
if (!fresh) return null;
|
||||
await this.deps.channelRepository.deleteMessage(
|
||||
await this.deps.channelRepository.messages.deleteMessage(
|
||||
channelId,
|
||||
messageId,
|
||||
fresh.authorId ?? createUserID(0n),
|
||||
@@ -727,7 +727,7 @@ export class CrosspostDeliveryService {
|
||||
});
|
||||
if (!removed) return;
|
||||
await purgeMessageAttachments(removed, this.deps.storageService, this.deps.purgeQueue);
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
|
||||
if (channel) {
|
||||
await dispatchChannelEvent({
|
||||
gatewayService: this.deps.gatewayService,
|
||||
@@ -877,7 +877,7 @@ export class CrosspostDeliveryService {
|
||||
}
|
||||
|
||||
private async loadSyncTarget(channelId: ChannelID): Promise<CrosspostTarget | null> {
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
|
||||
if (!channel?.guildId) return null;
|
||||
const guild = await this.loadGuild(channel.guildId);
|
||||
if (!guild) return null;
|
||||
@@ -886,7 +886,7 @@ export class CrosspostDeliveryService {
|
||||
|
||||
private async loadParent(channel: Channel): Promise<Channel | null> {
|
||||
if (!channel.parentId || channel.type === ChannelTypes.GUILD_CATEGORY) return null;
|
||||
return this.deps.channelRepository.findUnique(channel.parentId);
|
||||
return this.deps.channelRepository.channelData.findUnique(channel.parentId);
|
||||
}
|
||||
|
||||
private async loadGuild(guildId: GuildID): Promise<GuildResponse | null> {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import {createGuildID, type GuildID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import {mapGuildToPartialResponse} from '@app/api/guild/GuildModel';
|
||||
import type {IGuildDiscoveryRepository} from '@app/api/guild/repositories/GuildDiscoveryRepository';
|
||||
import type {IGuildDataRepository} from '@app/api/guild/repositories/IGuildDataRepository';
|
||||
import type {GuildDiscoveryRepository} from '@app/api/guild/repositories/GuildDiscoveryRepository';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import {MessageFlags, MessageTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -45,8 +45,8 @@ function getCrosspostSourceGuildId(message: MessageResponse): GuildID | null {
|
||||
|
||||
export class CrosspostSourceService {
|
||||
constructor(
|
||||
private readonly guildRepository: IGuildDataRepository,
|
||||
private readonly discoveryRepository: IGuildDiscoveryRepository,
|
||||
private readonly guildRepository: GuildRepository,
|
||||
private readonly discoveryRepository: GuildDiscoveryRepository,
|
||||
private readonly gatewayService: IGatewayService,
|
||||
private readonly cacheService: ICacheService,
|
||||
) {}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {UserID} from '@app/api/BrandedTypes';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
|
||||
export type DmSearchScope = 'all_dms' | 'open_dms';
|
||||
@@ -10,7 +10,7 @@ export type DmSearchScope = 'all_dms' | 'open_dms';
|
||||
interface DmScopeOptions {
|
||||
scope: DmSearchScope;
|
||||
userId: UserID;
|
||||
userRepository: IUserRepository;
|
||||
userRepository: UserRepository;
|
||||
includeChannel?: Channel | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {getGuildRepository, getInviteRepository} from '@app/api/middleware/Servi
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {findInvites} from '@app/api/utils/InviteUtils';
|
||||
import {extractLinkHosts} from '@app/api/utils/UrlNormalizer';
|
||||
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -67,7 +67,7 @@ export interface MessageCreatedActivity {
|
||||
dmRecipientId: UserID | null;
|
||||
channelHadMessages: boolean;
|
||||
delivered: boolean;
|
||||
userRepository: Pick<IUserRepository, 'getRelationship'>;
|
||||
userRepository: Pick<UserRepository, 'getRelationship'>;
|
||||
}
|
||||
|
||||
function attachmentMeta(message: Message): Array<AttachmentMeta> {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
|
||||
export class MessageAnonymizationService {
|
||||
constructor(private channelRepository: IChannelRepositoryAggregate) {}
|
||||
constructor(private channelRepository: ChannelRepository) {}
|
||||
|
||||
async anonymizeMessagesByAuthor(originalAuthorId: UserID, newAuthorId: UserID): Promise<void> {
|
||||
const CHUNK_SIZE = 100;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {GuildID, UserID, WebhookID} from '@app/api/BrandedTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {LimitConfigService} from '@app/api/limits/LimitConfigService';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import * as EmojiUtils from '@app/api/utils/EmojiUtils';
|
||||
import {ChannelTypes, GUILD_TEXT_BASED_CHANNEL_TYPES} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {GuildExplicitContentFilterTypes, GuildFeatures, GuildNSFWLevel} from '@fluxer/constants/src/GuildConstants';
|
||||
@@ -20,8 +20,8 @@ export interface DmNsfwContext {
|
||||
|
||||
export class MessageContentService {
|
||||
constructor(
|
||||
private userRepository: IUserRepository,
|
||||
private guildRepository: IGuildRepositoryAggregate,
|
||||
private userRepository: UserRepository,
|
||||
private guildRepository: GuildRepository,
|
||||
private limitConfigService: LimitConfigService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ChannelID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {AuthenticatedChannel} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
import {collectEmbedContentHashes} from '@app/api/channel/services/message/CrosspostEmbedObjects';
|
||||
import {
|
||||
@@ -36,7 +36,7 @@ import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponse
|
||||
import type {IRateLimitService} from '@pkgs/rate_limit/src/IRateLimitService';
|
||||
|
||||
interface MessageCrosspostServiceDeps {
|
||||
channelRepository: IChannelRepositoryAggregate;
|
||||
channelRepository: ChannelRepository;
|
||||
channelAuthService: MessageChannelAuthService;
|
||||
dispatchService: MessageDispatchService;
|
||||
rateLimitService: IRateLimitService;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {ChannelID, GuildID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createMessageID, createUserID} from '@app/api/BrandedTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {CrosspostPropagation} from '@app/api/channel/services/message/CrosspostPropagation';
|
||||
import type {MessageChannelAuthService} from '@app/api/channel/services/message/MessageChannelAuthService';
|
||||
import type {MessageDispatchService} from '@app/api/channel/services/message/MessageDispatchService';
|
||||
@@ -37,7 +37,7 @@ import {createSnowflakeFromTimestamp} from '@fluxer/snowflake/src/Snowflake';
|
||||
import {ms} from 'itty-time';
|
||||
|
||||
interface MessageDeleteServiceDeps {
|
||||
channelRepository: IChannelRepositoryAggregate;
|
||||
channelRepository: ChannelRepository;
|
||||
storageService: IStorageService;
|
||||
purgeQueue: IPurgeQueue;
|
||||
validationService: MessageValidationService;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {type ChannelID, createGuildID, createUserID, type MessageID, type UserID} from '@app/api/BrandedTypes';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {MessageUpdateRequest} from '@app/api/channel/MessageTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {AuthenticatedChannel} from '@app/api/channel/services/AuthenticatedChannel';
|
||||
import type {CrosspostPropagation} from '@app/api/channel/services/message/CrosspostPropagation';
|
||||
import {emitMessageUpdated} from '@app/api/channel/services/message/MessageActivity';
|
||||
@@ -22,8 +22,8 @@ import {Logger} from '@app/api/Logger';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import {assertAccountNotLimited} from '@app/api/user/AccountLimit';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import {assertMayStartConversation, oneToOneDmRecipient} from '@app/api/user/NewConversationLimit';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {isDirectDeliverySuppressed} from '@app/api/user/UserHelpers';
|
||||
import {assertGuildMemberCanCommunicate} from '@app/api/utils/GuildCommunicationUtils';
|
||||
import {Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -40,8 +40,8 @@ interface EditMessageResult {
|
||||
}
|
||||
|
||||
interface MessageEditServiceDeps {
|
||||
channelRepository: IChannelRepositoryAggregate;
|
||||
userRepository: IUserRepository;
|
||||
channelRepository: ChannelRepository;
|
||||
userRepository: UserRepository;
|
||||
validationService: MessageValidationService;
|
||||
persistenceService: MessagePersistenceService;
|
||||
channelAuthService: MessageChannelAuthService;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type {AttachmentID, ChannelID, MessageID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createAttachmentID, userIdToChannelId} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {forEachEmbedMedia} from '@app/api/channel/services/message/CrosspostEmbedObjects';
|
||||
import type {
|
||||
MessageSnapshot as CassandraMessageSnapshot,
|
||||
@@ -479,7 +479,7 @@ export function countedThreadMessages(channel: Channel, messageIds: Array<Messag
|
||||
}
|
||||
|
||||
export async function decrementThreadMessageCount(
|
||||
channelRepository: IChannelRepositoryAggregate,
|
||||
channelRepository: ChannelRepository,
|
||||
channel: Channel | null,
|
||||
messageIds: Array<MessageID>,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
} from '@app/api/BrandedTypes';
|
||||
import {isOperationDisabled, isPersonalNotesChannel} from '@app/api/channel/services/message/MessageHelpers';
|
||||
import type {MessageResponseDataService} from '@app/api/channel/services/message/MessageResponseDataService';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {GatewayChannelMention, IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import type {WorkerTaskName} from '@app/api/worker/WorkerLaneConfig';
|
||||
import {WorkerQueueOverflowError} from '@app/api/worker/WorkerQueueOverflowError';
|
||||
import {ChannelTypes, MessageTypes, SENDABLE_MESSAGE_FLAGS} from '@fluxer/constants/src/ChannelConstants';
|
||||
@@ -55,8 +55,8 @@ interface MentionableUser {
|
||||
|
||||
export class MessageMentionService {
|
||||
constructor(
|
||||
private userRepository: IUserRepository,
|
||||
private guildRepository: IGuildRepositoryAggregate,
|
||||
private userRepository: UserRepository,
|
||||
private guildRepository: GuildRepository,
|
||||
private gatewayService: IGatewayService,
|
||||
private workerService: IWorkerService<WorkerTaskName>,
|
||||
private responseDataService: MessageResponseDataService,
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import type {ChannelID, UserID} from '@app/api/BrandedTypes';
|
||||
import {createAttachmentID, createChannelID, createMemeID, createMessageID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import {makeAttachmentCdnKey} from '@app/api/channel/services/message/MessageHelpers';
|
||||
import type {MessageAttachment} from '@app/api/database/types/MessageTypes';
|
||||
import type {IFavoriteMemeRepository} from '@app/api/favorite_meme/IFavoriteMemeRepository';
|
||||
import type {FavoriteMemeRepository} from '@app/api/favorite_meme/FavoriteMemeRepository';
|
||||
import type {IMediaService} from '@app/api/infrastructure/IMediaService';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
import type {IStorageService} from '@app/api/infrastructure/IStorageService';
|
||||
@@ -22,11 +22,11 @@ import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidat
|
||||
import type {ICacheService} from '@pkgs/cache/src/ICacheService';
|
||||
|
||||
interface MessageOperationsHelpersDeps {
|
||||
channelRepository: IChannelRepositoryAggregate;
|
||||
channelRepository: ChannelRepository;
|
||||
cacheService: ICacheService;
|
||||
storageService: IStorageService;
|
||||
snowflakeService: ISnowflakeService;
|
||||
favoriteMemeRepository: IFavoriteMemeRepository;
|
||||
favoriteMemeRepository: FavoriteMemeRepository;
|
||||
mediaService: IMediaService;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import type {ChannelID, GuildID, MessageID, RoleID, StickerID, UserID, WebhookID
|
||||
import {createAttachmentID, createGuildID} from '@app/api/BrandedTypes';
|
||||
import {Config} from '@app/api/Config';
|
||||
import type {AttachmentToProcess} from '@app/api/channel/AttachmentDTOs';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {MessageUpdateRequest} from '@app/api/channel/MessageTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {AttachmentUploadTraceRepository} from '@app/api/channel/repositories/message/AttachmentUploadTraceRepository';
|
||||
import {AttachmentProcessingService} from '@app/api/channel/services/message/AttachmentProcessingService';
|
||||
import {type DmNsfwContext, MessageContentService} from '@app/api/channel/services/message/MessageContentService';
|
||||
@@ -27,7 +27,7 @@ import type {
|
||||
MessageReference,
|
||||
MessageStickerItem,
|
||||
} from '@app/api/database/types/MessageTypes';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import type {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
||||
import type {EmbedService} from '@app/api/infrastructure/EmbedService';
|
||||
import type {IMediaService, MediaProxyNsfwMode} from '@app/api/infrastructure/IMediaService';
|
||||
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
||||
@@ -40,7 +40,7 @@ import type {Message} from '@app/api/models/Message';
|
||||
import type {MessageSnapshot} from '@app/api/models/MessageSnapshot';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {ReadStateService} from '@app/api/read_state/ReadStateService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {hasVisibleContent} from '@app/api/utils/StringUtils';
|
||||
import {MessageFlags, Permissions, SENDABLE_MESSAGE_FLAGS} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {ATTACHMENT_MAX_SIZE_NON_PREMIUM} from '@fluxer/constants/src/LimitConstants';
|
||||
@@ -129,9 +129,9 @@ export class MessagePersistenceService {
|
||||
private readonly attachmentDecayService: AttachmentDecayService;
|
||||
|
||||
constructor(
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private userRepository: IUserRepository,
|
||||
guildRepository: IGuildRepositoryAggregate,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
guildRepository: GuildRepository,
|
||||
private embedService: EmbedService,
|
||||
private readonly storageService: IStorageService,
|
||||
attachmentUploadTraceRepository: AttachmentUploadTraceRepository,
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import {Channel} from '@app/api/models/Channel';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
@@ -62,7 +62,7 @@ function buildService(): {service: MessageProcessingService; opened: Array<Chann
|
||||
openPrivateChannelForUser: async (_userId: UserID, channel: Channel) => {
|
||||
opened.push(channel);
|
||||
},
|
||||
} as unknown as IUserRepository;
|
||||
} as unknown as UserRepository;
|
||||
const userCacheService = {
|
||||
getUserPartialResponses: async (userIds: Array<UserID>) =>
|
||||
new Map(userIds.map((userId) => [userId, {id: userId.toString()}])),
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import {type ChannelID, createChannelID, createGuildID, type MessageID, type UserID} from '@app/api/BrandedTypes';
|
||||
import {mapChannelToResponse} from '@app/api/channel/ChannelMappers';
|
||||
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
||||
import type {MessageRequest, MessageUpdateRequest} from '@app/api/channel/MessageTypes';
|
||||
import type {IChannelRepositoryAggregate} from '@app/api/channel/repositories/IChannelRepositoryAggregate';
|
||||
import type {MessageDispatchService} from '@app/api/channel/services/message/MessageDispatchService';
|
||||
import {isPersonalNotesChannel} from '@app/api/channel/services/message/MessageHelpers';
|
||||
import type {MessageMentionService} from '@app/api/channel/services/message/MessageMentionService';
|
||||
@@ -17,7 +17,7 @@ import {Channel} from '@app/api/models/Channel';
|
||||
import type {Message} from '@app/api/models/Message';
|
||||
import type {User} from '@app/api/models/User';
|
||||
import type {ReadStateService} from '@app/api/read_state/ReadStateService';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {ChannelTypes, MessageFlags} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {CannotEditOtherUserMessageError} from '@fluxer/errors/src/domains/channel/CannotEditOtherUserMessageError';
|
||||
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
||||
@@ -42,8 +42,8 @@ function channelWithLastMessageId(channel: Channel, messageId: MessageID): Chann
|
||||
|
||||
export class MessageProcessingService {
|
||||
constructor(
|
||||
private channelRepository: IChannelRepositoryAggregate,
|
||||
private userRepository: IUserRepository,
|
||||
private channelRepository: ChannelRepository,
|
||||
private userRepository: UserRepository,
|
||||
private userCacheService: UserCacheService,
|
||||
private gatewayService: IGatewayService,
|
||||
private readStateService: ReadStateService,
|
||||
|
||||
Loaded 100 of 426 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user