mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
refactor(schema): simplify validation and OpenAPI generation (#2709)
This commit is contained in:
1 parent
4bc5593f9f
commit
de1fd95a99
510 files changed
+29494
-36131
No files matched your search
@@ -7,7 +7,7 @@ import {
|
||||
ENTRANCE_SOUND_NAME_MAX_LENGTH,
|
||||
} from '@fluxer/constants/src/EntranceSoundConstants';
|
||||
import {createBase64StringType} from '@fluxer/schema/src/primitives/FileValidators';
|
||||
import {createStringType, SnowflakeType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {createStringType, SnowflakeStringType, SnowflakeType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {z} from 'zod';
|
||||
|
||||
const ScopeIdSchema = z
|
||||
@@ -19,16 +19,6 @@ const ScopeIdSchema = z
|
||||
|
||||
const ENTRANCE_SOUND_BASE64_MAX_CHARS = Math.ceil((ENTRANCE_SOUND_MAX_BYTES * 4) / 3) + 32;
|
||||
|
||||
function toNonEmptyStringTuple<TValue extends string>(values: ReadonlyArray<TValue>): [TValue, ...Array<TValue>] {
|
||||
const [first, ...rest] = values;
|
||||
if (first === undefined) {
|
||||
throw new Error('Expected at least one enum value');
|
||||
}
|
||||
return [first, ...rest];
|
||||
}
|
||||
|
||||
const EntranceSoundExtensionValues = toNonEmptyStringTuple(ENTRANCE_SOUND_EXTENSIONS);
|
||||
|
||||
export const EntranceSoundUploadRequest = z.object({
|
||||
name: createStringType(1, ENTRANCE_SOUND_NAME_MAX_LENGTH).describe('Display label for the sound'),
|
||||
audio: createBase64StringType(1, ENTRANCE_SOUND_BASE64_MAX_CHARS).describe('Base64-encoded audio bytes'),
|
||||
@@ -50,22 +40,22 @@ export const EntranceSoundSelectionRequest = z.object({
|
||||
export type EntranceSoundSelectionRequest = z.infer<typeof EntranceSoundSelectionRequest>;
|
||||
|
||||
export const EntranceSoundResponse = z.object({
|
||||
id: SnowflakeType,
|
||||
id: SnowflakeStringType,
|
||||
name: z.string(),
|
||||
hash: z.string(),
|
||||
extension: z.enum(EntranceSoundExtensionValues),
|
||||
extension: z.enum(ENTRANCE_SOUND_EXTENSIONS),
|
||||
content_type: z.string(),
|
||||
duration_ms: z.number().int().min(0).max(ENTRANCE_SOUND_MAX_DURATION_MS),
|
||||
size_bytes: z.number().int().min(0).max(ENTRANCE_SOUND_MAX_BYTES),
|
||||
url: z.string().url(),
|
||||
created_at: z.string().datetime(),
|
||||
url: z.url(),
|
||||
created_at: z.iso.datetime(),
|
||||
});
|
||||
|
||||
export type EntranceSoundResponse = z.infer<typeof EntranceSoundResponse>;
|
||||
|
||||
const EntranceSoundSelectionResponse = z.object({
|
||||
scope_id: ScopeIdSchema,
|
||||
sound_id: SnowflakeType,
|
||||
sound_id: SnowflakeStringType,
|
||||
});
|
||||
|
||||
export const EntranceSoundLibraryResponse = z.object({
|
||||
|
||||
@@ -20,11 +20,15 @@ const HarvestStatusEnum = withOpenApiType(
|
||||
),
|
||||
'HarvestStatus',
|
||||
);
|
||||
export type HarvestStatus = z.infer<typeof HarvestStatusEnum>;
|
||||
|
||||
export const HarvestCreationResponseSchema = z.object({
|
||||
harvest_id: SnowflakeStringType.describe('Unique identifier for the harvest request'),
|
||||
status: HarvestStatusEnum,
|
||||
created_at: z.string().describe('ISO 8601 timestamp when the harvest request was created'),
|
||||
});
|
||||
export type HarvestCreationResponse = z.infer<typeof HarvestCreationResponseSchema>;
|
||||
|
||||
export const HarvestStatusResponseSchema = HarvestCreationResponseSchema.extend({
|
||||
started_at: z.string().nullable().describe('ISO 8601 timestamp when the harvest started, or null if pending'),
|
||||
completed_at: z.string().nullable().describe('ISO 8601 timestamp when the harvest completed, or null otherwise'),
|
||||
@@ -54,3 +58,5 @@ export const HarvestDownloadUrlResponse = z.object({
|
||||
});
|
||||
|
||||
export type HarvestDownloadUrlResponse = z.infer<typeof HarvestDownloadUrlResponse>;
|
||||
|
||||
export const HarvestArchiveResponse = z.file().describe('The ZIP archive containing the requested user data');
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {AVATAR_MAX_SIZE, MAX_GROUP_DM_OTHER_RECIPIENTS} from '@fluxer/constants/src/LimitConstants';
|
||||
import {AVATAR_MAX_SIZE, MAX_GROUP_DM_OTHER_RECIPIENTS, MAX_GUILDS_PREMIUM} from '@fluxer/constants/src/LimitConstants';
|
||||
import {StatusTypes} from '@fluxer/constants/src/StatusConstants';
|
||||
import {
|
||||
DEFAULT_GUILD_FOLDER_ICON,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
GroupDmAddPermissionFlagsDescriptions,
|
||||
GuildFolderFlags,
|
||||
GuildFolderFlagsDescriptions,
|
||||
GuildFolderIcons,
|
||||
IncomingCallFlags,
|
||||
IncomingCallFlagsDescriptions,
|
||||
ProfileFieldPrivacyFlags,
|
||||
@@ -32,8 +31,6 @@ import {
|
||||
createBitflagInt32Type,
|
||||
createNamedStringLiteralUnion,
|
||||
createStringType,
|
||||
Int32Type,
|
||||
SignedInt32Type,
|
||||
SnowflakeStringType,
|
||||
SnowflakeType,
|
||||
withFieldDescription,
|
||||
@@ -41,6 +38,7 @@ import {
|
||||
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {URLType} from '@fluxer/schema/src/primitives/UrlValidators';
|
||||
import {
|
||||
GuildFolderIconSchema,
|
||||
MentionReplyPreferencesSchema,
|
||||
ProfilePrivacyLevelSchema,
|
||||
RelationshipTypesSchema,
|
||||
@@ -103,21 +101,18 @@ export const UserUpdateRequest = z
|
||||
export type UserUpdateRequest = z.infer<typeof UserUpdateRequest>;
|
||||
|
||||
const EmailTokenType = createStringType(1, 256);
|
||||
export const UserUpdateWithVerificationRequest = UserUpdateRequest.merge(
|
||||
z.object({
|
||||
email_token: EmailTokenType.optional().describe('Email change token for updating email'),
|
||||
}),
|
||||
)
|
||||
.merge(SudoVerificationSchema)
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.email !== undefined) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: ValidationErrorCodes.EMAIL_MUST_BE_CHANGED_VIA_TOKEN,
|
||||
path: ['email'],
|
||||
});
|
||||
}
|
||||
});
|
||||
export const UserUpdateWithVerificationRequest = UserUpdateRequest.extend({
|
||||
email_token: EmailTokenType.optional().describe('Email change token for updating email'),
|
||||
...SudoVerificationSchema.shape,
|
||||
}).superRefine((data, ctx) => {
|
||||
if (data.email !== undefined) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: ValidationErrorCodes.EMAIL_MUST_BE_CHANGED_VIA_TOKEN,
|
||||
path: ['email'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type UserUpdateWithVerificationRequest = z.infer<typeof UserUpdateWithVerificationRequest>;
|
||||
|
||||
@@ -153,7 +148,7 @@ export const EmailChangeApplyRequest = z
|
||||
.object({
|
||||
email_token: EmailTokenType.describe('Email change token returned from verify-new'),
|
||||
})
|
||||
.merge(SudoVerificationSchema);
|
||||
.extend(SudoVerificationSchema.shape);
|
||||
|
||||
export type EmailChangeApplyRequest = z.infer<typeof EmailChangeApplyRequest>;
|
||||
|
||||
@@ -230,11 +225,11 @@ const CustomStatusPayloadSchema = z
|
||||
.describe('Unicode emoji to display (ignored when emoji_id is provided)'),
|
||||
})
|
||||
.refine((value) => value.emoji_name == null || isValidSingleUnicodeEmoji(value.emoji_name), {
|
||||
message: 'Emoji name must be a valid Unicode emoji',
|
||||
error: 'Emoji name must be a valid Unicode emoji',
|
||||
path: ['emoji_name'],
|
||||
})
|
||||
.refine((value) => value.expires_at == null || value.expires_at.getTime() > Date.now(), {
|
||||
message: 'expires_at must be in the future',
|
||||
error: 'expires_at must be in the future',
|
||||
path: ['expires_at'],
|
||||
});
|
||||
export const CustomStatusPayload = z.preprocess((value) => {
|
||||
@@ -251,21 +246,6 @@ export const CustomStatusPayload = z.preprocess((value) => {
|
||||
|
||||
export type CustomStatusPayload = z.infer<typeof CustomStatusPayload>;
|
||||
|
||||
const GuildFolderIconSchema = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
[GuildFolderIcons.FOLDER, 'FOLDER', 'Classic folder icon'],
|
||||
[GuildFolderIcons.STAR, 'STAR', 'Star icon'],
|
||||
[GuildFolderIcons.HEART, 'HEART', 'Heart icon'],
|
||||
[GuildFolderIcons.BOOKMARK, 'BOOKMARK', 'Bookmark icon'],
|
||||
[GuildFolderIcons.GAME_CONTROLLER, 'GAME_CONTROLLER', 'Game controller icon'],
|
||||
[GuildFolderIcons.SHIELD, 'SHIELD', 'Shield icon'],
|
||||
[GuildFolderIcons.MUSIC_NOTE, 'MUSIC_NOTE', 'Music note icon'],
|
||||
] as const,
|
||||
'Guild folder icon',
|
||||
),
|
||||
'GuildFolderIconType',
|
||||
);
|
||||
export const CreatePrivateChannelRequest = z
|
||||
.object({
|
||||
recipient_id: SnowflakeType.optional().describe('User ID for creating a DM channel'),
|
||||
@@ -279,16 +259,21 @@ export const CreatePrivateChannelRequest = z
|
||||
(data) =>
|
||||
(data.recipient_id != null && data.recipients == null) || (data.recipient_id == null && data.recipients != null),
|
||||
{
|
||||
message: 'Either recipient_id or recipients must be provided, but not both',
|
||||
error: 'Either recipient_id or recipients must be provided, but not both',
|
||||
},
|
||||
);
|
||||
|
||||
export type CreatePrivateChannelRequest = z.infer<typeof CreatePrivateChannelRequest>;
|
||||
|
||||
const UserSettingsGuildIds = z
|
||||
.array(SnowflakeType)
|
||||
.transform((ids) => [...new Set(ids)])
|
||||
.refine((ids) => ids.length <= MAX_GUILDS_PREMIUM, `Maximum ${MAX_GUILDS_PREMIUM} guilds allowed`);
|
||||
|
||||
const GuildFolderSchema = z.object({
|
||||
id: SignedInt32Type.describe('Unique identifier for the folder (-1 for uncategorized)'),
|
||||
id: z.number().int().min(-1).describe('Unique identifier for the folder (-1 for uncategorized)'),
|
||||
name: createStringType(0, 100).nullish().describe('Display name of the folder'),
|
||||
color: Int32Type.nullish().describe('Color of the folder as integer'),
|
||||
color: ColorType.nullish().default(0x000000).describe('Color of the folder as integer'),
|
||||
flags: createBitflagInt32Type(
|
||||
GuildFolderFlags,
|
||||
GuildFolderFlagsDescriptions,
|
||||
@@ -298,7 +283,7 @@ const GuildFolderSchema = z.object({
|
||||
.default(0)
|
||||
.describe('Bitfield for guild folder display behaviour'),
|
||||
icon: GuildFolderIconSchema.default(DEFAULT_GUILD_FOLDER_ICON).describe('Selected icon for the guild folder'),
|
||||
guild_ids: z.array(SnowflakeType).max(200).describe('Guild IDs in this folder'),
|
||||
guild_ids: UserSettingsGuildIds.describe('Guild IDs in this folder'),
|
||||
});
|
||||
const UserStatusType = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
@@ -327,19 +312,15 @@ const UserThemeType = withOpenApiType(
|
||||
);
|
||||
export const UserSettingsUpdateRequest = z
|
||||
.object({
|
||||
flags: createBitflagInt32Type(
|
||||
FriendSourceFlags,
|
||||
FriendSourceFlagsDescriptions,
|
||||
'Friend source flags',
|
||||
'FriendSourceFlags',
|
||||
),
|
||||
flags: z.number().int().describe('Bitfield of user settings flags'),
|
||||
status: UserStatusType,
|
||||
status_resets_at: DateTimeType.nullish().describe('When status resets'),
|
||||
status_resets_to: UserStatusType.nullish(),
|
||||
theme: UserThemeType,
|
||||
guild_positions: UserSettingsGuildIds.describe('Ordered array of guild IDs for sidebar positioning'),
|
||||
locale: LocaleSchema,
|
||||
restricted_guilds: z.array(SnowflakeType).max(200).describe('Guilds with DM restrictions'),
|
||||
bot_restricted_guilds: z.array(SnowflakeType).max(200).describe('Guilds with bot DM restrictions'),
|
||||
restricted_guilds: UserSettingsGuildIds.describe('Guilds with DM restrictions'),
|
||||
bot_restricted_guilds: UserSettingsGuildIds.describe('Guilds with bot DM restrictions'),
|
||||
default_guilds_restricted: z.boolean().describe('Default DM restriction for new guilds'),
|
||||
bot_default_guilds_restricted: z.boolean().describe('Default bot DM restriction for new guilds'),
|
||||
inline_attachment_media: z.boolean().describe('Display attachments inline'),
|
||||
@@ -369,9 +350,9 @@ export const UserSettingsUpdateRequest = z
|
||||
'Group DM add permissions',
|
||||
'GroupDmAddPermissionFlags',
|
||||
),
|
||||
guild_folders: z.array(GuildFolderSchema).max(200).describe('Guild folder organization'),
|
||||
guild_folders: z.array(GuildFolderSchema).max(100).describe('Guild folder organization'),
|
||||
custom_status: CustomStatusPayload.nullish().describe('Custom status'),
|
||||
afk_timeout: z.number().int().describe('AFK timeout in seconds'),
|
||||
afk_timeout: z.number().int().min(60).max(600).describe('AFK timeout in seconds (60-600)'),
|
||||
time_format: withFieldDescription(TimeFormatTypesSchema, 'Time format preference'),
|
||||
developer_mode: z.boolean().describe('Developer mode enabled'),
|
||||
trusted_domains: z
|
||||
@@ -403,6 +384,9 @@ export const UserSettingsUpdateRequest = z
|
||||
ProfilePrivacyLevelSchema,
|
||||
'Controls who sees the full profile: all guild members, only small-guild members, or only friends',
|
||||
),
|
||||
default_share_voice_activity: z
|
||||
.boolean()
|
||||
.describe('Default share_voice_activity applied to new friend relationships'),
|
||||
synced_preferences: z
|
||||
.string()
|
||||
.max(SYNCED_PREFERENCES_MAX_ENCODED_LENGTH)
|
||||
@@ -516,21 +500,21 @@ export const RegisterMobileDeviceRequest = z
|
||||
if (value.platform !== 'android_unified_push') return;
|
||||
if (!URLType.safeParse(value.token).success) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: 'custom',
|
||||
path: ['token'],
|
||||
message: 'UnifiedPush registrations require a valid endpoint URL',
|
||||
});
|
||||
}
|
||||
if (!value.encryption_key) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: 'custom',
|
||||
path: ['encryption_key'],
|
||||
message: 'UnifiedPush registrations require encryption_key',
|
||||
});
|
||||
}
|
||||
if (!value.auth_secret) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: 'custom',
|
||||
path: ['auth_secret'],
|
||||
message: 'UnifiedPush registrations require auth_secret',
|
||||
});
|
||||
@@ -642,7 +626,7 @@ export const BulkIgnoreFriendRequestsRequest = z.object({
|
||||
|
||||
export type BulkIgnoreFriendRequestsRequest = z.infer<typeof BulkIgnoreFriendRequestsRequest>;
|
||||
|
||||
const BulkDeleteSelfMessagesScope = createNamedStringLiteralUnion(
|
||||
export const BulkDeleteSelfMessagesScope = createNamedStringLiteralUnion(
|
||||
[
|
||||
['selected', 'Selected', 'Delete messages matching the explicit include_* toggles and excluded_guild_ids.'],
|
||||
[
|
||||
@@ -653,105 +637,108 @@ const BulkDeleteSelfMessagesScope = createNamedStringLiteralUnion(
|
||||
],
|
||||
'Which set of contexts the deletion targets',
|
||||
);
|
||||
const BulkDeleteSelfMessagesFilterShape = z.object({
|
||||
scope: BulkDeleteSelfMessagesScope.optional().default('selected'),
|
||||
include_dms: z.boolean().optional().default(true).describe('Include 1:1 direct messages the caller still has open.'),
|
||||
include_dms_closed: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe(
|
||||
'Include 1:1 direct messages the caller has previously closed. Independent of include_dms — set include_dms=false and include_dms_closed=true to target closed DMs only.',
|
||||
),
|
||||
include_group_dms: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe('Include group DMs the caller is still a member of.'),
|
||||
include_guilds: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe('Include text channels in guilds the caller is a member of.'),
|
||||
guild_filter_mode: createNamedStringLiteralUnion(
|
||||
[
|
||||
['exclude', 'Exclude', 'Apply to every guild except those listed in excluded_guild_ids.'],
|
||||
[
|
||||
'include_only',
|
||||
'Include Only',
|
||||
'Apply only to the guilds listed in included_guild_ids; all other guilds are left untouched.',
|
||||
],
|
||||
],
|
||||
'How the guild filter list is interpreted when include_guilds is true.',
|
||||
)
|
||||
.optional()
|
||||
.default('exclude'),
|
||||
excluded_guild_ids: z
|
||||
.array(SnowflakeType)
|
||||
.max(500)
|
||||
.optional()
|
||||
.default([])
|
||||
.describe(
|
||||
'Guild IDs to leave untouched. Used when include_guilds is true, guild_filter_mode is exclude, and scope is selected.',
|
||||
),
|
||||
included_guild_ids: z
|
||||
.array(SnowflakeType)
|
||||
.max(500)
|
||||
.optional()
|
||||
.default([])
|
||||
.describe(
|
||||
'The only guild IDs to apply this operation to. Used when include_guilds is true, guild_filter_mode is include_only, and scope is selected.',
|
||||
),
|
||||
start_date: z
|
||||
.string()
|
||||
.datetime()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Inclusive ISO8601 lower bound for message timestamps. Null/omitted means unbounded in the past.'),
|
||||
end_date: z
|
||||
.string()
|
||||
.datetime()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Exclusive ISO8601 upper bound for message timestamps. Null/omitted means unbounded in the future.'),
|
||||
});
|
||||
export type BulkDeleteSelfMessagesScope = z.infer<typeof BulkDeleteSelfMessagesScope>;
|
||||
|
||||
function applyBulkDeleteSelfMessagesRefinement<T extends z.ZodObject<z.ZodRawShape>>(schema: T) {
|
||||
return schema.superRefine((value, ctx) => {
|
||||
const cast = value as z.infer<typeof BulkDeleteSelfMessagesFilterShape> & Record<string, unknown>;
|
||||
if (cast.scope === 'selected') {
|
||||
const anyToggle = cast.include_dms || cast.include_dms_closed || cast.include_group_dms || cast.include_guilds;
|
||||
if (!anyToggle) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Enable at least one of include_dms, include_dms_closed, include_group_dms, or include_guilds.',
|
||||
path: ['include_dms'],
|
||||
});
|
||||
}
|
||||
export const BulkDeleteSelfMessagesGuildFilterMode = createNamedStringLiteralUnion(
|
||||
[
|
||||
['exclude', 'Exclude', 'Apply to every guild except those listed in excluded_guild_ids.'],
|
||||
[
|
||||
'include_only',
|
||||
'Include Only',
|
||||
'Apply only to the guilds listed in included_guild_ids; all other guilds are left untouched.',
|
||||
],
|
||||
],
|
||||
'How the guild filter list is interpreted when include_guilds is true.',
|
||||
);
|
||||
export type BulkDeleteSelfMessagesGuildFilterMode = z.infer<typeof BulkDeleteSelfMessagesGuildFilterMode>;
|
||||
|
||||
export const BulkDeleteSelfMessagesFilter = z
|
||||
.object({
|
||||
scope: BulkDeleteSelfMessagesScope.optional().default('selected'),
|
||||
include_dms: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe('Include 1:1 direct messages the caller still has open.'),
|
||||
include_dms_closed: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe(
|
||||
'Include 1:1 direct messages the caller has previously closed. Independent of include_dms — set include_dms=false and include_dms_closed=true to target closed DMs only.',
|
||||
),
|
||||
include_group_dms: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe('Include group DMs the caller is still a member of.'),
|
||||
include_guilds: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe('Include text channels in guilds the caller is a member of.'),
|
||||
guild_filter_mode: BulkDeleteSelfMessagesGuildFilterMode.optional().default('exclude'),
|
||||
excluded_guild_ids: z
|
||||
.array(SnowflakeType)
|
||||
.max(500)
|
||||
.optional()
|
||||
.default([])
|
||||
.describe(
|
||||
'Guild IDs to leave untouched. Used when include_guilds is true, guild_filter_mode is exclude, and scope is selected.',
|
||||
),
|
||||
included_guild_ids: z
|
||||
.array(SnowflakeType)
|
||||
.max(500)
|
||||
.optional()
|
||||
.default([])
|
||||
.describe(
|
||||
'The only guild IDs to apply this operation to. Used when include_guilds is true, guild_filter_mode is include_only, and scope is selected.',
|
||||
),
|
||||
start_date: z.iso
|
||||
.datetime()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Inclusive ISO8601 lower bound for message timestamps. Null/omitted means unbounded in the past.'),
|
||||
end_date: z.iso
|
||||
.datetime()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Exclusive ISO8601 upper bound for message timestamps. Null/omitted means unbounded in the future.'),
|
||||
})
|
||||
.superRefine((value, ctx) => {
|
||||
if (
|
||||
value.scope === 'selected' &&
|
||||
!value.include_dms &&
|
||||
!value.include_dms_closed &&
|
||||
!value.include_group_dms &&
|
||||
!value.include_guilds
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Enable at least one of include_dms, include_dms_closed, include_group_dms, or include_guilds.',
|
||||
path: ['include_dms'],
|
||||
});
|
||||
}
|
||||
if (cast.start_date && cast.end_date) {
|
||||
if (new Date(cast.start_date).getTime() >= new Date(cast.end_date).getTime()) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'start_date must be earlier than end_date.',
|
||||
path: ['end_date'],
|
||||
});
|
||||
}
|
||||
if (
|
||||
value.start_date &&
|
||||
value.end_date &&
|
||||
new Date(value.start_date).getTime() >= new Date(value.end_date).getTime()
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'start_date must be earlier than end_date.',
|
||||
path: ['end_date'],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const BulkDeleteSelfMessagesFilter = applyBulkDeleteSelfMessagesRefinement(BulkDeleteSelfMessagesFilterShape);
|
||||
|
||||
export type BulkDeleteSelfMessagesFilter = z.infer<typeof BulkDeleteSelfMessagesFilter>;
|
||||
|
||||
export const BulkDeleteSelfMessagesRequest = applyBulkDeleteSelfMessagesRefinement(
|
||||
BulkDeleteSelfMessagesFilterShape.merge(SudoVerificationSchema),
|
||||
);
|
||||
export const BulkDeleteSelfMessagesRequest = BulkDeleteSelfMessagesFilter.safeExtend(SudoVerificationSchema.shape);
|
||||
|
||||
export type BulkDeleteSelfMessagesRequest = z.infer<typeof BulkDeleteSelfMessagesRequest>;
|
||||
|
||||
export const HarvestSelfDataRequest = applyBulkDeleteSelfMessagesRefinement(BulkDeleteSelfMessagesFilterShape);
|
||||
export const HarvestSelfDataRequest = BulkDeleteSelfMessagesFilter.clone();
|
||||
|
||||
export type HarvestSelfDataRequest = z.infer<typeof HarvestSelfDataRequest>;
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
GroupDmAddPermissionFlagsDescriptions,
|
||||
GuildFolderFlags,
|
||||
GuildFolderFlagsDescriptions,
|
||||
GuildFolderIcons,
|
||||
IncomingCallFlags,
|
||||
IncomingCallFlagsDescriptions,
|
||||
type MentionReplyPreference,
|
||||
@@ -33,6 +32,7 @@ import {
|
||||
withOpenApiType,
|
||||
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {
|
||||
GuildFolderIconSchema,
|
||||
MentionReplyPreferencesSchema,
|
||||
ProfilePrivacyLevelSchema,
|
||||
RelationshipTypesSchema,
|
||||
@@ -257,21 +257,6 @@ export const CustomStatusResponse = z.object({
|
||||
|
||||
export type CustomStatusResponse = z.infer<typeof CustomStatusResponse>;
|
||||
|
||||
const GuildFolderIconSchema = withOpenApiType(
|
||||
createNamedStringLiteralUnion(
|
||||
[
|
||||
[GuildFolderIcons.FOLDER, 'FOLDER', 'Classic folder icon'],
|
||||
[GuildFolderIcons.STAR, 'STAR', 'Star icon'],
|
||||
[GuildFolderIcons.HEART, 'HEART', 'Heart icon'],
|
||||
[GuildFolderIcons.BOOKMARK, 'BOOKMARK', 'Bookmark icon'],
|
||||
[GuildFolderIcons.GAME_CONTROLLER, 'GAME_CONTROLLER', 'Game controller icon'],
|
||||
[GuildFolderIcons.SHIELD, 'SHIELD', 'Shield icon'],
|
||||
[GuildFolderIcons.MUSIC_NOTE, 'MUSIC_NOTE', 'Music note icon'],
|
||||
] as const,
|
||||
'Guild folder icon',
|
||||
),
|
||||
'GuildFolderIconType',
|
||||
);
|
||||
export const UserSettingsResponse = z.object({
|
||||
status: z.string().describe('The current online status of the user'),
|
||||
status_resets_at: z.iso.datetime().nullish().describe('ISO8601 timestamp of when the status will reset'),
|
||||
@@ -454,13 +439,7 @@ export interface PendingBulkMessageDeletion {
|
||||
readonly message_count: number;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
readonly bio: string | null;
|
||||
readonly banner: string | null;
|
||||
readonly banner_color?: number | null;
|
||||
readonly pronouns: string | null;
|
||||
readonly accent_color: number | null;
|
||||
}
|
||||
export type UserProfile = Readonly<UserProfileResponse>;
|
||||
|
||||
export interface UserPartial {
|
||||
readonly id: string;
|
||||
@@ -688,3 +667,5 @@ export const PhoneGateEscapePreviewResponse = z.object({
|
||||
});
|
||||
|
||||
export type PhoneGateEscapePreviewResponse = z.infer<typeof PhoneGateEscapePreviewResponse>;
|
||||
|
||||
export const RelationshipListResponse = z.array(RelationshipResponse);
|
||||
Reference in new issue
Block a user