claudmax 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claudmax-1.0.16.tgz +0 -0
- package/{packages/cli/index.js → index.js} +2 -0
- package/package.json +27 -55
- package/.claude/settings.local.json +0 -7
- package/.env.example +0 -24
- package/.github/workflows/publish.yml +0 -31
- package/README.md +0 -178
- package/claudmax-mcp-1.0.2.tgz +0 -0
- package/help +0 -0
- package/help-wal +0 -0
- package/next-env.d.ts +0 -6
- package/next.config.mjs +0 -43
- package/packages/cli/claudmax-1.0.16.tgz +0 -0
- package/packages/cli/package.json +0 -33
- package/packages/mcp/claudmax-mcp-1.0.0.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.1.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.2.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.3.tgz +0 -0
- package/packages/mcp/index.js +0 -129
- package/packages/mcp/package-lock.json +0 -1146
- package/packages/mcp/package.json +0 -32
- package/postcss.config.mjs +0 -6
- package/prisma/schema.prisma +0 -130
- package/prisma/seed.ts +0 -27
- package/public/favicon.svg +0 -10
- package/public/robots.txt +0 -10
- package/run_build.sh +0 -4
- package/scripts/migrate-plans.js +0 -98
- package/scripts/seed-blog.ts +0 -1014
- package/src/app/admin/dashboard/AdminDashboardClient.tsx +0 -1546
- package/src/app/admin/dashboard/page.tsx +0 -13
- package/src/app/admin/page.tsx +0 -132
- package/src/app/api/admin/auth/me/route.ts +0 -34
- package/src/app/api/admin/health/route.ts +0 -110
- package/src/app/api/admin/keys/[id]/route.ts +0 -116
- package/src/app/api/admin/keys/route.ts +0 -192
- package/src/app/api/admin/keys-list/route.ts +0 -81
- package/src/app/api/admin/login/route.ts +0 -72
- package/src/app/api/admin/logout/route.ts +0 -8
- package/src/app/api/admin/migrate/route.ts +0 -133
- package/src/app/api/admin/plans/[id]/route.ts +0 -65
- package/src/app/api/admin/plans/route.ts +0 -66
- package/src/app/api/admin/posts/[id]/route.ts +0 -81
- package/src/app/api/admin/posts/route.ts +0 -83
- package/src/app/api/admin/seed/route.ts +0 -145
- package/src/app/api/admin/settings/route.ts +0 -44
- package/src/app/api/admin/stats/route.ts +0 -74
- package/src/app/api/admin/users/[id]/route.ts +0 -166
- package/src/app/api/admin/users/plans/route.ts +0 -45
- package/src/app/api/admin/users/route.ts +0 -202
- package/src/app/api/blog/[slug]/route.ts +0 -22
- package/src/app/api/blog/route.ts +0 -40
- package/src/app/api/cron/daily-status/route.ts +0 -208
- package/src/app/api/support/chat/route.ts +0 -55
- package/src/app/api/support/chat/session/route.ts +0 -62
- package/src/app/api/support/chat/stream/route.ts +0 -44
- package/src/app/api/support/email/route.ts +0 -63
- package/src/app/api/tools/understand_image/route.ts +0 -113
- package/src/app/api/tools/upload/route.ts +0 -179
- package/src/app/api/tools/web_search/route.ts +0 -99
- package/src/app/api/v1/audio/route.ts +0 -67
- package/src/app/api/v1/audio/speech/route.ts +0 -73
- package/src/app/api/v1/chat/completions/route.ts +0 -3
- package/src/app/api/v1/chat/route.ts +0 -1079
- package/src/app/api/v1/images/generations/route.ts +0 -93
- package/src/app/api/v1/info/route.ts +0 -30
- package/src/app/api/v1/key-status/route.ts +0 -109
- package/src/app/api/v1/key-status/stream/route.ts +0 -135
- package/src/app/api/v1/messages/count_tokens/route.ts +0 -22
- package/src/app/api/v1/messages/route.ts +0 -807
- package/src/app/api/v1/models/route.ts +0 -14
- package/src/app/api/v1/route.ts +0 -18
- package/src/app/blog/BlogClient.tsx +0 -193
- package/src/app/blog/[slug]/page.tsx +0 -117
- package/src/app/blog/page.tsx +0 -20
- package/src/app/check-usage/CheckUsageClient.tsx +0 -186
- package/src/app/check-usage/layout.tsx +0 -11
- package/src/app/check-usage/page.tsx +0 -15
- package/src/app/docs/layout.tsx +0 -16
- package/src/app/docs/page.tsx +0 -1055
- package/src/app/faq/FAQClient.tsx +0 -227
- package/src/app/faq/page.tsx +0 -21
- package/src/app/globals.css +0 -75
- package/src/app/layout.tsx +0 -80
- package/src/app/page.tsx +0 -256
- package/src/app/reseller/ResellerClient.tsx +0 -435
- package/src/app/reseller/page.tsx +0 -15
- package/src/app/setup.ps1/route.ts +0 -79
- package/src/app/setup.sh/route.ts +0 -113
- package/src/app/sitemap.ts +0 -50
- package/src/app/status/StatusClient.tsx +0 -103
- package/src/app/status/layout.tsx +0 -11
- package/src/app/status/page.tsx +0 -15
- package/src/app/support/SupportClient.tsx +0 -411
- package/src/app/support/page.tsx +0 -25
- package/src/app/v1/chat/completions/route.ts +0 -3
- package/src/app/v1/chat/route.ts +0 -4
- package/src/app/v1/messages/route.ts +0 -3
- package/src/components/Footer.tsx +0 -120
- package/src/components/Header.tsx +0 -131
- package/src/components/landing/features.tsx +0 -99
- package/src/components/ui/badge.tsx +0 -32
- package/src/components/ui/button.tsx +0 -46
- package/src/components/ui/card.tsx +0 -50
- package/src/components/ui/dialog.tsx +0 -97
- package/src/components/ui/dropdown-menu.tsx +0 -156
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -15
- package/src/components/ui/separator.tsx +0 -22
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/tabs.tsx +0 -51
- package/src/components/ui/toast.tsx +0 -103
- package/src/lib/auth.ts +0 -45
- package/src/lib/prisma.ts +0 -20
- package/src/lib/providers.ts +0 -158
- package/src/lib/security.ts +0 -165
- package/src/lib/utils.ts +0 -14
- package/src/middleware.ts +0 -30
- package/tailwind.config.ts +0 -53
- package/tsconfig.json +0 -41
- package/tsconfig.tsbuildinfo +0 -1
- package/vercel.json +0 -8
- /package/{packages/cli/bin → bin}/claudmax.js +0 -0
- /package/{packages/cli/claudmax-1.0.17.tgz → claudmax-1.0.17.tgz} +0 -0
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import bcrypt from 'bcryptjs';
|
|
4
|
-
import { signToken } from '@/lib/auth';
|
|
5
|
-
|
|
6
|
-
export async function POST(req: Request) {
|
|
7
|
-
try {
|
|
8
|
-
const { username, password } = await req.json();
|
|
9
|
-
|
|
10
|
-
if (!username || !password) {
|
|
11
|
-
return NextResponse.json({ error: 'Username and password are required' }, { status: 400 });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const admin = await prisma.admin.findUnique({ where: { username } });
|
|
15
|
-
|
|
16
|
-
if (!admin) {
|
|
17
|
-
return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (!admin.isActive) {
|
|
21
|
-
return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const valid = await bcrypt.compare(password, admin.password);
|
|
25
|
-
if (!valid) {
|
|
26
|
-
return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const token = signToken({
|
|
30
|
-
id: admin.id,
|
|
31
|
-
username: admin.username,
|
|
32
|
-
name: admin.name,
|
|
33
|
-
role: admin.role as 'super_admin' | 'admin' | 'reseller',
|
|
34
|
-
isActive: admin.isActive,
|
|
35
|
-
canCreateKey: admin.canCreateKey,
|
|
36
|
-
canDeleteKey: admin.canDeleteKey,
|
|
37
|
-
canBlockKey: admin.canBlockKey,
|
|
38
|
-
canManageTokens: admin.canManageTokens,
|
|
39
|
-
canCreateReseller: admin.canCreateReseller,
|
|
40
|
-
canManageResellers: admin.canManageResellers,
|
|
41
|
-
canManageUsers: admin.canManageUsers,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const { cookies } = await import('next/headers');
|
|
45
|
-
const cookieStore = await cookies();
|
|
46
|
-
cookieStore.set('admin_token', token, {
|
|
47
|
-
httpOnly: true,
|
|
48
|
-
secure: process.env.NODE_ENV === 'production',
|
|
49
|
-
sameSite: 'lax',
|
|
50
|
-
maxAge: 60 * 60 * 24,
|
|
51
|
-
path: '/',
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
return NextResponse.json({
|
|
55
|
-
id: admin.id,
|
|
56
|
-
username: admin.username,
|
|
57
|
-
name: admin.name,
|
|
58
|
-
role: admin.role,
|
|
59
|
-
isActive: admin.isActive,
|
|
60
|
-
canCreateKey: admin.canCreateKey,
|
|
61
|
-
canDeleteKey: admin.canDeleteKey,
|
|
62
|
-
canBlockKey: admin.canBlockKey,
|
|
63
|
-
canManageTokens: admin.canManageTokens,
|
|
64
|
-
canCreateReseller: admin.canCreateReseller,
|
|
65
|
-
canManageResellers: admin.canManageResellers,
|
|
66
|
-
canManageUsers: admin.canManageUsers,
|
|
67
|
-
});
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.error('Admin login error:', err);
|
|
70
|
-
return NextResponse.json({ error: 'Login failed' }, { status: 500 });
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Migration route: Fix Plan.tokensPerWindow from TEXT/BigInt to INTEGER
|
|
3
|
-
* Dynamically inspects existing schema and migrates data safely.
|
|
4
|
-
* Call: POST /api/admin/migrate with { migrate: true }
|
|
5
|
-
*/
|
|
6
|
-
import { NextResponse } from 'next/server';
|
|
7
|
-
import { createClient } from '@libsql/client';
|
|
8
|
-
import { getAuthUser } from '@/lib/auth';
|
|
9
|
-
|
|
10
|
-
export async function POST(req: Request) {
|
|
11
|
-
const me = await getAuthUser();
|
|
12
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
13
|
-
if (me.role !== 'super_admin') return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
14
|
-
|
|
15
|
-
const { migrate } = await req.json();
|
|
16
|
-
if (!migrate) return NextResponse.json({ error: 'migrate flag required' }, { status: 400 });
|
|
17
|
-
|
|
18
|
-
const databaseUrl = process.env.DATABASE_URL;
|
|
19
|
-
if (!databaseUrl) {
|
|
20
|
-
return NextResponse.json({ error: 'DATABASE_URL not configured' }, { status: 500 });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Parse auth token from DATABASE_URL (format: libsql://host?authToken=xxx)
|
|
24
|
-
const url = new URL(databaseUrl);
|
|
25
|
-
const authToken = url.searchParams.get('authToken') ?? undefined;
|
|
26
|
-
const dbUrl = `${url.protocol}//${url.host}${url.pathname}`;
|
|
27
|
-
const db = createClient({ url: dbUrl, authToken });
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
// Step 1: Inspect actual table schema
|
|
31
|
-
const tableInfo = await db.execute("PRAGMA table_info(Plan)");
|
|
32
|
-
const cols = tableInfo.rows as any[];
|
|
33
|
-
const colMap: Record<string, any> = {};
|
|
34
|
-
for (const c of cols) colMap[c.name] = c;
|
|
35
|
-
console.log('Current Plan columns:', Object.keys(colMap));
|
|
36
|
-
|
|
37
|
-
if (!colMap['tokensPerWindow']) {
|
|
38
|
-
return NextResponse.json({ error: 'tokensPerWindow column not found' }, { status: 500 });
|
|
39
|
-
}
|
|
40
|
-
console.log(`tokensPerWindow type: ${colMap['tokensPerWindow'].type}`);
|
|
41
|
-
|
|
42
|
-
// Step 2: Read all existing plan data
|
|
43
|
-
const plans = await db.execute("SELECT * FROM Plan");
|
|
44
|
-
console.log(`Found ${plans.rows.length} plan(s)`);
|
|
45
|
-
|
|
46
|
-
for (const row of plans.rows as any[]) {
|
|
47
|
-
const raw = String(row.tokensPerWindow ?? '5000000').replace('.0', '');
|
|
48
|
-
const cleaned = isNaN(parseInt(raw)) ? 500000 : parseInt(raw);
|
|
49
|
-
console.log(` "${row.name}": tokensPerWindow="${row.tokensPerWindow}" -> ${cleaned}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Step 3: Get all column names from existing table
|
|
53
|
-
const existingCols = Object.keys(colMap);
|
|
54
|
-
const newCols = ['id', 'name', 'tier', 'tokensPerWindow', 'requestsPerWindow',
|
|
55
|
-
'durationDays', 'displayMultiplier', 'minDurationDays', 'maxDurationDays',
|
|
56
|
-
'isActive', 'createdAt', 'updatedAt', 'createdByAdminId', 'adminsAllowed'];
|
|
57
|
-
|
|
58
|
-
// Build new table
|
|
59
|
-
await db.execute("DROP TABLE IF EXISTS _Plan_new");
|
|
60
|
-
await db.execute(`
|
|
61
|
-
CREATE TABLE _Plan_new (
|
|
62
|
-
id TEXT PRIMARY KEY,
|
|
63
|
-
name TEXT NOT NULL,
|
|
64
|
-
tier TEXT NOT NULL,
|
|
65
|
-
tokensPerWindow INTEGER NOT NULL,
|
|
66
|
-
requestsPerWindow INTEGER NOT NULL,
|
|
67
|
-
durationDays INTEGER NOT NULL,
|
|
68
|
-
displayMultiplier REAL DEFAULT 1.0,
|
|
69
|
-
minDurationDays INTEGER DEFAULT 0,
|
|
70
|
-
maxDurationDays INTEGER DEFAULT -1,
|
|
71
|
-
isActive INTEGER DEFAULT 1,
|
|
72
|
-
createdAt TEXT NOT NULL,
|
|
73
|
-
updatedAt TEXT NOT NULL,
|
|
74
|
-
createdByAdminId TEXT,
|
|
75
|
-
adminsAllowed TEXT DEFAULT '[]'
|
|
76
|
-
)
|
|
77
|
-
`);
|
|
78
|
-
|
|
79
|
-
// Step 4: Copy rows, converting tokensPerWindow to clean integer
|
|
80
|
-
for (const row of plans.rows as any[]) {
|
|
81
|
-
const raw = String(row.tokensPerWindow ?? '5000000').replace('.0', '');
|
|
82
|
-
const cleaned = isNaN(parseInt(raw)) ? 500000 : parseInt(raw);
|
|
83
|
-
|
|
84
|
-
const vals: any[] = [];
|
|
85
|
-
const usedCols: string[] = [];
|
|
86
|
-
for (const c of newCols) {
|
|
87
|
-
usedCols.push(c);
|
|
88
|
-
if (c === 'tokensPerWindow') {
|
|
89
|
-
vals.push(cleaned);
|
|
90
|
-
} else if (c === 'isActive') {
|
|
91
|
-
vals.push(row.isActive ?? 1);
|
|
92
|
-
} else if (row[c] !== undefined) {
|
|
93
|
-
vals.push(row[c]);
|
|
94
|
-
} else if (c === 'createdByAdminId') {
|
|
95
|
-
vals.push(null);
|
|
96
|
-
} else if (c === 'adminsAllowed') {
|
|
97
|
-
vals.push('[]');
|
|
98
|
-
} else {
|
|
99
|
-
vals.push(null);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
await db.execute({
|
|
105
|
-
sql: `INSERT INTO _Plan_new (${usedCols.join(', ')}) VALUES (${usedCols.map(() => '?').join(', ')})`,
|
|
106
|
-
args: vals,
|
|
107
|
-
});
|
|
108
|
-
} catch (insertErr: any) {
|
|
109
|
-
console.error(`Failed to insert plan "${row.name}":`, insertErr);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Step 5: Swap tables
|
|
114
|
-
await db.execute("DROP TABLE Plan");
|
|
115
|
-
await db.execute("ALTER TABLE _Plan_new RENAME TO Plan");
|
|
116
|
-
|
|
117
|
-
// Step 6: Verify
|
|
118
|
-
const verify = await db.execute("SELECT id, name, tokensPerWindow, typeof(tokensPerWindow) as type FROM Plan");
|
|
119
|
-
console.log('\nPost-migration:');
|
|
120
|
-
for (const row of verify.rows as any[]) {
|
|
121
|
-
console.log(` ${row.name}: ${row.tokensPerWindow} (type: ${row.type})`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return NextResponse.json({
|
|
125
|
-
success: true,
|
|
126
|
-
message: 'Plan.tokensPerWindow migrated to INTEGER',
|
|
127
|
-
plansMigrated: verify.rows.length,
|
|
128
|
-
});
|
|
129
|
-
} catch (err: any) {
|
|
130
|
-
console.error('Migration error:', err);
|
|
131
|
-
return NextResponse.json({ error: 'Migration failed', detail: String(err) }, { status: 500 });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import { getAuthUser } from '@/lib/auth';
|
|
4
|
-
|
|
5
|
-
type Params = { params: Promise<{ id: string }> };
|
|
6
|
-
|
|
7
|
-
export async function GET(req: Request, { params }: Params) {
|
|
8
|
-
const me = await getAuthUser();
|
|
9
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
10
|
-
if (!['super_admin', 'admin'].includes(me.role)) {
|
|
11
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const { id } = await params;
|
|
15
|
-
const plan = await prisma.plan.findUnique({ where: { id } });
|
|
16
|
-
if (!plan) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
17
|
-
|
|
18
|
-
return NextResponse.json({ ...plan, tokensPerWindow: Number(plan.tokensPerWindow) });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function PUT(req: Request, { params }: Params) {
|
|
22
|
-
try {
|
|
23
|
-
const me = await getAuthUser();
|
|
24
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
25
|
-
if (me.role !== 'super_admin') {
|
|
26
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const { id } = await params;
|
|
30
|
-
const body = await req.json();
|
|
31
|
-
const { name, tier, tokensPerWindow, requestsPerWindow, durationDays, displayMultiplier, minDurationDays, maxDurationDays, isActive } = body;
|
|
32
|
-
|
|
33
|
-
const plan = await prisma.plan.update({
|
|
34
|
-
where: { id },
|
|
35
|
-
data: {
|
|
36
|
-
name: name ?? undefined,
|
|
37
|
-
tier: tier ?? undefined,
|
|
38
|
-
tokensPerWindow: tokensPerWindow !== undefined ? tokensPerWindow : undefined,
|
|
39
|
-
requestsPerWindow: requestsPerWindow ?? undefined,
|
|
40
|
-
durationDays: durationDays ?? undefined,
|
|
41
|
-
displayMultiplier: displayMultiplier ?? undefined,
|
|
42
|
-
minDurationDays: minDurationDays ?? undefined,
|
|
43
|
-
maxDurationDays: maxDurationDays ?? undefined,
|
|
44
|
-
isActive: isActive ?? undefined,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return NextResponse.json({ ...plan, tokensPerWindow: Number(plan.tokensPerWindow) });
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.error('[/api/admin/plans/[id] PUT]', err);
|
|
51
|
-
return NextResponse.json({ error: 'Internal error', detail: String(err) }, { status: 500 });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function DELETE(req: Request, { params }: Params) {
|
|
56
|
-
const me = await getAuthUser();
|
|
57
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
58
|
-
if (me.role !== 'super_admin') {
|
|
59
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const { id } = await params;
|
|
63
|
-
await prisma.plan.delete({ where: { id } });
|
|
64
|
-
return NextResponse.json({ success: true });
|
|
65
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import { getAuthUser } from '@/lib/auth';
|
|
4
|
-
|
|
5
|
-
export async function GET(req: Request) {
|
|
6
|
-
try {
|
|
7
|
-
const me = await getAuthUser();
|
|
8
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
9
|
-
if (!['super_admin', 'admin'].includes(me.role)) {
|
|
10
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const plans = await prisma.plan.findMany({
|
|
14
|
-
where: { isActive: true },
|
|
15
|
-
orderBy: { tokensPerWindow: 'asc' },
|
|
16
|
-
select: {
|
|
17
|
-
id: true, name: true, tier: true,
|
|
18
|
-
tokensPerWindow: true, requestsPerWindow: true,
|
|
19
|
-
durationDays: true, displayMultiplier: true,
|
|
20
|
-
minDurationDays: true, maxDurationDays: true,
|
|
21
|
-
isActive: true, createdAt: true,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return NextResponse.json({ plans: plans.map(p => ({ ...p, tokensPerWindow: Number(p.tokensPerWindow) })) });
|
|
26
|
-
} catch (err) {
|
|
27
|
-
console.error('[/api/admin/plans GET]', err);
|
|
28
|
-
return NextResponse.json({ error: 'Internal error', detail: String(err) }, { status: 500 });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function POST(req: Request) {
|
|
33
|
-
try {
|
|
34
|
-
const me = await getAuthUser();
|
|
35
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
36
|
-
if (me.role !== 'super_admin') {
|
|
37
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const body = await req.json();
|
|
41
|
-
const { name, tier, tokensPerWindow, requestsPerWindow, durationDays, displayMultiplier, minDurationDays, maxDurationDays } = body;
|
|
42
|
-
|
|
43
|
-
if (!name || !tier) {
|
|
44
|
-
return NextResponse.json({ error: 'Name and tier are required' }, { status: 400 });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const plan = await prisma.plan.create({
|
|
48
|
-
data: {
|
|
49
|
-
name,
|
|
50
|
-
tier: tier ?? 'free',
|
|
51
|
-
tokensPerWindow: tokensPerWindow ?? 500_000,
|
|
52
|
-
requestsPerWindow: requestsPerWindow ?? 100,
|
|
53
|
-
durationDays: durationDays ?? 30,
|
|
54
|
-
displayMultiplier: displayMultiplier ?? 1.0,
|
|
55
|
-
minDurationDays: minDurationDays ?? 0,
|
|
56
|
-
maxDurationDays: maxDurationDays ?? -1,
|
|
57
|
-
createdByAdminId: me.id,
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return NextResponse.json({ ...plan, tokensPerWindow: Number(plan.tokensPerWindow) }, { status: 201 });
|
|
62
|
-
} catch (err) {
|
|
63
|
-
console.error('[/api/admin/plans POST]', err);
|
|
64
|
-
return NextResponse.json({ error: 'Internal error', detail: String(err) }, { status: 500 });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import { getAuthUser } from '@/lib/auth';
|
|
4
|
-
import { randomBytes } from 'crypto';
|
|
5
|
-
|
|
6
|
-
function slugify(text: string): string {
|
|
7
|
-
return text
|
|
8
|
-
.toLowerCase()
|
|
9
|
-
.replace(/[^\w\s-]/g, '')
|
|
10
|
-
.replace(/\s+/g, '-')
|
|
11
|
-
.replace(/-+/g, '-')
|
|
12
|
-
.trim();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type Params = { params: Promise<{ id: string }> };
|
|
16
|
-
|
|
17
|
-
export async function GET(req: Request, { params }: Params) {
|
|
18
|
-
const me = await getAuthUser();
|
|
19
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
20
|
-
if (!['super_admin', 'admin'].includes(me.role)) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
21
|
-
const { id } = await params;
|
|
22
|
-
|
|
23
|
-
const post = await prisma.post.findUnique({
|
|
24
|
-
where: { id },
|
|
25
|
-
include: { author: { select: { name: true } } },
|
|
26
|
-
});
|
|
27
|
-
if (!post) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
28
|
-
|
|
29
|
-
return NextResponse.json(post);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function PUT(req: Request, { params }: Params) {
|
|
33
|
-
const me = await getAuthUser();
|
|
34
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
35
|
-
if (!['super_admin', 'admin'].includes(me.role)) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
36
|
-
const { id } = await params;
|
|
37
|
-
|
|
38
|
-
const post = await prisma.post.findUnique({ where: { id } });
|
|
39
|
-
if (!post) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
40
|
-
|
|
41
|
-
const body = await req.json();
|
|
42
|
-
const { title, content, excerpt, coverImage, tags, seoTitle, seoDesc, published } = body;
|
|
43
|
-
|
|
44
|
-
let slug = post.slug;
|
|
45
|
-
if (title && slugify(title) !== post.slug) {
|
|
46
|
-
const newSlug = slugify(title);
|
|
47
|
-
const existing = await prisma.post.findUnique({ where: { slug: newSlug } });
|
|
48
|
-
slug = existing ? `${newSlug}-${randomBytes(4).toString('hex').slice(0, 6)}` : newSlug;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const wasPublished = post.published;
|
|
52
|
-
const nowPublishing = published && !wasPublished;
|
|
53
|
-
|
|
54
|
-
const updated = await prisma.post.update({
|
|
55
|
-
where: { id },
|
|
56
|
-
data: {
|
|
57
|
-
title: title ?? post.title,
|
|
58
|
-
slug,
|
|
59
|
-
content: content ?? post.content,
|
|
60
|
-
excerpt: excerpt !== undefined ? excerpt : post.excerpt,
|
|
61
|
-
coverImage: coverImage !== undefined ? coverImage : post.coverImage,
|
|
62
|
-
tags: tags !== undefined ? tags : post.tags,
|
|
63
|
-
seoTitle: seoTitle !== undefined ? seoTitle : post.seoTitle,
|
|
64
|
-
seoDesc: seoDesc !== undefined ? seoDesc : post.seoDesc,
|
|
65
|
-
published: published !== undefined ? published : post.published,
|
|
66
|
-
publishedAt: nowPublishing ? new Date() : post.publishedAt,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return NextResponse.json(updated);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function DELETE(req: Request, { params }: Params) {
|
|
74
|
-
const me = await getAuthUser();
|
|
75
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
76
|
-
if (!['super_admin', 'admin'].includes(me.role)) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
77
|
-
const { id } = await params;
|
|
78
|
-
|
|
79
|
-
await prisma.post.delete({ where: { id } });
|
|
80
|
-
return NextResponse.json({ success: true });
|
|
81
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import { getAuthUser } from '@/lib/auth';
|
|
4
|
-
import { randomBytes } from 'crypto';
|
|
5
|
-
|
|
6
|
-
function slugify(text: string): string {
|
|
7
|
-
return text
|
|
8
|
-
.toLowerCase()
|
|
9
|
-
.replace(/[^\w\s-]/g, '')
|
|
10
|
-
.replace(/\s+/g, '-')
|
|
11
|
-
.replace(/-+/g, '-')
|
|
12
|
-
.trim();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function GET(req: Request) {
|
|
16
|
-
const me = await getAuthUser();
|
|
17
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
18
|
-
if (!['super_admin', 'admin'].includes(me.role)) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
19
|
-
|
|
20
|
-
const { searchParams } = new URL(req.url);
|
|
21
|
-
const page = parseInt(searchParams.get('page') ?? '1');
|
|
22
|
-
const limit = parseInt(searchParams.get('limit') ?? '10');
|
|
23
|
-
const status = searchParams.get('status'); // published | draft
|
|
24
|
-
const skip = (page - 1) * limit;
|
|
25
|
-
|
|
26
|
-
const where: any = {};
|
|
27
|
-
if (status === 'published') where.published = true;
|
|
28
|
-
if (status === 'draft') where.published = false;
|
|
29
|
-
|
|
30
|
-
const [posts, total] = await Promise.all([
|
|
31
|
-
prisma.post.findMany({
|
|
32
|
-
where,
|
|
33
|
-
orderBy: { createdAt: 'desc' },
|
|
34
|
-
skip,
|
|
35
|
-
take: limit,
|
|
36
|
-
select: {
|
|
37
|
-
id: true, title: true, slug: true, excerpt: true, coverImage: true,
|
|
38
|
-
published: true, publishedAt: true, tags: true, views: true, seoTitle: true, seoDesc: true,
|
|
39
|
-
createdAt: true, updatedAt: true,
|
|
40
|
-
author: { select: { name: true } },
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
prisma.post.count({ where }),
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
return NextResponse.json({ posts, total, pages: Math.ceil(total / limit), page });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function POST(req: Request) {
|
|
50
|
-
const me = await getAuthUser();
|
|
51
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
52
|
-
if (!['super_admin', 'admin'].includes(me.role)) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
53
|
-
|
|
54
|
-
const body = await req.json();
|
|
55
|
-
const { title, content, excerpt, coverImage, tags, seoTitle, seoDesc, published } = body;
|
|
56
|
-
|
|
57
|
-
if (!title || !content) {
|
|
58
|
-
return NextResponse.json({ error: 'Title and content are required' }, { status: 400 });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let slug = slugify(title);
|
|
62
|
-
// Ensure unique slug
|
|
63
|
-
const existing = await prisma.post.findUnique({ where: { slug } });
|
|
64
|
-
if (existing) slug = `${slug}-${randomBytes(4).toString('hex').slice(0, 6)}`;
|
|
65
|
-
|
|
66
|
-
const post = await prisma.post.create({
|
|
67
|
-
data: {
|
|
68
|
-
title,
|
|
69
|
-
slug,
|
|
70
|
-
content,
|
|
71
|
-
excerpt: excerpt ?? null,
|
|
72
|
-
coverImage: coverImage ?? null,
|
|
73
|
-
tags: tags ?? '',
|
|
74
|
-
seoTitle: seoTitle ?? null,
|
|
75
|
-
seoDesc: seoDesc ?? null,
|
|
76
|
-
published: published ?? false,
|
|
77
|
-
publishedAt: published ? new Date() : null,
|
|
78
|
-
authorId: me.id,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return NextResponse.json(post, { status: 201 });
|
|
83
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { Resend } from 'resend';
|
|
3
|
-
import { prisma } from '@/lib/prisma';
|
|
4
|
-
import bcrypt from 'bcryptjs';
|
|
5
|
-
import { randomBytes } from 'crypto';
|
|
6
|
-
|
|
7
|
-
function getResendClient() {
|
|
8
|
-
if (!process.env.RESEND_API_KEY) return null;
|
|
9
|
-
return new Resend(process.env.RESEND_API_KEY);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const DEFAULT_SUPERADMIN_PASSWORD = '@Monu123';
|
|
13
|
-
|
|
14
|
-
const DEFAULT_PLANS = [
|
|
15
|
-
{
|
|
16
|
-
name: '5x Max Trial',
|
|
17
|
-
tier: '5x',
|
|
18
|
-
tokensPerWindow: 5000000,
|
|
19
|
-
requestsPerWindow: 18000,
|
|
20
|
-
durationDays: 7,
|
|
21
|
-
displayMultiplier: 3.0,
|
|
22
|
-
minDurationDays: 7,
|
|
23
|
-
maxDurationDays: 90,
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
name: '5x Max',
|
|
27
|
-
tier: '5x',
|
|
28
|
-
tokensPerWindow: 5000000,
|
|
29
|
-
requestsPerWindow: 18000,
|
|
30
|
-
durationDays: 30,
|
|
31
|
-
displayMultiplier: 3.0,
|
|
32
|
-
minDurationDays: 7,
|
|
33
|
-
maxDurationDays: 90,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: '20x Max Trial',
|
|
37
|
-
tier: '20x',
|
|
38
|
-
tokensPerWindow: 20000000,
|
|
39
|
-
requestsPerWindow: 18000,
|
|
40
|
-
durationDays: 7,
|
|
41
|
-
displayMultiplier: 3.0,
|
|
42
|
-
minDurationDays: 7,
|
|
43
|
-
maxDurationDays: 90,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: '20x Max',
|
|
47
|
-
tier: '20x',
|
|
48
|
-
tokensPerWindow: 20000000,
|
|
49
|
-
requestsPerWindow: 18000,
|
|
50
|
-
durationDays: 30,
|
|
51
|
-
displayMultiplier: 3.0,
|
|
52
|
-
minDurationDays: 7,
|
|
53
|
-
maxDurationDays: 90,
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
export async function POST(req: Request) {
|
|
58
|
-
try {
|
|
59
|
-
const { secret } = await req.json();
|
|
60
|
-
|
|
61
|
-
if (secret !== (process.env.SUPER_ADMIN_PASSWORD || DEFAULT_SUPERADMIN_PASSWORD)) {
|
|
62
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let plansCreated = 0;
|
|
66
|
-
await prisma.plan.deleteMany({});
|
|
67
|
-
for (const plan of DEFAULT_PLANS) {
|
|
68
|
-
await prisma.plan.create({ data: plan });
|
|
69
|
-
plansCreated++;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Create or update superadmin — always use DEFAULT_SUPERADMIN_PASSWORD
|
|
73
|
-
const allPlans = await prisma.plan.findMany();
|
|
74
|
-
const existing = await prisma.admin.findUnique({ where: { username: 'superadmin' } });
|
|
75
|
-
const passwordHash = await bcrypt.hash(DEFAULT_SUPERADMIN_PASSWORD, 12);
|
|
76
|
-
|
|
77
|
-
if (existing) {
|
|
78
|
-
// Update password to the new credential
|
|
79
|
-
const updateData: any = {
|
|
80
|
-
password: passwordHash,
|
|
81
|
-
allowedPlanIds: JSON.stringify(allPlans.map(p => p.id)),
|
|
82
|
-
defaultPlanId: allPlans[0]?.id ?? null,
|
|
83
|
-
supportEnabled: true,
|
|
84
|
-
};
|
|
85
|
-
await prisma.admin.update({ where: { username: 'superadmin' }, data: updateData });
|
|
86
|
-
} else {
|
|
87
|
-
const createData: any = {
|
|
88
|
-
username: 'superadmin',
|
|
89
|
-
password: passwordHash,
|
|
90
|
-
name: 'Super Admin',
|
|
91
|
-
role: 'super_admin',
|
|
92
|
-
isActive: true,
|
|
93
|
-
canCreateKey: true,
|
|
94
|
-
canDeleteKey: true,
|
|
95
|
-
canBlockKey: true,
|
|
96
|
-
canManageTokens: true,
|
|
97
|
-
canCreateReseller: true,
|
|
98
|
-
canManageResellers: true,
|
|
99
|
-
canManageUsers: true,
|
|
100
|
-
allowedPlanIds: JSON.stringify(allPlans.map(p => p.id)),
|
|
101
|
-
defaultPlanId: allPlans[0]?.id ?? null,
|
|
102
|
-
supportEnabled: true,
|
|
103
|
-
};
|
|
104
|
-
await prisma.admin.create({ data: createData });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Create a default API key if none exist
|
|
108
|
-
const keyCount = await prisma.apiKey.count();
|
|
109
|
-
if (keyCount === 0) {
|
|
110
|
-
const rawKey = randomBytes(32).toString('hex');
|
|
111
|
-
const key = `sk-cmx_${rawKey}`;
|
|
112
|
-
const defaultPlan = await prisma.plan.findFirst({ where: { name: '20x Max' } });
|
|
113
|
-
const expiresAt = defaultPlan && defaultPlan.durationDays > 0
|
|
114
|
-
? new Date(Date.now() + defaultPlan.durationDays * 24 * 60 * 60 * 1000)
|
|
115
|
-
: null;
|
|
116
|
-
await prisma.apiKey.create({
|
|
117
|
-
data: {
|
|
118
|
-
key,
|
|
119
|
-
name: 'ClaudMax Default Key',
|
|
120
|
-
prefix: `sk-cmx_${rawKey.slice(0, 6)}`,
|
|
121
|
-
tier: '20x',
|
|
122
|
-
isActive: true,
|
|
123
|
-
windowStartAt: new Date(),
|
|
124
|
-
windowTokensUsed: 0,
|
|
125
|
-
windowRequestsUsed: 0,
|
|
126
|
-
totalTokensUsed: 0,
|
|
127
|
-
displayMultiplier: 3.0,
|
|
128
|
-
planId: defaultPlan?.id,
|
|
129
|
-
expiresAt,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return NextResponse.json({
|
|
135
|
-
success: true,
|
|
136
|
-
message: 'Database seeded successfully',
|
|
137
|
-
superadmin: existing ? 'password updated' : 'created',
|
|
138
|
-
plansCreated,
|
|
139
|
-
apiKey: keyCount === 0 ? 'generated and created' : 'already exists',
|
|
140
|
-
});
|
|
141
|
-
} catch (err) {
|
|
142
|
-
console.error('Seed error:', err);
|
|
143
|
-
return NextResponse.json({ error: 'Seed failed', detail: String(err) }, { status: 500 });
|
|
144
|
-
}
|
|
145
|
-
}
|