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:
smueller
2026-07-22 14:51:56 +02:00
parent 946283dfba
commit 1de28fa494
54 changed files with 3564 additions and 3 deletions

View File

@@ -0,0 +1,46 @@
import { AutomodConfigDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getAutomodDashboard, updateAutomodDashboard } from '@/lib/module-configs/automod';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getAutomodDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = AutomodConfigDashboardPatchSchema.parse(body);
const before = await getAutomodDashboard(guildId);
const after = await updateAutomodDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'automod.update',
path: `/dashboard/${guildId}/automod`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,25 @@
import { CaseListQuerySchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { listCases } from '@/lib/module-configs/cases';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const searchParams = request.nextUrl.searchParams;
const query = CaseListQuerySchema.parse({
search: searchParams.get('search') ?? undefined,
action: searchParams.get('action') ?? undefined,
limit: searchParams.get('limit') ?? undefined
});
const cases = await listCases(guildId, query);
return NextResponse.json({ cases });
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,46 @@
import { EconomyConfigDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getEconomyDashboard, updateEconomyDashboard } from '@/lib/module-configs/economy';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getEconomyDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = EconomyConfigDashboardPatchSchema.parse(body);
const before = await getEconomyDashboard(guildId);
const after = await updateEconomyDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'economy.update',
path: `/dashboard/${guildId}/economy`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,46 @@
import { FunConfigDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getFunDashboard, updateFunDashboard } from '@/lib/module-configs/fun';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getFunDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = FunConfigDashboardPatchSchema.parse(body);
const before = await getFunDashboard(guildId);
const after = await updateFunDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'fun.update',
path: `/dashboard/${guildId}/fun`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,46 @@
import { LevelingConfigDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getLevelingDashboard, updateLevelingDashboard } from '@/lib/module-configs/leveling';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getLevelingDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = LevelingConfigDashboardPatchSchema.parse(body);
const before = await getLevelingDashboard(guildId);
const after = await updateLevelingDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'leveling.update',
path: `/dashboard/${guildId}/leveling`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,46 @@
import { LoggingConfigDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getLoggingDashboard, updateLoggingDashboard } from '@/lib/module-configs/logging';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getLoggingDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = LoggingConfigDashboardPatchSchema.parse(body);
const before = await getLoggingDashboard(guildId);
const after = await updateLoggingDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'logging.update',
path: `/dashboard/${guildId}/logging`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,46 @@
import { ModerationDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getModerationDashboard, updateModerationDashboard } from '@/lib/module-configs/moderation';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getModerationDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = ModerationDashboardPatchSchema.parse(body);
const before = await getModerationDashboard(guildId);
const after = await updateModerationDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'moderation.update',
path: `/dashboard/${guildId}/moderation`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,46 @@
import { VerificationConfigDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getVerificationDashboard, updateVerificationDashboard } from '@/lib/module-configs/verification';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getVerificationDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = VerificationConfigDashboardPatchSchema.parse(body);
const before = await getVerificationDashboard(guildId);
const after = await updateVerificationDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'verification.update',
path: `/dashboard/${guildId}/verification`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,23 @@
import { WarningListQuerySchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { listWarnings } from '@/lib/module-configs/warnings';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const searchParams = request.nextUrl.searchParams;
const query = WarningListQuerySchema.parse({
userId: searchParams.get('userId') ?? undefined
});
const warnings = await listWarnings(guildId, query);
return NextResponse.json({ warnings });
} catch (error) {
return toApiErrorResponse(error);
}
}

View File

@@ -0,0 +1,46 @@
import { WelcomeConfigDashboardPatchSchema } from '@nexumi/shared';
import { type NextRequest, NextResponse } from 'next/server';
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
import { writeDashboardAudit } from '@/lib/audit';
import { getWelcomeDashboard, updateWelcomeDashboard } from '@/lib/module-configs/welcome';
import { prisma } from '@/lib/prisma';
interface RouteParams {
params: Promise<{ guildId: string }>;
}
export async function GET(_request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
await requireGuildAccess(guildId);
const config = await getWelcomeDashboard(guildId);
return NextResponse.json(config);
} catch (error) {
return toApiErrorResponse(error);
}
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
const { guildId } = await params;
try {
const session = await requireGuildAccess(guildId);
const body = await request.json();
const patch = WelcomeConfigDashboardPatchSchema.parse(body);
const before = await getWelcomeDashboard(guildId);
const after = await updateWelcomeDashboard(guildId, patch);
await writeDashboardAudit(prisma, {
guildId,
actorUserId: session.user.id,
action: 'welcome.update',
path: `/dashboard/${guildId}/welcome`,
before,
after
});
return NextResponse.json(after);
} catch (error) {
return toApiErrorResponse(error);
}
}