Files
fluxer/packages/schema/src/domains/admin/AdminSchemas.tsx

1054 lines
37 KiB
TypeScript

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {SystemChannelFlags, SystemChannelFlagsDescriptions} from '@fluxer/constants/src/GuildConstants';
import {LIMIT_KEYS} from '@fluxer/constants/src/LimitConfigMetadata';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {GuildAdminResponse} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas';
import {UserAdminResponseSchema} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
import {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import {ChannelTypeSchema} from '@fluxer/schema/src/primitives/ChannelValidators';
import {
DefaultMessageNotificationsSchema,
GuildExplicitContentFilterSchema,
GuildMFALevelSchema,
GuildVerificationLevelSchema,
NSFWLevelSchema,
} from '@fluxer/schema/src/primitives/GuildValidators';
import {PermissionStringType} from '@fluxer/schema/src/primitives/PermissionValidators';
import {createQueryIntegerType} from '@fluxer/schema/src/primitives/QueryValidators';
import {
createBitflagInt32Type,
createInt32EnumType,
createNamedStringLiteralUnion,
createStringType,
Int32Type,
Int64StringType,
SnowflakeStringType,
SnowflakeType,
withOpenApiType,
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import {EmailType, PhoneNumberType} from '@fluxer/schema/src/primitives/UserValidators';
import {z} from 'zod';
const ReportStatusSchema = withOpenApiType(
createInt32EnumType(
[
[0, 'PENDING', 'Report is pending review'],
[1, 'RESOLVED', 'Report has been resolved'],
],
'The status of the report',
'ReportStatus',
),
'ReportStatus',
);
const ReportTypeSchema = withOpenApiType(
createInt32EnumType(
[
[0, 'MESSAGE', 'Report of a message'],
[1, 'USER', 'Report of a user'],
[2, 'GUILD', 'Report of a guild'],
],
'The type of entity being reported',
'ReportType',
),
'ReportType',
);
const SortOrderEnum = createNamedStringLiteralUnion(
[
['asc', 'asc', 'Ascending order (oldest first)'],
['desc', 'desc', 'Descending order (newest first)'],
],
'Sort order direction',
);
const AuditLogSortByEnum = createNamedStringLiteralUnion(
[
['createdAt', 'createdAt', 'Sort by creation timestamp'],
['relevance', 'relevance', 'Sort by search relevance score'],
],
'Field to sort audit logs by',
);
const ReportSortByEnum = createNamedStringLiteralUnion(
[
['createdAt', 'createdAt', 'Sort by creation timestamp'],
['reportedAt', 'reportedAt', 'Sort by report submission timestamp'],
['resolvedAt', 'resolvedAt', 'Sort by resolution timestamp'],
],
'Field to sort reports by',
);
const ArchiveListSubjectTypeEnum = createNamedStringLiteralUnion(
[
['user', 'user', 'List user archives'],
['guild', 'guild', 'List guild archives'],
['all', 'all', 'List all archives'],
],
'Type of archives to list',
);
const SearchIndexTypeEnum = createNamedStringLiteralUnion(
[
['guilds', 'guilds', 'Guild search index'],
['users', 'users', 'User search index'],
['reports', 'reports', 'Report search index'],
['audit_logs', 'audit_logs', 'Audit log search index'],
['channel_messages', 'channel_messages', 'Channel message search index'],
['guild_members', 'guild_members', 'Guild member search index'],
['favorite_memes', 'favorite_memes', 'Favourite meme search index'],
],
'Type of search index to refresh',
);
export const ListAuditLogsRequest = z.object({
admin_user_id: SnowflakeType.optional().describe('Filter by admin user who performed the action'),
target_type: createStringType(1, 64).optional().describe('Filter by target entity type'),
target_id: z.string().optional().describe('Filter by target entity ID (user, channel, role, invite code, etc.)'),
limit: z.number().int().min(1).max(200).default(50).describe('Maximum number of entries to return'),
offset: z.number().int().min(0).default(0).describe('Number of entries to skip'),
});
export type ListAuditLogsRequest = z.infer<typeof ListAuditLogsRequest>;
export const SearchAuditLogsRequest = z.object({
query: createStringType(1, 1024).optional().describe('Search query string'),
admin_user_id: SnowflakeType.optional().describe('Filter by admin user who performed the action'),
target_id: z.string().optional().describe('Filter by target entity ID (user, channel, role, invite code, etc.)'),
sort_by: AuditLogSortByEnum.default('createdAt'),
sort_order: SortOrderEnum.default('desc'),
limit: z.number().int().min(1).max(200).default(50).describe('Maximum number of entries to return'),
offset: z.number().int().min(0).default(0).describe('Number of entries to skip'),
});
export type SearchAuditLogsRequest = z.infer<typeof SearchAuditLogsRequest>;
export const SearchReportsRequest = z.object({
query: createStringType(1, 1024).optional().describe('Search query string'),
limit: z.number().int().min(1).max(200).default(50).describe('Maximum number of entries to return'),
offset: z.number().int().min(0).default(0).describe('Number of entries to skip'),
reporter_id: SnowflakeType.optional().describe('Filter by user who submitted the report'),
status: ReportStatusSchema.optional(),
report_type: ReportTypeSchema.optional(),
category: createStringType(1, 128).optional().describe('Filter by report category'),
reported_user_id: SnowflakeType.optional().describe('Filter by reported user ID'),
reported_guild_id: SnowflakeType.optional().describe('Filter by reported guild ID'),
reported_channel_id: SnowflakeType.optional().describe('Filter by reported channel ID'),
guild_context_id: SnowflakeType.optional().describe('Filter by guild context where report was made'),
resolved_by_admin_id: SnowflakeType.optional().describe('Filter by admin who resolved the report'),
sort_by: ReportSortByEnum.default('reportedAt'),
sort_order: SortOrderEnum.default('desc'),
});
export type SearchReportsRequest = z.infer<typeof SearchReportsRequest>;
export const ListReportsRequest = z.object({
status: ReportStatusSchema.optional(),
limit: z.number().int().min(1).max(200).optional().describe('Maximum number of reports to return'),
offset: z.number().int().min(0).optional().describe('Number of reports to skip'),
});
export type ListReportsRequest = z.infer<typeof ListReportsRequest>;
export const ResolveReportRequest = z.object({
report_id: SnowflakeType.describe('The ID of the report to resolve'),
public_comment: createStringType(0, 512).optional().describe('Public comment to include with the resolution'),
});
export type ResolveReportRequest = z.infer<typeof ResolveReportRequest>;
export const RefreshSearchIndexRequest = z.object({
index_type: SearchIndexTypeEnum,
guild_id: SnowflakeType.optional().describe('Specific guild ID to reindex'),
user_id: SnowflakeType.optional().describe('Specific user ID to reindex'),
});
export type RefreshSearchIndexRequest = z.infer<typeof RefreshSearchIndexRequest>;
export const GetIndexRefreshStatusRequest = z.object({
job_id: createStringType(1, 128).describe('ID of the index refresh job to check'),
});
export type GetIndexRefreshStatusRequest = z.infer<typeof GetIndexRefreshStatusRequest>;
export const PurgeGuildAssetsRequest = z.object({
ids: z.array(createStringType(1, 64)).max(100).describe('List of asset IDs to purge'),
});
export type PurgeGuildAssetsRequest = z.infer<typeof PurgeGuildAssetsRequest>;
export const TriggerUserArchiveRequest = z.object({
user_id: SnowflakeType.describe('ID of the user to archive'),
});
export type TriggerUserArchiveRequest = z.infer<typeof TriggerUserArchiveRequest>;
export const TriggerGuildArchiveRequest = z.object({
guild_id: SnowflakeType.describe('ID of the guild to archive'),
});
export type TriggerGuildArchiveRequest = z.infer<typeof TriggerGuildArchiveRequest>;
export const ListArchivesRequest = z.object({
subject_type: ArchiveListSubjectTypeEnum.default('all'),
subject_id: SnowflakeType.optional().describe('Filter by specific subject ID'),
requested_by: SnowflakeType.optional().describe('Filter by user who requested the archive'),
limit: z.number().min(1).max(200).default(50).describe('Maximum number of archives to return'),
include_expired: z.boolean().default(false).describe('Whether to include expired archives'),
});
export type ListArchivesRequest = z.infer<typeof ListArchivesRequest>;
const IP_OR_CIDR_REGEX = /^(?:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,2})?|(?:[a-fA-F0-9:]+)(?:\/\d{1,3})?)$/;
export const BanIpRequest = z.object({
ip: createStringType(1, 45)
.refine((value) => IP_OR_CIDR_REGEX.test(value), 'Must be a valid IPv4/IPv6 address or CIDR range')
.describe('IPv4/IPv6 address or CIDR range to ban'),
});
export type BanIpRequest = z.infer<typeof BanIpRequest>;
export const BanEmailRequest = z.object({
email: EmailType.describe('Email address to ban'),
});
export type BanEmailRequest = z.infer<typeof BanEmailRequest>;
export const BanPhoneRequest = z.object({
phone: PhoneNumberType.describe('Phone number to ban'),
});
export type BanPhoneRequest = z.infer<typeof BanPhoneRequest>;
export const ListBansRequest = z.object({
limit: z.number().int().min(1).max(500).default(200).describe('Maximum number of bans to return'),
});
export type ListBansRequest = z.infer<typeof ListBansRequest>;
export const IpBanListEntrySchema = z.object({
ip: createStringType(1, 45).describe('Banned IPv4/IPv6 address or CIDR range'),
reverse_dns: z.string().nullable().describe('Reverse DNS hostname for the IP, if available'),
});
export type IpBanListEntry = z.infer<typeof IpBanListEntrySchema>;
export const ListIpBansResponseSchema = z.object({
bans: z.array(IpBanListEntrySchema).max(500),
});
export type ListIpBansResponse = z.infer<typeof ListIpBansResponseSchema>;
export const ListEmailBansResponseSchema = z.object({
bans: z.array(EmailType).max(500),
});
export type ListEmailBansResponse = z.infer<typeof ListEmailBansResponseSchema>;
export const ListPhoneBansResponseSchema = z.object({
bans: z.array(PhoneNumberType).max(500),
});
export type ListPhoneBansResponse = z.infer<typeof ListPhoneBansResponseSchema>;
export const LegalHoldRequest = z.object({
expires_at: z.string().optional().describe('ISO 8601 timestamp when the legal hold expires'),
});
export type LegalHoldRequest = z.infer<typeof LegalHoldRequest>;
const MAX_CODES_PER_REQUEST = 100;
export const GiftProductTypeEnum = createNamedStringLiteralUnion(
[
['gift_1_month', 'gift_1_month', 'One month gift subscription'],
['gift_1_year', 'gift_1_year', 'One year gift subscription'],
],
'Type of gift subscription product',
);
export type GiftProductType = z.infer<typeof GiftProductTypeEnum>;
export const GenerateGiftCodesRequest = z.object({
count: z.number().int().min(1).max(MAX_CODES_PER_REQUEST).describe('Number of gift codes to generate'),
product_type: GiftProductTypeEnum.describe('Type of gift subscription'),
});
export type GenerateGiftCodesRequest = z.infer<typeof GenerateGiftCodesRequest>;
export const SsoConfigResponse = z.object({
enabled: z.boolean(),
display_name: z.string().nullable(),
issuer: z.string().nullable(),
authorization_url: z.string().nullable(),
token_url: z.string().nullable(),
userinfo_url: z.string().nullable(),
jwks_url: z.string().nullable(),
client_id: z.string().nullable(),
client_secret_set: z.boolean(),
scope: z.string().nullable(),
allowed_domains: z.array(z.string()).max(100),
auto_provision: z.boolean(),
redirect_uri: z.string().nullable(),
});
export type SsoConfigResponse = z.infer<typeof SsoConfigResponse>;
export const InstanceConfigResponse = z.object({
manual_review_enabled: z.boolean(),
manual_review_schedule_enabled: z.boolean(),
manual_review_schedule_start_hour_utc: Int32Type,
manual_review_schedule_end_hour_utc: Int32Type,
manual_review_active_now: z.boolean(),
registration_alerts_webhook_url: z.string().nullable(),
system_alerts_webhook_url: z.string().nullable(),
sso: SsoConfigResponse,
self_hosted: z.boolean(),
});
export type InstanceConfigResponse = z.infer<typeof InstanceConfigResponse>;
export const InstanceConfigUpdateRequest = z.object({
manual_review_enabled: z.boolean().optional(),
manual_review_schedule_enabled: z.boolean().optional(),
manual_review_schedule_start_hour_utc: z.number().int().min(0).max(23).optional(),
manual_review_schedule_end_hour_utc: z.number().int().min(0).max(23).optional(),
registration_alerts_webhook_url: z.url().nullish(),
system_alerts_webhook_url: z.url().nullish(),
sso: z
.object({
enabled: z.boolean().optional(),
display_name: z.string().nullish(),
issuer: z.string().nullish(),
authorization_url: z.string().nullish(),
token_url: z.string().nullish(),
userinfo_url: z.string().nullish(),
jwks_url: z.string().nullish(),
client_id: z.string().nullish(),
client_secret: z.string().nullish(),
scope: z.string().nullish(),
allowed_domains: z.array(z.string()).max(100).optional(),
auto_provision: z.boolean().optional(),
redirect_uri: z.url().nullish(),
})
.optional(),
});
export type InstanceConfigUpdateRequest = z.infer<typeof InstanceConfigUpdateRequest>;
const LimitKeySchema = z.enum(LIMIT_KEYS as unknown as [string, ...Array<string>]);
const LimitFilterSchema = z.object({
traits: z.array(z.string()).optional().describe('Trait filters that must match for the rule to apply'),
guildFeatures: z.array(z.string()).optional().describe('Guild feature flags required for the rule to apply'),
});
const LimitRuleSchema = z.object({
id: z.string().min(1).describe('Unique rule identifier'),
filters: LimitFilterSchema.optional().describe('Optional filters that scope the rule'),
limits: z
.record(z.string(), z.number().min(0))
.refine(
(limits) => {
const limitKeys = Object.keys(limits);
return limitKeys.every((key) => (LIMIT_KEYS as ReadonlyArray<string>).includes(key));
},
{message: 'Invalid limit key detected'},
)
.describe('Per-limit key values'),
});
const LimitConfigSchema = z.object({
traitDefinitions: z.array(z.string()).optional().describe('Trait definitions used by rules'),
rules: z.array(LimitRuleSchema).describe('Limit rules'),
});
export const LimitConfigUpdateRequest = z.object({
limit_config: LimitConfigSchema.describe('New limit configuration snapshot'),
});
export type LimitConfigUpdateRequest = z.infer<typeof LimitConfigUpdateRequest>;
export const SnowflakeReservationEntry = z.object({
email: z.string().describe('Email address the snowflake is reserved for'),
snowflake: z.string().describe('Reserved snowflake ID'),
updated_at: z.string().nullable().describe('ISO 8601 timestamp when the reservation was last updated'),
});
export type SnowflakeReservationEntry = z.infer<typeof SnowflakeReservationEntry>;
export const ListSnowflakeReservationsResponse = z.object({
reservations: z.array(SnowflakeReservationEntry).max(1000).describe('List of snowflake reservations'),
});
export type ListSnowflakeReservationsResponse = z.infer<typeof ListSnowflakeReservationsResponse>;
export const AddSnowflakeReservationRequest = z.object({
email: EmailType.describe('Email address to reserve the snowflake for'),
snowflake: SnowflakeType.transform((val) => val.toString()).describe('Snowflake ID to reserve'),
});
export type AddSnowflakeReservationRequest = z.infer<typeof AddSnowflakeReservationRequest>;
export const DeleteSnowflakeReservationRequest = z.object({
email: EmailType.describe('Email address of the reservation to delete'),
});
export type DeleteSnowflakeReservationRequest = z.infer<typeof DeleteSnowflakeReservationRequest>;
export const CreateSystemDmJobRequest = z.object({
content: z.string().min(1).max(4000).describe('Message content to send to users'),
registration_start: z.string().nullish().optional().describe('Only target users registered after this date'),
registration_end: z.string().nullish().optional().describe('Only target users registered before this date'),
excluded_guild_ids: z.array(SnowflakeType).max(100).optional().describe('Guild IDs whose members should be excluded'),
});
export type CreateSystemDmJobRequest = z.infer<typeof CreateSystemDmJobRequest>;
export const SystemDmJobsQueryRequest = z.object({
limit: createQueryIntegerType({minValue: 1, maxValue: 100, defaultValue: 20}).describe(
'Maximum number of jobs to return (1-100, default 20)',
),
before_job_id: SnowflakeType.optional().describe('Return jobs before this job ID'),
});
export type SystemDmJobsQueryRequest = z.infer<typeof SystemDmJobsQueryRequest>;
const SystemDmJobStatusEnum = createNamedStringLiteralUnion(
[
['pending', 'pending', 'Job is pending approval'],
['approved', 'approved', 'Job has been approved and is queued'],
['running', 'running', 'Job is currently running'],
['completed', 'completed', 'Job completed successfully'],
['failed', 'failed', 'Job failed'],
],
'Current status of the system DM job',
);
export const SystemDmJobResponse = z.object({
job_id: z.string().describe('Unique identifier for the job'),
status: SystemDmJobStatusEnum,
content: z.string().describe('Message content being sent'),
target_count: Int32Type.describe('Total number of users targeted'),
sent_count: Int32Type.describe('Number of messages successfully sent'),
failed_count: Int32Type.describe('Number of messages that failed to send'),
created_at: z.string().describe('ISO 8601 timestamp when the job was created'),
approved_at: z.string().nullish().describe('ISO 8601 timestamp when the job was approved'),
registration_start: z.string().nullish().describe('Registration date filter start'),
registration_end: z.string().nullish().describe('Registration date filter end'),
excluded_guild_ids: z.array(z.string()).max(100).describe('List of excluded guild IDs'),
last_error: z.string().nullish().describe('Last error message if the job failed'),
});
export type SystemDmJobResponse = z.infer<typeof SystemDmJobResponse>;
export const ListSystemDmJobsResponse = z.object({
jobs: z.array(SystemDmJobResponse).max(100).describe('List of system DM jobs'),
next_cursor: z.string().nullish().describe('Pagination cursor for the next page'),
});
export type ListSystemDmJobsResponse = z.infer<typeof ListSystemDmJobsResponse>;
export const CreateAdminApiKeyRequest = z.object({
name: z
.string()
.min(1)
.max(100)
.refine((value) => value.trim().length > 0, 'Name cannot be empty')
.describe('Display name for the API key'),
expires_in_days: z.number().int().min(1).max(365).optional().describe('Number of days until the key expires'),
acls: z.array(z.string()).max(100).describe('List of access control permissions for the key'),
});
export type CreateAdminApiKeyRequest = z.infer<typeof CreateAdminApiKeyRequest>;
export const CreateAdminApiKeyResponse = z.object({
key_id: z.string().describe('Unique identifier for the API key'),
key: z.string().describe('The generated API key secret (only shown once)'),
name: z.string().describe('Display name for the API key'),
created_at: z.string().describe('ISO 8601 timestamp when the key was created'),
expires_at: z.string().nullable().describe('ISO 8601 timestamp when the key expires, or null if no expiration'),
acls: z.array(z.string()).max(100).describe('List of access control permissions for the key'),
});
export type CreateAdminApiKeyResponse = z.infer<typeof CreateAdminApiKeyResponse>;
export const ListAdminApiKeyResponse = z.object({
key_id: z.string().describe('Unique identifier for the API key'),
name: z.string().describe('Display name for the API key'),
created_at: z.string().describe('ISO 8601 timestamp when the key was created'),
last_used_at: z.string().nullable().describe('ISO 8601 timestamp when the key was last used, or null if never used'),
expires_at: z.string().nullable().describe('ISO 8601 timestamp when the key expires, or null if no expiration'),
created_by_user_id: SnowflakeStringType.describe('User ID of the admin who created this key'),
acls: z.array(z.string()).max(100).describe('List of access control permissions for the key'),
});
export type ListAdminApiKeyResponse = z.infer<typeof ListAdminApiKeyResponse>;
export const SearchGuildsResponse = z.object({
guilds: z.array(GuildAdminResponse),
total: z.number(),
});
export type SearchGuildsResponse = z.infer<typeof SearchGuildsResponse>;
export const SearchUsersResponse = z.object({
users: z.array(UserAdminResponseSchema),
total: z.number(),
});
export type SearchUsersResponse = z.infer<typeof SearchUsersResponse>;
export const RefreshSearchIndexResponse = z.object({
success: z.literal(true),
job_id: z.string(),
});
export type RefreshSearchIndexResponse = z.infer<typeof RefreshSearchIndexResponse>;
const IndexRefreshStatusEnum = createNamedStringLiteralUnion(
[
['in_progress', 'in_progress', 'Index refresh is currently in progress'],
['completed', 'completed', 'Index refresh completed successfully'],
['failed', 'failed', 'Index refresh failed'],
],
'Current status of the index refresh job',
);
export const IndexRefreshStatusResponse = z.union([
z.object({
status: z.literal('not_found').describe('Job was not found'),
}),
z.object({
status: IndexRefreshStatusEnum,
index_type: z.string().describe('Type of index being refreshed'),
total: z.number().optional().describe('Total number of items to index'),
indexed: z.number().optional().describe('Number of items indexed so far'),
started_at: z.string().optional().describe('ISO 8601 timestamp when the job started'),
completed_at: z.string().optional().describe('ISO 8601 timestamp when the job completed'),
failed_at: z.string().optional().describe('ISO 8601 timestamp when the job failed'),
error: z.string().optional().describe('Error message if the job failed'),
}),
]);
export type IndexRefreshStatusResponse = z.infer<typeof IndexRefreshStatusResponse>;
const AdminArchiveSubjectTypeSchema = createNamedStringLiteralUnion(
[
['user', 'user', 'User data archive'],
['guild', 'guild', 'Guild data archive'],
],
'Type of subject being archived',
);
export const AdminArchiveResponseSchema = z.object({
archive_id: SnowflakeStringType,
subject_type: AdminArchiveSubjectTypeSchema,
subject_id: SnowflakeStringType,
requested_by: SnowflakeStringType,
requested_at: z.string(),
started_at: z.string().nullable(),
completed_at: z.string().nullable(),
failed_at: z.string().nullable(),
file_size: createStringType(1, 64).nullable(),
progress_percent: z.number(),
progress_step: createStringType(1, 256).nullable(),
error_message: createStringType(1, 4000).nullable(),
download_url_expires_at: z.string().nullable(),
expires_at: z.string().nullable(),
});
export const ListArchivesResponseSchema = z.object({
archives: z.array(AdminArchiveResponseSchema),
});
export const GetArchiveResponseSchema = z.object({
archive: AdminArchiveResponseSchema.nullable(),
});
export const DownloadUrlResponseSchema = z.object({
downloadUrl: createStringType(1, 2048),
expiresAt: z.string(),
});
const GuildAssetTypeEnum = createNamedStringLiteralUnion(
[
['emoji', 'emoji', 'Custom emoji asset'],
['sticker', 'sticker', 'Custom sticker asset'],
['unknown', 'unknown', 'Unknown asset type'],
],
'Type of guild asset',
);
export const PurgeGuildAssetResultSchema = z.object({
id: SnowflakeStringType.describe('Unique identifier of the asset'),
asset_type: GuildAssetTypeEnum,
found_in_db: z.boolean().describe('Whether the asset was found in the database'),
guild_id: SnowflakeStringType.nullable().describe('ID of the guild the asset belongs to'),
});
export type PurgeGuildAssetResult = z.infer<typeof PurgeGuildAssetResultSchema>;
export const PurgeGuildAssetErrorSchema = z.object({
id: SnowflakeStringType,
error: createStringType(1, 4000),
});
export type PurgeGuildAssetError = z.infer<typeof PurgeGuildAssetErrorSchema>;
export const PurgeGuildAssetsResponseSchema = z.object({
processed: z.array(PurgeGuildAssetResultSchema),
errors: z.array(PurgeGuildAssetErrorSchema),
});
export type PurgeGuildAssetsResponse = z.infer<typeof PurgeGuildAssetsResponseSchema>;
export const AdminAuditLogResponseSchema = z.object({
log_id: SnowflakeStringType,
admin_user_id: SnowflakeStringType,
target_type: createStringType(1, 256),
target_id: z.string().describe('The ID of the affected entity (user, channel, role, invite code, etc.)'),
action: createStringType(1, 256),
audit_log_reason: createStringType(1, 4000).nullable(),
metadata: z.record(createStringType(1, 256), createStringType(0, 4000)),
created_at: z.string(),
});
export const AuditLogsListResponseSchema = z.object({
logs: z.array(AdminAuditLogResponseSchema),
total: z.number(),
});
export const BanCheckResponseSchema = z.object({
banned: z.boolean(),
});
export const BulkOperationFailedResponse = z.object({
id: SnowflakeStringType,
error: createStringType(1, 4000),
});
export const BulkOperationResponse = z.object({
successful: z.array(SnowflakeStringType).max(1000),
failed: z.array(BulkOperationFailedResponse).max(1000),
});
export const LegalHoldResponse = z.object({
held: z.boolean(),
});
const NcmecSubmissionStatusEnum = createNamedStringLiteralUnion(
[
['not_submitted', 'not_submitted', 'Report has not been submitted to NCMEC'],
['submitted', 'submitted', 'Report has been submitted to NCMEC'],
['failed', 'failed', 'Report submission to NCMEC failed'],
],
'NCMEC submission status',
);
export const NcmecSubmissionStatusResponse = z.object({
status: NcmecSubmissionStatusEnum,
ncmec_report_id: createStringType(1, 256).nullable().describe('NCMEC report ID if submitted'),
submitted_at: z.string().nullable().describe('ISO 8601 timestamp when the report was submitted'),
submitted_by_admin_id: SnowflakeStringType.nullable().describe('ID of the admin who submitted the report'),
failure_reason: createStringType(1, 4000).nullable().describe('Reason for submission failure if failed'),
});
export const NcmecSubmitResultResponse = z.object({
success: z.boolean(),
ncmec_report_id: createStringType(1, 256).nullable(),
error: createStringType(1, 4000).nullable(),
});
export const CodesResponse = z.object({
codes: z.array(z.string()),
});
export const GuildMemoryStatsResponse = z.object({
guilds: z
.array(
z.object({
guild_id: SnowflakeStringType.nullable(),
guild_name: createStringType(1, 100),
guild_icon: createStringType(1, 256).nullable(),
memory: Int64StringType,
member_count: Int32Type,
session_count: Int32Type,
presence_count: Int32Type,
}),
)
.max(100),
});
export type GuildMemoryStatsResponse = z.infer<typeof GuildMemoryStatsResponse>;
export const ReloadGuildsRequest = z.object({
guild_ids: z.array(SnowflakeType).max(1000).describe('List of guild IDs to reload'),
});
export type ReloadGuildsRequest = z.infer<typeof ReloadGuildsRequest>;
export const ReloadAllGuildsResponse = z.object({
count: Int32Type,
});
export type ReloadAllGuildsResponse = z.infer<typeof ReloadAllGuildsResponse>;
export const NodeStatsResponse = z.object({
status: createStringType(1, 256),
sessions: Int32Type,
guilds: Int32Type,
presences: Int32Type,
calls: Int32Type,
memory: z.object({
total: Int64StringType,
processes: Int64StringType,
system: Int64StringType,
}),
process_count: Int32Type,
process_limit: Int32Type,
uptime_seconds: Int32Type,
});
export type NodeStatsResponse = z.infer<typeof NodeStatsResponse>;
export const SuccessResponse = z.object({
success: z.boolean(),
});
const AdminGuildResponseSchema = z.object({
id: SnowflakeStringType,
name: createStringType(1, 100),
features: z.array(createStringType(1, 256)).max(100),
owner_id: SnowflakeStringType,
icon: createStringType(1, 256).nullable(),
banner: createStringType(1, 256).nullable(),
member_count: Int32Type,
});
const AdminGuildChannelSummarySchema = z.object({
id: SnowflakeStringType,
name: createStringType(1, 100).nullable(),
type: ChannelTypeSchema,
position: Int32Type,
parent_id: SnowflakeStringType.nullable(),
});
const AdminGuildRoleSummarySchema = z.object({
id: SnowflakeStringType,
name: createStringType(1, 100),
color: Int32Type,
position: Int32Type,
permissions: PermissionStringType.describe('fluxer:PermissionStringType The role permissions bitfield'),
hoist: z.boolean(),
mentionable: z.boolean(),
});
const AdminLookupGuildSchema = z.object({
id: SnowflakeStringType,
owner_id: SnowflakeStringType,
name: createStringType(1, 100),
vanity_url_code: createStringType(1, 256).nullable(),
icon: createStringType(1, 256).nullable(),
banner: createStringType(1, 256).nullable(),
splash: createStringType(1, 256).nullable(),
embed_splash: createStringType(1, 256).nullable(),
features: z.array(createStringType(1, 256)).max(100),
verification_level: GuildVerificationLevelSchema,
mfa_level: GuildMFALevelSchema,
nsfw_level: NSFWLevelSchema,
explicit_content_filter: GuildExplicitContentFilterSchema,
default_message_notifications: DefaultMessageNotificationsSchema,
afk_channel_id: SnowflakeStringType.nullable(),
afk_timeout: Int32Type,
system_channel_id: SnowflakeStringType.nullable(),
system_channel_flags: createBitflagInt32Type(
SystemChannelFlags,
SystemChannelFlagsDescriptions,
'System channel message flags',
'SystemChannelFlags',
),
rules_channel_id: SnowflakeStringType.nullable(),
disabled_operations: Int32Type,
member_count: Int32Type,
channels: z.array(AdminGuildChannelSummarySchema).max(500),
roles: z.array(AdminGuildRoleSummarySchema).max(250),
});
export const LookupGuildResponse = z.object({
guild: AdminLookupGuildSchema.nullable(),
});
export const ListGuildMembersResponse = z.object({
members: z.array(z.lazy(() => GuildMemberResponse)).max(200),
total: Int32Type,
limit: Int32Type,
offset: Int32Type,
});
export const GuildAssetItemSchema = z.object({
id: SnowflakeStringType,
name: createStringType(1, 100),
animated: z.boolean(),
creator_id: SnowflakeStringType,
media_url: createStringType(1, 2048),
});
export type GuildEmojiAsset = z.infer<typeof GuildAssetItemSchema>;
export type GuildStickerAsset = z.infer<typeof GuildAssetItemSchema>;
export const ListGuildEmojisResponse = z.object({
guild_id: SnowflakeStringType,
emojis: z.array(GuildAssetItemSchema).max(500),
});
export type ListGuildEmojisResponse = z.infer<typeof ListGuildEmojisResponse>;
export const ListGuildStickersResponse = z.object({
guild_id: SnowflakeStringType,
stickers: z.array(GuildAssetItemSchema).max(500),
});
export type ListGuildStickersResponse = z.infer<typeof ListGuildStickersResponse>;
export const GuildUpdateResponse = z.object({
guild: AdminGuildResponseSchema,
});
const AdminMessageSchema = z.object({
id: SnowflakeStringType,
channel_id: SnowflakeStringType,
author_id: SnowflakeStringType,
author_username: createStringType(1, 100),
author_discriminator: createStringType(1, 10),
content: createStringType(0, 4000),
timestamp: z.string(),
attachments: z
.array(
z.object({
filename: createStringType(1, 256),
url: createStringType(1, 2048),
}),
)
.max(10),
});
export const LookupMessageResponse = z.object({
messages: z.array(AdminMessageSchema).max(100),
message_id: SnowflakeStringType.nullable(),
});
export const DeleteMessageResponse = z.object({
success: z.literal(true),
});
export const MessageShredStatusNotFoundResponse = z.object({
status: z.literal('not_found'),
});
export const MessageShredStatusProgressResponse = z.object({
status: createNamedStringLiteralUnion(
[
['in_progress', 'in_progress', 'Shredding is currently running'],
['completed', 'completed', 'Shredding completed successfully'],
['failed', 'failed', 'Shredding failed'],
],
'Current message shred job status',
),
requested: Int32Type,
total: Int32Type,
processed: Int32Type,
skipped: Int32Type,
started_at: z.string().optional(),
completed_at: z.string().optional(),
failed_at: z.string().optional(),
error: createStringType(1, 4000).optional(),
});
export const MessageShredStatusResponse = z.union([
MessageShredStatusNotFoundResponse,
MessageShredStatusProgressResponse,
]);
const ReportMessageContextSchema = z.object({
id: SnowflakeStringType,
channel_id: SnowflakeStringType,
guild_id: SnowflakeStringType.nullable(),
content: z.string(),
timestamp: z.string(),
attachments: z.array(
z.object({
filename: z.string(),
url: z.string(),
}),
),
author_id: SnowflakeStringType,
author_username: z.string(),
author_discriminator: z.string(),
});
export const ReportAdminResponseSchema = z.object({
report_id: SnowflakeStringType,
reporter_id: SnowflakeStringType.nullable(),
reporter_tag: z.string().nullable(),
reporter_username: z.string().nullable(),
reporter_discriminator: z.string().nullable(),
reporter_email: z.string().nullable(),
reporter_full_legal_name: z.string().nullable(),
reporter_country_of_residence: z.string().nullable(),
reported_at: z.string(),
status: ReportStatusSchema,
report_type: ReportTypeSchema,
category: z.string().nullable(),
additional_info: z.string().nullable(),
reported_user_id: SnowflakeStringType.nullable(),
reported_user_tag: z.string().nullable(),
reported_user_username: z.string().nullable(),
reported_user_discriminator: z.string().nullable(),
reported_user_avatar_hash: z.string().nullable(),
reported_guild_id: SnowflakeStringType.nullable(),
reported_guild_name: z.string().nullable(),
reported_message_id: SnowflakeStringType.nullable(),
reported_channel_id: SnowflakeStringType.nullable(),
reported_channel_name: z.string().nullable(),
reported_guild_invite_code: z.string().nullable(),
resolved_at: z.string().nullable(),
resolved_by_admin_id: SnowflakeStringType.nullable(),
public_comment: z.string().nullable(),
mutual_dm_channel_id: SnowflakeStringType.nullable().optional(),
message_context: z.array(ReportMessageContextSchema).optional(),
});
export const ListReportsResponse = z.object({
reports: z.array(ReportAdminResponseSchema),
});
export const ResolveReportResponse = z.object({
report_id: SnowflakeStringType,
status: ReportStatusSchema,
resolved_at: z.string().nullable(),
public_comment: z.string().nullable(),
});
export const SearchReportsResponse = z.object({
reports: z.array(ReportAdminResponseSchema),
total: z.number(),
offset: z.number(),
limit: z.number(),
});
const LimitKeyMetadataSchema = z.object({
key: z.string(),
label: z.string(),
description: z.string(),
category: z.string(),
scope: z.string(),
isToggle: z.boolean(),
unit: z.enum(['bytes', 'count']).optional(),
min: z.number().optional(),
max: z.number().optional(),
});
export const LimitConfigGetResponse = z.object({
limit_config: LimitConfigSchema.extend({
traitDefinitions: z.array(z.string()),
rules: z.array(
LimitRuleSchema.extend({
modifiedFields: z.array(z.string()).optional(),
}),
),
}),
limit_config_json: z.string(),
self_hosted: z.boolean(),
defaults: z.record(z.string(), z.record(LimitKeySchema, z.number())),
metadata: z.record(LimitKeySchema, LimitKeyMetadataSchema),
categories: z.record(z.string(), z.string()),
limit_keys: z.array(z.string()),
bounds: z.record(z.string(), z.object({min: z.number(), max: z.number()})).optional(),
});
export const DeleteApiKeyResponse = z.object({
success: z.literal(true),
});
const SnowflakeOrSentinelType = z
.string()
.regex(/^(-1|0|[1-9][0-9]*)$/)
.describe('fluxer:SnowflakeStringType');
export const VisionarySlotSchema = z.object({
slot_index: Int32Type.describe('The slot index'),
user_id: SnowflakeOrSentinelType.nullable().describe(
'User ID that reserved this slot, or null if unreserved (special value -1 is also valid)',
),
});
export type VisionarySlotSchema = z.infer<typeof VisionarySlotSchema>;
export const ListVisionarySlotsResponse = z.object({
slots: z.array(VisionarySlotSchema).max(10000).describe('List of all visionary slots'),
total_count: Int32Type.describe('Total number of slots'),
reserved_count: Int32Type.describe('Number of reserved slots'),
});
export type ListVisionarySlotsResponse = z.infer<typeof ListVisionarySlotsResponse>;
export const ExpandVisionarySlotsRequest = z.object({
count: Int32Type.min(1).max(1000).describe('Number of new slots to create'),
});
export type ExpandVisionarySlotsRequest = z.infer<typeof ExpandVisionarySlotsRequest>;
export const ShrinkVisionarySlotsRequest = z.object({
target_count: Int32Type.min(0)
.max(100000)
.describe('Target total number of slots (removes from highest indices, minimum 0 slots)'),
});
export type ShrinkVisionarySlotsRequest = z.infer<typeof ShrinkVisionarySlotsRequest>;
export const ReserveVisionarySlotRequest = z.object({
slot_index: Int32Type.min(1).describe('Slot index to reserve (must be >= 1)'),
user_id: SnowflakeOrSentinelType.nullable().describe(
'User ID to reserve the slot for, or null to unreserve (special value -1 is also valid)',
),
});
export type ReserveVisionarySlotRequest = z.infer<typeof ReserveVisionarySlotRequest>;
export const SwapVisionarySlotsRequest = z
.object({
slot_index_a: Int32Type.min(1).describe('First slot index to swap (must be >= 1)'),
slot_index_b: Int32Type.min(1).describe('Second slot index to swap (must be >= 1)'),
})
.refine((data) => data.slot_index_a !== data.slot_index_b, {
message: ValidationErrorCodes.INVALID_FORMAT,
path: ['slot_index_b'],
params: {detail: 'Slot indices must be different'},
});
export type SwapVisionarySlotsRequest = z.infer<typeof SwapVisionarySlotsRequest>;
export const VisionarySlotOperationResponse = z.object({
success: z.literal(true),
});