Enhance environment configuration and expand Prisma schema for bot management
- Added OWNER_USER_IDS to .env.example for specifying global bot owners. - Introduced new models in the Prisma schema: CommandOverride, OwnerTeamMember, GlobalUserBlacklist, GuildBlacklist, FeatureFlag, BotPresenceConfig, ChangelogEntry, and OwnerAuditLog to support advanced bot management features. - Updated env.ts to preprocess OWNER_USER_IDS for better handling of global bot owner IDs. - Modified ModulePlaceholderPage to ensure proper routing for remaining dashboard modules.
This commit is contained in:
279
packages/shared/src/dashboard.ts
Normal file
279
packages/shared/src/dashboard.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Discord snowflake ID: 17-20 digit numeric string.
|
||||
*/
|
||||
export const SnowflakeSchema = z.string().regex(/^\d{17,20}$/, 'Invalid Discord ID');
|
||||
|
||||
/**
|
||||
* Optional snowflake field for forms: accepts an empty string (treated as
|
||||
* "unset"/cleared) or a valid snowflake. Kept as a plain string (rather than
|
||||
* transformed to `undefined`) so that `JSON.stringify` does not silently
|
||||
* drop the field when a user clears it in the dashboard - the API layer
|
||||
* maps `''` to `null` when persisting.
|
||||
*/
|
||||
export const OptionalSnowflakeSchema = z.union([z.literal(''), SnowflakeSchema]);
|
||||
|
||||
function nonEmptyPatch<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
|
||||
return schema.partial().refine((value) => Object.keys(value).length > 0, {
|
||||
message: 'At least one field is required'
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Moderation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const EscalationActionSchema = z.enum(['TIMEOUT', 'KICK', 'BAN']);
|
||||
export type EscalationAction = z.infer<typeof EscalationActionSchema>;
|
||||
|
||||
export const EscalationRuleDashboardSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
warnCount: z.number().int().positive(),
|
||||
action: EscalationActionSchema,
|
||||
durationMs: z.number().int().positive().nullable().optional(),
|
||||
reason: z.string().max(500).nullable().optional(),
|
||||
enabled: z.boolean().default(true)
|
||||
});
|
||||
export type EscalationRuleDashboard = z.infer<typeof EscalationRuleDashboardSchema>;
|
||||
|
||||
export const ModerationDashboardSchema = z.object({
|
||||
moderationEnabled: z.boolean(),
|
||||
escalations: z.array(EscalationRuleDashboardSchema)
|
||||
});
|
||||
export type ModerationDashboard = z.infer<typeof ModerationDashboardSchema>;
|
||||
|
||||
export const ModerationDashboardPatchSchema = z
|
||||
.object({
|
||||
moderationEnabled: z.boolean().optional(),
|
||||
escalations: z.array(EscalationRuleDashboardSchema).optional()
|
||||
})
|
||||
.refine((value) => Object.keys(value).length > 0, { message: 'At least one field is required' });
|
||||
export type ModerationDashboardPatch = z.infer<typeof ModerationDashboardPatchSchema>;
|
||||
|
||||
export const CaseDashboardSchema = z.object({
|
||||
id: z.string(),
|
||||
caseNumber: z.number().int(),
|
||||
targetUserId: z.string(),
|
||||
moderatorId: z.string(),
|
||||
action: z.string(),
|
||||
reason: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
deletedAt: z.string().nullable()
|
||||
});
|
||||
export type CaseDashboard = z.infer<typeof CaseDashboardSchema>;
|
||||
|
||||
export const CaseListQuerySchema = z.object({
|
||||
search: z.string().trim().max(100).optional(),
|
||||
action: z.string().trim().max(50).optional(),
|
||||
limit: z.coerce.number().int().positive().max(100).default(25)
|
||||
});
|
||||
export type CaseListQuery = z.infer<typeof CaseListQuerySchema>;
|
||||
|
||||
export const WarningDashboardSchema = z.object({
|
||||
id: z.string(),
|
||||
userId: z.string(),
|
||||
moderatorId: z.string(),
|
||||
reason: z.string().nullable(),
|
||||
caseId: z.string().nullable(),
|
||||
createdAt: z.string()
|
||||
});
|
||||
export type WarningDashboard = z.infer<typeof WarningDashboardSchema>;
|
||||
|
||||
export const WarningListQuerySchema = z.object({
|
||||
userId: SnowflakeSchema.optional()
|
||||
});
|
||||
export type WarningListQuery = z.infer<typeof WarningListQuerySchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AutoMod
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const AntiRaidActionSchema = z.enum(['LOCKDOWN']);
|
||||
|
||||
export const AutomodConfigDashboardSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
antiRaidEnabled: z.boolean(),
|
||||
antiRaidJoinThreshold: z.number().int().positive(),
|
||||
antiRaidWindowSeconds: z.number().int().positive(),
|
||||
antiRaidAction: AntiRaidActionSchema,
|
||||
antiNukeEnabled: z.boolean(),
|
||||
antiNukeBanThreshold: z.number().int().positive(),
|
||||
antiNukeChannelThreshold: z.number().int().positive(),
|
||||
antiNukeWindowSeconds: z.number().int().positive(),
|
||||
lockdownActive: z.boolean()
|
||||
});
|
||||
export type AutomodConfigDashboard = z.infer<typeof AutomodConfigDashboardSchema>;
|
||||
|
||||
export const AutomodConfigDashboardPatchSchema = nonEmptyPatch(AutomodConfigDashboardSchema);
|
||||
export type AutomodConfigDashboardPatch = z.infer<typeof AutomodConfigDashboardPatchSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Logging
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const LogEventTypeDashboardSchema = z.enum([
|
||||
'MESSAGE_EDIT',
|
||||
'MESSAGE_DELETE',
|
||||
'MESSAGE_BULK_DELETE',
|
||||
'MEMBER_JOIN',
|
||||
'MEMBER_LEAVE',
|
||||
'MEMBER_BAN',
|
||||
'MEMBER_UNBAN',
|
||||
'MEMBER_UPDATE',
|
||||
'CHANNEL_CREATE',
|
||||
'CHANNEL_DELETE',
|
||||
'CHANNEL_UPDATE',
|
||||
'VOICE_JOIN',
|
||||
'VOICE_LEAVE',
|
||||
'VOICE_MOVE',
|
||||
'INVITE_CREATE',
|
||||
'EMOJI_CREATE',
|
||||
'EMOJI_UPDATE',
|
||||
'EMOJI_DELETE',
|
||||
'STICKER_CREATE',
|
||||
'STICKER_UPDATE',
|
||||
'STICKER_DELETE',
|
||||
'THREAD_CREATE',
|
||||
'THREAD_UPDATE',
|
||||
'THREAD_DELETE',
|
||||
'GUILD_UPDATE',
|
||||
'MOD_ACTION'
|
||||
]);
|
||||
export type LogEventTypeDashboard = z.infer<typeof LogEventTypeDashboardSchema>;
|
||||
|
||||
export const LogChannelMappingSchema = z.object({
|
||||
eventType: LogEventTypeDashboardSchema,
|
||||
channelId: SnowflakeSchema
|
||||
});
|
||||
export type LogChannelMapping = z.infer<typeof LogChannelMappingSchema>;
|
||||
|
||||
export const LoggingConfigDashboardSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
ignoreBots: z.boolean(),
|
||||
ignoredChannelIds: z.array(z.string()),
|
||||
ignoredRoleIds: z.array(z.string()),
|
||||
logChannels: z.array(LogChannelMappingSchema)
|
||||
});
|
||||
export type LoggingConfigDashboard = z.infer<typeof LoggingConfigDashboardSchema>;
|
||||
|
||||
export const LoggingConfigDashboardPatchSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
ignoreBots: z.boolean().optional(),
|
||||
ignoredChannelIds: z.array(z.string()).optional(),
|
||||
ignoredRoleIds: z.array(z.string()).optional(),
|
||||
logChannels: z.array(LogChannelMappingSchema).optional()
|
||||
})
|
||||
.refine((value) => Object.keys(value).length > 0, { message: 'At least one field is required' });
|
||||
export type LoggingConfigDashboardPatch = z.infer<typeof LoggingConfigDashboardPatchSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Welcome & Leave
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const WelcomeMessageTypeDashboardSchema = z.enum(['TEXT', 'EMBED', 'IMAGE']);
|
||||
|
||||
export const WelcomeConfigDashboardSchema = z.object({
|
||||
welcomeEnabled: z.boolean(),
|
||||
leaveEnabled: z.boolean(),
|
||||
welcomeChannelId: OptionalSnowflakeSchema,
|
||||
leaveChannelId: OptionalSnowflakeSchema,
|
||||
welcomeType: WelcomeMessageTypeDashboardSchema,
|
||||
welcomeContent: z.string().max(2000).nullable().optional(),
|
||||
welcomeEmbed: z.record(z.string(), z.unknown()).nullable().optional(),
|
||||
leaveContent: z.string().max(2000).nullable().optional(),
|
||||
leaveEmbed: z.record(z.string(), z.unknown()).nullable().optional(),
|
||||
welcomeDmEnabled: z.boolean(),
|
||||
welcomeDmContent: z.string().max(2000).nullable().optional(),
|
||||
userAutoroleIds: z.array(z.string()),
|
||||
botAutoroleIds: z.array(z.string()),
|
||||
imageTitle: z.string().max(200).nullable().optional(),
|
||||
imageSubtitle: z.string().max(200).nullable().optional()
|
||||
});
|
||||
export type WelcomeConfigDashboard = z.infer<typeof WelcomeConfigDashboardSchema>;
|
||||
|
||||
export const WelcomeConfigDashboardPatchSchema = nonEmptyPatch(WelcomeConfigDashboardSchema);
|
||||
export type WelcomeConfigDashboardPatch = z.infer<typeof WelcomeConfigDashboardPatchSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Verification
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const VerificationModeDashboardSchema = z.enum(['BUTTON', 'CAPTCHA']);
|
||||
export const VerificationFailActionDashboardSchema = z.enum(['KICK', 'BAN', 'NONE']);
|
||||
|
||||
export const VerificationConfigDashboardSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
channelId: OptionalSnowflakeSchema,
|
||||
verifiedRoleId: OptionalSnowflakeSchema,
|
||||
unverifiedRoleId: OptionalSnowflakeSchema,
|
||||
mode: VerificationModeDashboardSchema,
|
||||
minAccountAgeDays: z.number().int().nonnegative(),
|
||||
failAction: VerificationFailActionDashboardSchema
|
||||
});
|
||||
export type VerificationConfigDashboard = z.infer<typeof VerificationConfigDashboardSchema>;
|
||||
|
||||
export const VerificationConfigDashboardPatchSchema = nonEmptyPatch(VerificationConfigDashboardSchema);
|
||||
export type VerificationConfigDashboardPatch = z.infer<typeof VerificationConfigDashboardPatchSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Leveling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const LevelUpModeDashboardSchema = z.enum(['CHANNEL', 'DM', 'OFF']);
|
||||
|
||||
export const LevelingConfigDashboardSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
textXpMin: z.number().int().positive(),
|
||||
textXpMax: z.number().int().positive(),
|
||||
textCooldownSeconds: z.number().int().positive(),
|
||||
voiceXpPerMinute: z.number().int().nonnegative(),
|
||||
noXpChannelIds: z.array(z.string()),
|
||||
noXpRoleIds: z.array(z.string()),
|
||||
roleMultipliers: z.record(z.string(), z.number().positive()),
|
||||
channelMultipliers: z.record(z.string(), z.number().positive()),
|
||||
levelUpMode: LevelUpModeDashboardSchema,
|
||||
levelUpChannelId: OptionalSnowflakeSchema,
|
||||
levelUpMessage: z.string().max(500).nullable().optional(),
|
||||
stackRewards: z.boolean(),
|
||||
cardAccentColor: z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
||||
cardBackgroundColor: z.string().regex(/^#[0-9a-fA-F]{6}$/)
|
||||
});
|
||||
export type LevelingConfigDashboard = z.infer<typeof LevelingConfigDashboardSchema>;
|
||||
|
||||
export const LevelingConfigDashboardPatchSchema = nonEmptyPatch(LevelingConfigDashboardSchema);
|
||||
export type LevelingConfigDashboardPatch = z.infer<typeof LevelingConfigDashboardPatchSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Economy
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const EconomyConfigDashboardSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
currencyName: z.string().min(1).max(32),
|
||||
currencySymbol: z.string().min(1).max(8),
|
||||
dailyAmount: z.number().int().nonnegative(),
|
||||
weeklyAmount: z.number().int().nonnegative(),
|
||||
workMin: z.number().int().nonnegative(),
|
||||
workMax: z.number().int().nonnegative(),
|
||||
workCooldownSeconds: z.number().int().positive(),
|
||||
startingBalance: z.number().int().nonnegative()
|
||||
});
|
||||
export type EconomyConfigDashboard = z.infer<typeof EconomyConfigDashboardSchema>;
|
||||
|
||||
export const EconomyConfigDashboardPatchSchema = nonEmptyPatch(EconomyConfigDashboardSchema);
|
||||
export type EconomyConfigDashboardPatch = z.infer<typeof EconomyConfigDashboardPatchSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fun
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const FunConfigDashboardSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
mediaEnabled: z.boolean()
|
||||
});
|
||||
export type FunConfigDashboard = z.infer<typeof FunConfigDashboardSchema>;
|
||||
|
||||
export const FunConfigDashboardPatchSchema = nonEmptyPatch(FunConfigDashboardSchema);
|
||||
export type FunConfigDashboardPatch = z.infer<typeof FunConfigDashboardPatchSchema>;
|
||||
Reference in New Issue
Block a user