Implement Phase 7 features and enhance dashboard schemas
- Updated PHASE-TRACKING.md to reflect the completion of Batch A features, including the implementation of dashboard pages and API routes for various modules. - Added new Prisma models and migration for owner features, including CommandOverride and GlobalUserBlacklist. - Expanded dashboard schemas in dashboard.ts to include Ticket, Giveaway, Self Role, Tag, and Starboard configurations, along with their respective validation tests in dashboard.test.ts. - Enhanced localization files with new i18n keys for the added modules and features. - Verified successful builds and tests across multiple packages, ensuring stability and functionality of new features.
This commit is contained in:
@@ -9,12 +9,22 @@
|
|||||||
|
|
||||||
## Phase 7 – WebUI Modul-Seiten + Owner-Panel (Status: in Arbeit)
|
## Phase 7 – WebUI Modul-Seiten + Owner-Panel (Status: in Arbeit)
|
||||||
|
|
||||||
### Geplant
|
### Abgeschlossen (Batch A)
|
||||||
|
|
||||||
- Funktionierende Dashboard-Seiten je Modul (Settings-Framework + Zod-API, keine Platzhalter)
|
- Prisma-Grundlage für Owner-Features: `CommandOverride`, `OwnerTeamMember`, `GlobalUserBlacklist`, `GuildBlacklist`, `FeatureFlag`, `BotPresenceConfig`, `ChangelogEntry`, `OwnerAuditLog` inkl. Migration `20260722190000_phase7_owner`
|
||||||
- Owner-Panel: Übersicht, Guilds, User-Blacklist, Feature-Flags, Präsenz, Team, Jobs, Changelog, Audit
|
- Shared Zod-Schemas für Dashboard-Module in `packages/shared/src/dashboard.ts` (Moderation, Automod, Logging, Welcome, Verification, Leveling, Economy, Fun, Cases, Warnings) + Tests
|
||||||
- Prisma: Owner-/Command-Override-Modelle
|
- `OWNER_USER_IDS`-Env-Variable (WebUI-Env-Schema + `.env.example`), noch ohne Owner-Panel-Nutzung
|
||||||
|
- API-Routen (GET+PATCH) für: `moderation`, `automod`, `logging`, `welcome`, `verification`, `leveling`, `economy`, `fun`; zusätzlich GET `cases` und `warnings`
|
||||||
|
- Funktionierende Dashboard-Seiten (Formular + Speichern über SettingsForm/SaveBar, echte Daten, keine Platzhalter) für alle acht Batch-A-Module, inkl. Cases-Tabelle auf der Moderation-Seite
|
||||||
|
- i18n-Keys `modulePages.*` in `de.json` und `en.json` ergänzt
|
||||||
|
- Platzhalter-Route `dashboard/[guildId]/[module]/page.tsx` greift nur noch für die verbleibenden Batch-B/C-Module (Batch-A-Routen haben eigene Ordner mit Vorrang)
|
||||||
|
- Verifiziert: `pnpm --filter @nexumi/shared build+test`, `pnpm --filter @nexumi/webui typecheck+lint`, `pnpm --filter @nexumi/bot typecheck+lint+test` grün; `next build` kompiliert und rendert alle Seiten (inkl. neue Batch-A-Routen) erfolgreich – einzige Auffälligkeit ist ein bekanntes Windows-only `EPERM`-Symlink-Problem bei `output: standalone` außerhalb von Docker, ohne Auswirkung auf den Linux-Container-Build
|
||||||
|
|
||||||
|
### Offen (Batch B/C)
|
||||||
|
|
||||||
|
- Batch B: Tickets, Giveaways, Reaction-Roles, Custom-Commands, Polls (eigene Module + Seiten, aktuell noch über `[module]`-Platzhalter)
|
||||||
|
- Batch C: Owner-Panel-UI (Übersicht, Guilds, User-Blacklist, Feature-Flags, Präsenz, Team, Jobs, Changelog, Audit) auf Basis der jetzt vorhandenen Prisma-Modelle
|
||||||
|
|
||||||
## Nächster geplanter Schritt
|
## Nächster geplanter Schritt
|
||||||
|
|
||||||
- Phase 7 fertigstellen, manuell freigeben, danach Phase 8 (Landing, Status, Rechtsseiten).
|
- Batch B (Restmodule) und Batch C (Owner-Panel-UI) umsetzen, danach Phase 7 komplett manuell freigeben, anschließend Phase 8 (Landing, Status, Rechtsseiten).
|
||||||
|
|||||||
@@ -4,9 +4,18 @@ import {
|
|||||||
CaseListQuerySchema,
|
CaseListQuerySchema,
|
||||||
EconomyConfigDashboardPatchSchema,
|
EconomyConfigDashboardPatchSchema,
|
||||||
FunConfigDashboardSchema,
|
FunConfigDashboardSchema,
|
||||||
|
GiveawayCreateDashboardSchema,
|
||||||
LoggingConfigDashboardPatchSchema,
|
LoggingConfigDashboardPatchSchema,
|
||||||
ModerationDashboardPatchSchema,
|
ModerationDashboardPatchSchema,
|
||||||
OptionalSnowflakeSchema,
|
OptionalSnowflakeSchema,
|
||||||
|
ScheduledMessageDashboardCreateSchema,
|
||||||
|
SelfRolePanelDashboardCreateSchema,
|
||||||
|
SocialFeedDashboardCreateSchema,
|
||||||
|
StarboardConfigDashboardPatchSchema,
|
||||||
|
SuggestionActionSchema,
|
||||||
|
TagDashboardCreateSchema,
|
||||||
|
TicketCategoryDashboardCreateSchema,
|
||||||
|
TicketConfigDashboardPatchSchema,
|
||||||
WelcomeConfigDashboardPatchSchema
|
WelcomeConfigDashboardPatchSchema
|
||||||
} from './dashboard.js';
|
} from './dashboard.js';
|
||||||
|
|
||||||
@@ -74,4 +83,98 @@ describe('dashboard schemas', () => {
|
|||||||
const parsed = CaseListQuerySchema.parse({});
|
const parsed = CaseListQuerySchema.parse({});
|
||||||
expect(parsed.limit).toBe(25);
|
expect(parsed.limit).toBe(25);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('validates ticket config patches', () => {
|
||||||
|
const parsed = TicketConfigDashboardPatchSchema.parse({ mode: 'THREAD', inactivityHours: 24 });
|
||||||
|
expect(parsed.mode).toBe('THREAD');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates ticket category creation', () => {
|
||||||
|
const parsed = TicketCategoryDashboardCreateSchema.parse({
|
||||||
|
name: 'Support',
|
||||||
|
description: null,
|
||||||
|
emoji: '🎫',
|
||||||
|
supportRoleIds: ['123456789012345678']
|
||||||
|
});
|
||||||
|
expect(parsed.supportRoleIds).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates giveaway creation input', () => {
|
||||||
|
const parsed = GiveawayCreateDashboardSchema.parse({
|
||||||
|
channelId: '123456789012345678',
|
||||||
|
prize: 'Nitro',
|
||||||
|
winnerCount: 1,
|
||||||
|
endsAt: new Date(Date.now() + 60_000).toISOString()
|
||||||
|
});
|
||||||
|
expect(parsed.prize).toBe('Nitro');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects giveaway creation with invalid date', () => {
|
||||||
|
expect(() =>
|
||||||
|
GiveawayCreateDashboardSchema.parse({
|
||||||
|
channelId: '123456789012345678',
|
||||||
|
prize: 'Nitro',
|
||||||
|
winnerCount: 1,
|
||||||
|
endsAt: 'not-a-date'
|
||||||
|
})
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates self role panel creation', () => {
|
||||||
|
const parsed = SelfRolePanelDashboardCreateSchema.parse({
|
||||||
|
channelId: '123456789012345678',
|
||||||
|
title: 'Colors',
|
||||||
|
mode: 'BUTTONS',
|
||||||
|
behavior: 'NORMAL',
|
||||||
|
roles: [{ roleId: '123456789012345678', label: 'Red' }]
|
||||||
|
});
|
||||||
|
expect(parsed.roles).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates tag creation', () => {
|
||||||
|
const parsed = TagDashboardCreateSchema.parse({
|
||||||
|
name: 'rules',
|
||||||
|
content: 'Please read the rules',
|
||||||
|
responseType: 'TEXT',
|
||||||
|
allowedRoleIds: [],
|
||||||
|
allowedChannelIds: []
|
||||||
|
});
|
||||||
|
expect(parsed.name).toBe('rules');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates starboard patches', () => {
|
||||||
|
const parsed = StarboardConfigDashboardPatchSchema.parse({ threshold: 5 });
|
||||||
|
expect(parsed.threshold).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates suggestion staff actions', () => {
|
||||||
|
const parsed = SuggestionActionSchema.parse({ status: 'APPROVED', staffReason: 'Great idea' });
|
||||||
|
expect(parsed.status).toBe('APPROVED');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates social feed creation', () => {
|
||||||
|
const parsed = SocialFeedDashboardCreateSchema.parse({
|
||||||
|
type: 'TWITCH',
|
||||||
|
sourceId: 'somechannel',
|
||||||
|
channelId: '123456789012345678',
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
expect(parsed.type).toBe('TWITCH');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates scheduled message creation requires cron or runAt', () => {
|
||||||
|
expect(() =>
|
||||||
|
ScheduledMessageDashboardCreateSchema.parse({
|
||||||
|
channelId: '123456789012345678',
|
||||||
|
content: 'Hello'
|
||||||
|
})
|
||||||
|
).toThrow();
|
||||||
|
|
||||||
|
const parsed = ScheduledMessageDashboardCreateSchema.parse({
|
||||||
|
channelId: '123456789012345678',
|
||||||
|
content: 'Hello',
|
||||||
|
runAt: new Date(Date.now() + 60_000).toISOString()
|
||||||
|
});
|
||||||
|
expect(parsed.content).toBe('Hello');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import {
|
||||||
|
SelfRoleBehaviorSchema,
|
||||||
|
SelfRoleEntrySchema,
|
||||||
|
SelfRoleModeSchema,
|
||||||
|
SuggestionStatusSchema,
|
||||||
|
TagResponseTypeSchema,
|
||||||
|
TicketModeSchema
|
||||||
|
} from './phase4.js';
|
||||||
|
import { SocialFeedTypeSchema } from './phase5.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discord snowflake ID: 17-20 digit numeric string.
|
* Discord snowflake ID: 17-20 digit numeric string.
|
||||||
@@ -277,3 +286,352 @@ export type FunConfigDashboard = z.infer<typeof FunConfigDashboardSchema>;
|
|||||||
|
|
||||||
export const FunConfigDashboardPatchSchema = nonEmptyPatch(FunConfigDashboardSchema);
|
export const FunConfigDashboardPatchSchema = nonEmptyPatch(FunConfigDashboardSchema);
|
||||||
export type FunConfigDashboardPatch = z.infer<typeof FunConfigDashboardPatchSchema>;
|
export type FunConfigDashboardPatch = z.infer<typeof FunConfigDashboardPatchSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tickets
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const TicketConfigDashboardSchema = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
mode: TicketModeSchema,
|
||||||
|
categoryId: OptionalSnowflakeSchema,
|
||||||
|
logChannelId: OptionalSnowflakeSchema,
|
||||||
|
transcriptDm: z.boolean(),
|
||||||
|
inactivityHours: z.number().int().positive(),
|
||||||
|
ratingEnabled: z.boolean()
|
||||||
|
});
|
||||||
|
export type TicketConfigDashboard = z.infer<typeof TicketConfigDashboardSchema>;
|
||||||
|
|
||||||
|
export const TicketConfigDashboardPatchSchema = nonEmptyPatch(TicketConfigDashboardSchema);
|
||||||
|
export type TicketConfigDashboardPatch = z.infer<typeof TicketConfigDashboardPatchSchema>;
|
||||||
|
|
||||||
|
export const TicketCategoryDashboardSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z.string().min(1).max(100),
|
||||||
|
description: z.string().max(500).nullable().optional(),
|
||||||
|
emoji: z.string().max(32).nullable().optional(),
|
||||||
|
supportRoleIds: z.array(z.string())
|
||||||
|
});
|
||||||
|
export type TicketCategoryDashboard = z.infer<typeof TicketCategoryDashboardSchema>;
|
||||||
|
|
||||||
|
export const TicketCategoryDashboardCreateSchema = TicketCategoryDashboardSchema.omit({ id: true });
|
||||||
|
export type TicketCategoryDashboardCreate = z.infer<typeof TicketCategoryDashboardCreateSchema>;
|
||||||
|
|
||||||
|
export const TicketCategoryDashboardUpdateSchema = nonEmptyPatch(TicketCategoryDashboardCreateSchema);
|
||||||
|
export type TicketCategoryDashboardUpdate = z.infer<typeof TicketCategoryDashboardUpdateSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Giveaways
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GiveawayDashboardSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
guildId: z.string(),
|
||||||
|
channelId: z.string(),
|
||||||
|
messageId: z.string().nullable(),
|
||||||
|
prize: z.string(),
|
||||||
|
winnerCount: z.number().int(),
|
||||||
|
endsAt: z.string(),
|
||||||
|
requiredRoleId: z.string().nullable(),
|
||||||
|
requiredLevel: z.number().int().nullable(),
|
||||||
|
requiredMemberDays: z.number().int().nullable(),
|
||||||
|
paused: z.boolean(),
|
||||||
|
endedAt: z.string().nullable(),
|
||||||
|
winners: z.array(z.string()),
|
||||||
|
entrants: z.array(z.string()),
|
||||||
|
hostId: z.string(),
|
||||||
|
createdAt: z.string()
|
||||||
|
});
|
||||||
|
export type GiveawayDashboard = z.infer<typeof GiveawayDashboardSchema>;
|
||||||
|
|
||||||
|
export const GiveawayCreateDashboardSchema = z.object({
|
||||||
|
channelId: SnowflakeSchema,
|
||||||
|
prize: z.string().min(1).max(200),
|
||||||
|
winnerCount: z.number().int().positive().max(50),
|
||||||
|
endsAt: z.string().refine((value) => !Number.isNaN(Date.parse(value)), 'Invalid date'),
|
||||||
|
requiredRoleId: OptionalSnowflakeSchema.optional(),
|
||||||
|
requiredLevel: z.number().int().nonnegative().nullable().optional(),
|
||||||
|
requiredMemberDays: z.number().int().nonnegative().nullable().optional()
|
||||||
|
});
|
||||||
|
export type GiveawayCreateDashboard = z.infer<typeof GiveawayCreateDashboardSchema>;
|
||||||
|
|
||||||
|
export const GiveawayActionSchema = z.enum(['end', 'pause', 'unpause']);
|
||||||
|
export type GiveawayAction = z.infer<typeof GiveawayActionSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Self Roles
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const SelfRolePanelDashboardSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
channelId: SnowflakeSchema,
|
||||||
|
title: z.string().min(1).max(200),
|
||||||
|
description: z.string().max(1000).nullable().optional(),
|
||||||
|
mode: SelfRoleModeSchema,
|
||||||
|
behavior: SelfRoleBehaviorSchema,
|
||||||
|
roles: z.array(SelfRoleEntrySchema).min(1),
|
||||||
|
expiresAt: z.string().nullable().optional()
|
||||||
|
});
|
||||||
|
export type SelfRolePanelDashboard = z.infer<typeof SelfRolePanelDashboardSchema>;
|
||||||
|
|
||||||
|
export const SelfRolePanelDashboardCreateSchema = SelfRolePanelDashboardSchema.omit({ id: true });
|
||||||
|
export type SelfRolePanelDashboardCreate = z.infer<typeof SelfRolePanelDashboardCreateSchema>;
|
||||||
|
|
||||||
|
export const SelfRolePanelDashboardUpdateSchema = nonEmptyPatch(SelfRolePanelDashboardCreateSchema);
|
||||||
|
export type SelfRolePanelDashboardUpdate = z.infer<typeof SelfRolePanelDashboardUpdateSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tags
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const TagDashboardSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z.string().min(1).max(50),
|
||||||
|
content: z.string().max(2000).nullable().optional(),
|
||||||
|
responseType: TagResponseTypeSchema,
|
||||||
|
triggerWord: z.string().max(100).nullable().optional(),
|
||||||
|
allowedRoleIds: z.array(z.string()),
|
||||||
|
allowedChannelIds: z.array(z.string())
|
||||||
|
});
|
||||||
|
export type TagDashboard = z.infer<typeof TagDashboardSchema>;
|
||||||
|
|
||||||
|
export const TagDashboardCreateSchema = TagDashboardSchema.omit({ id: true });
|
||||||
|
export type TagDashboardCreate = z.infer<typeof TagDashboardCreateSchema>;
|
||||||
|
|
||||||
|
export const TagDashboardUpdateSchema = nonEmptyPatch(TagDashboardCreateSchema);
|
||||||
|
export type TagDashboardUpdate = z.infer<typeof TagDashboardUpdateSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Starboard
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const StarboardConfigDashboardSchema = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
channelId: OptionalSnowflakeSchema,
|
||||||
|
emoji: z.string().min(1).max(32),
|
||||||
|
threshold: z.number().int().positive(),
|
||||||
|
allowSelfStar: z.boolean(),
|
||||||
|
ignoredChannelIds: z.array(z.string())
|
||||||
|
});
|
||||||
|
export type StarboardConfigDashboard = z.infer<typeof StarboardConfigDashboardSchema>;
|
||||||
|
|
||||||
|
export const StarboardConfigDashboardPatchSchema = nonEmptyPatch(StarboardConfigDashboardSchema);
|
||||||
|
export type StarboardConfigDashboardPatch = z.infer<typeof StarboardConfigDashboardPatchSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Suggestions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const SuggestionConfigDashboardSchema = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
openChannelId: OptionalSnowflakeSchema,
|
||||||
|
approvedChannelId: OptionalSnowflakeSchema,
|
||||||
|
deniedChannelId: OptionalSnowflakeSchema,
|
||||||
|
createThread: z.boolean()
|
||||||
|
});
|
||||||
|
export type SuggestionConfigDashboard = z.infer<typeof SuggestionConfigDashboardSchema>;
|
||||||
|
|
||||||
|
export const SuggestionConfigDashboardPatchSchema = nonEmptyPatch(SuggestionConfigDashboardSchema);
|
||||||
|
export type SuggestionConfigDashboardPatch = z.infer<typeof SuggestionConfigDashboardPatchSchema>;
|
||||||
|
|
||||||
|
export const SuggestionDashboardSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
authorId: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
status: SuggestionStatusSchema,
|
||||||
|
upvotes: z.number().int(),
|
||||||
|
downvotes: z.number().int(),
|
||||||
|
staffReason: z.string().nullable(),
|
||||||
|
createdAt: z.string()
|
||||||
|
});
|
||||||
|
export type SuggestionDashboard = z.infer<typeof SuggestionDashboardSchema>;
|
||||||
|
|
||||||
|
export const SuggestionListQuerySchema = z.object({
|
||||||
|
status: SuggestionStatusSchema.optional(),
|
||||||
|
limit: z.coerce.number().int().positive().max(100).default(25)
|
||||||
|
});
|
||||||
|
export type SuggestionListQuery = z.infer<typeof SuggestionListQuerySchema>;
|
||||||
|
|
||||||
|
export const SuggestionActionSchema = z.object({
|
||||||
|
status: SuggestionStatusSchema,
|
||||||
|
staffReason: z.string().max(500).nullable().optional()
|
||||||
|
});
|
||||||
|
export type SuggestionAction = z.infer<typeof SuggestionActionSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Birthdays
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const BirthdayConfigDashboardSchema = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
channelId: OptionalSnowflakeSchema,
|
||||||
|
roleId: OptionalSnowflakeSchema,
|
||||||
|
message: z.string().max(500).nullable().optional(),
|
||||||
|
timezone: z.string().min(1).max(64)
|
||||||
|
});
|
||||||
|
export type BirthdayConfigDashboard = z.infer<typeof BirthdayConfigDashboardSchema>;
|
||||||
|
|
||||||
|
export const BirthdayConfigDashboardPatchSchema = nonEmptyPatch(BirthdayConfigDashboardSchema);
|
||||||
|
export type BirthdayConfigDashboardPatch = z.infer<typeof BirthdayConfigDashboardPatchSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Temp-Voice
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const TempVoiceConfigDashboardSchema = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
hubChannelId: OptionalSnowflakeSchema,
|
||||||
|
categoryId: OptionalSnowflakeSchema,
|
||||||
|
defaultName: z.string().min(1).max(100),
|
||||||
|
defaultLimit: z.number().int().nonnegative().max(99)
|
||||||
|
});
|
||||||
|
export type TempVoiceConfigDashboard = z.infer<typeof TempVoiceConfigDashboardSchema>;
|
||||||
|
|
||||||
|
export const TempVoiceConfigDashboardPatchSchema = nonEmptyPatch(TempVoiceConfigDashboardSchema);
|
||||||
|
export type TempVoiceConfigDashboardPatch = z.infer<typeof TempVoiceConfigDashboardPatchSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Server Stats
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const StatsConfigDashboardSchema = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
membersChannelId: OptionalSnowflakeSchema,
|
||||||
|
onlineChannelId: OptionalSnowflakeSchema,
|
||||||
|
boostsChannelId: OptionalSnowflakeSchema,
|
||||||
|
membersTemplate: z.string().min(1).max(100),
|
||||||
|
onlineTemplate: z.string().min(1).max(100),
|
||||||
|
boostsTemplate: z.string().min(1).max(100)
|
||||||
|
});
|
||||||
|
export type StatsConfigDashboard = z.infer<typeof StatsConfigDashboardSchema>;
|
||||||
|
|
||||||
|
export const StatsConfigDashboardPatchSchema = nonEmptyPatch(StatsConfigDashboardSchema);
|
||||||
|
export type StatsConfigDashboardPatch = z.infer<typeof StatsConfigDashboardPatchSchema>;
|
||||||
|
|
||||||
|
export const ActivityStatSummarySchema = z.object({
|
||||||
|
messageCount: z.number().int().nonnegative(),
|
||||||
|
voiceMinutes: z.number().int().nonnegative(),
|
||||||
|
activeUserCount: z.number().int().nonnegative()
|
||||||
|
});
|
||||||
|
export type ActivityStatSummary = z.infer<typeof ActivityStatSummarySchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Social Feeds
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const SocialFeedDashboardSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
type: SocialFeedTypeSchema,
|
||||||
|
sourceId: z.string().min(1).max(200),
|
||||||
|
channelId: SnowflakeSchema,
|
||||||
|
rolePingId: OptionalSnowflakeSchema.optional(),
|
||||||
|
template: z.string().max(500).nullable().optional(),
|
||||||
|
enabled: z.boolean()
|
||||||
|
});
|
||||||
|
export type SocialFeedDashboard = z.infer<typeof SocialFeedDashboardSchema>;
|
||||||
|
|
||||||
|
export const SocialFeedDashboardCreateSchema = SocialFeedDashboardSchema.omit({ id: true });
|
||||||
|
export type SocialFeedDashboardCreate = z.infer<typeof SocialFeedDashboardCreateSchema>;
|
||||||
|
|
||||||
|
export const SocialFeedDashboardUpdateSchema = nonEmptyPatch(SocialFeedDashboardCreateSchema);
|
||||||
|
export type SocialFeedDashboardUpdate = z.infer<typeof SocialFeedDashboardUpdateSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Scheduler
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const ScheduledMessageDashboardSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
channelId: z.string(),
|
||||||
|
creatorId: z.string(),
|
||||||
|
content: z.string().nullable(),
|
||||||
|
rolePingId: z.string().nullable(),
|
||||||
|
cron: z.string().nullable(),
|
||||||
|
runAt: z.string().nullable(),
|
||||||
|
enabled: z.boolean(),
|
||||||
|
createdAt: z.string()
|
||||||
|
});
|
||||||
|
export type ScheduledMessageDashboard = z.infer<typeof ScheduledMessageDashboardSchema>;
|
||||||
|
|
||||||
|
export const ScheduledMessageDashboardCreateSchema = z
|
||||||
|
.object({
|
||||||
|
channelId: SnowflakeSchema,
|
||||||
|
content: z.string().max(2000).nullable().optional(),
|
||||||
|
rolePingId: OptionalSnowflakeSchema.optional(),
|
||||||
|
cron: z.string().min(1).max(100).nullable().optional(),
|
||||||
|
runAt: z
|
||||||
|
.string()
|
||||||
|
.refine((value) => !Number.isNaN(Date.parse(value)), 'Invalid date')
|
||||||
|
.nullable()
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
.refine((value) => Boolean(value.cron?.trim()) || Boolean(value.runAt), {
|
||||||
|
message: 'Either cron or runAt is required'
|
||||||
|
})
|
||||||
|
.refine((value) => Boolean(value.content?.trim()), {
|
||||||
|
message: 'Content is required'
|
||||||
|
});
|
||||||
|
export type ScheduledMessageDashboardCreate = z.infer<typeof ScheduledMessageDashboardCreateSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Guild Backups
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GuildBackupDashboardSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
createdById: z.string(),
|
||||||
|
createdAt: z.string(),
|
||||||
|
roleCount: z.number().int().nonnegative(),
|
||||||
|
channelCount: z.number().int().nonnegative()
|
||||||
|
});
|
||||||
|
export type GuildBackupDashboard = z.infer<typeof GuildBackupDashboardSchema>;
|
||||||
|
|
||||||
|
export const GuildBackupCreateDashboardSchema = z.object({
|
||||||
|
name: z.string().min(1).max(100)
|
||||||
|
});
|
||||||
|
export type GuildBackupCreateDashboard = z.infer<typeof GuildBackupCreateDashboardSchema>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Command Overrides
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const CommandOverrideDashboardSchema = z.object({
|
||||||
|
commandName: z.string().min(1).max(100),
|
||||||
|
enabled: z.boolean(),
|
||||||
|
allowedRoleIds: z.array(z.string()),
|
||||||
|
deniedRoleIds: z.array(z.string()),
|
||||||
|
allowedChannelIds: z.array(z.string()),
|
||||||
|
deniedChannelIds: z.array(z.string()),
|
||||||
|
cooldownSeconds: z.number().int().nonnegative().nullable()
|
||||||
|
});
|
||||||
|
export type CommandOverrideDashboard = z.infer<typeof CommandOverrideDashboardSchema>;
|
||||||
|
|
||||||
|
export const CommandOverrideDashboardUpsertSchema = z.object({
|
||||||
|
commandName: z.string().min(1).max(100),
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
allowedRoleIds: z.array(z.string()).default([]),
|
||||||
|
deniedRoleIds: z.array(z.string()).default([]),
|
||||||
|
allowedChannelIds: z.array(z.string()).default([]),
|
||||||
|
deniedChannelIds: z.array(z.string()).default([]),
|
||||||
|
cooldownSeconds: z.number().int().nonnegative().nullable().optional()
|
||||||
|
});
|
||||||
|
export type CommandOverrideDashboardUpsert = z.infer<typeof CommandOverrideDashboardUpsertSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Known slash command names, aggregated from every module's
|
||||||
|
* `command-definitions.ts` (`SlashCommandBuilder#setName`). Used by the
|
||||||
|
* dashboard's command-management page to list overridable commands without
|
||||||
|
* requiring a live bot connection.
|
||||||
|
*/
|
||||||
|
export const KNOWN_COMMAND_NAMES = [
|
||||||
|
'ban', 'unban', 'kick', 'timeout', 'untimeout', 'warn', 'purge', 'slowmode', 'lock', 'unlock',
|
||||||
|
'nick', 'case', 'modnote', 'automod', 'welcome', 'verify', 'rank', 'leaderboard', 'xp', 'balance',
|
||||||
|
'daily', 'weekly', 'work', 'pay', 'gamble', 'slots', 'blackjack', 'coinflip', 'shop', 'inventory',
|
||||||
|
'eco', 'userinfo', 'serverinfo', 'roleinfo', 'channelinfo', 'avatar', 'banner', 'poll', 'remindme',
|
||||||
|
'reminders', 'afk', 'emoji', 'sticker', 'timestamp', 'snipe', 'editsnipe', 'embed', '8ball', 'dice',
|
||||||
|
'flip', 'rps', 'choose', 'trivia', 'tictactoe', 'connect4', 'hangman', 'meme', 'cat', 'dog',
|
||||||
|
'giveaway', 'ticket', 'selfroles', 'tag', 'starboard', 'suggest', 'suggestion', 'birthday', 'voice',
|
||||||
|
'stats', 'invites', 'feeds', 'schedule', 'announce', 'backup'
|
||||||
|
] as const;
|
||||||
|
export type KnownCommandName = (typeof KNOWN_COMMAND_NAMES)[number];
|
||||||
|
|||||||
Reference in New Issue
Block a user