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,44 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { getAuthUser } from '@/lib/auth';
|
|
3
|
-
import { prisma } from '@/lib/prisma';
|
|
4
|
-
|
|
5
|
-
// GET - read settings (public, no auth needed)
|
|
6
|
-
export async function GET() {
|
|
7
|
-
try {
|
|
8
|
-
const superadmin = await prisma.admin.findFirst({
|
|
9
|
-
where: { role: 'super_admin' },
|
|
10
|
-
select: { supportEnabled: true } as any,
|
|
11
|
-
});
|
|
12
|
-
return NextResponse.json({
|
|
13
|
-
supportEnabled: superadmin?.supportEnabled ?? true,
|
|
14
|
-
});
|
|
15
|
-
} catch {
|
|
16
|
-
// Default to support enabled if DB fails
|
|
17
|
-
return NextResponse.json({ supportEnabled: true });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// PATCH - update settings (super_admin only)
|
|
22
|
-
export async function PATCH(req: Request) {
|
|
23
|
-
const me = await getAuthUser();
|
|
24
|
-
if (!me || me.role !== 'super_admin') {
|
|
25
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const { supportEnabled } = await req.json();
|
|
30
|
-
if (typeof supportEnabled !== 'boolean') {
|
|
31
|
-
return NextResponse.json({ error: 'supportEnabled must be a boolean' }, { status: 400 });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await prisma.admin.updateMany({
|
|
35
|
-
where: { role: 'super_admin' },
|
|
36
|
-
data: { supportEnabled } as any,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
return NextResponse.json({ success: true, supportEnabled });
|
|
40
|
-
} catch (err) {
|
|
41
|
-
console.error('[ClaudMax] Settings update error:', err);
|
|
42
|
-
return NextResponse.json({ error: 'Failed to update settings' }, { status: 500 });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,74 +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() {
|
|
6
|
-
const me = await getAuthUser();
|
|
7
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
8
|
-
|
|
9
|
-
const keyWhere: any = {};
|
|
10
|
-
if (me.role === 'reseller') {
|
|
11
|
-
keyWhere.resellerId = me.id;
|
|
12
|
-
} else if (me.role === 'admin') {
|
|
13
|
-
keyWhere.resellerId = { not: null };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const userWhere: any = {};
|
|
17
|
-
if (me.role === 'admin') {
|
|
18
|
-
userWhere.role = 'reseller';
|
|
19
|
-
} else if (me.role === 'reseller') {
|
|
20
|
-
userWhere.id = me.id;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const [totalKeys, activeKeys, keysByTier, recentKeys, topTokens, totalRequests, totalTokens, totalUsers] = await Promise.all([
|
|
24
|
-
prisma.apiKey.count({ where: keyWhere }),
|
|
25
|
-
prisma.apiKey.count({ where: { ...keyWhere, isActive: true } }),
|
|
26
|
-
prisma.apiKey.groupBy({
|
|
27
|
-
by: ['tier'],
|
|
28
|
-
where: keyWhere,
|
|
29
|
-
_count: { _all: true },
|
|
30
|
-
}),
|
|
31
|
-
prisma.apiKey.findMany({
|
|
32
|
-
where: keyWhere,
|
|
33
|
-
orderBy: { createdAt: 'desc' },
|
|
34
|
-
take: 10,
|
|
35
|
-
select: {
|
|
36
|
-
id: true, name: true, prefix: true, tier: true, isActive: true,
|
|
37
|
-
windowRequestsUsed: true, windowTokensUsed: true, displayMultiplier: true,
|
|
38
|
-
createdAt: true, lastUsedAt: true,
|
|
39
|
-
},
|
|
40
|
-
}),
|
|
41
|
-
prisma.apiKey.findMany({
|
|
42
|
-
where: keyWhere,
|
|
43
|
-
orderBy: { totalTokensUsed: 'desc' },
|
|
44
|
-
take: 5,
|
|
45
|
-
select: {
|
|
46
|
-
id: true, name: true, prefix: true, tier: true,
|
|
47
|
-
totalTokensUsed: true, displayMultiplier: true,
|
|
48
|
-
},
|
|
49
|
-
}),
|
|
50
|
-
prisma.apiKey.aggregate({ where: keyWhere, _sum: { windowRequestsUsed: true } }),
|
|
51
|
-
prisma.apiKey.aggregate({ where: keyWhere, _sum: { totalTokensUsed: true } }),
|
|
52
|
-
prisma.admin.count({ where: userWhere }),
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
return NextResponse.json({
|
|
56
|
-
totalKeys,
|
|
57
|
-
activeKeys,
|
|
58
|
-
totalRequests: totalRequests._sum.windowRequestsUsed ?? 0,
|
|
59
|
-
totalTokens: totalTokens._sum.totalTokensUsed ?? 0,
|
|
60
|
-
totalUsers,
|
|
61
|
-
keysByTier,
|
|
62
|
-
recentKeys: recentKeys.map(k => ({
|
|
63
|
-
...k,
|
|
64
|
-
displayMultiplier: k.displayMultiplier ?? 3.0,
|
|
65
|
-
})),
|
|
66
|
-
topTokens: topTokens.map(k => ({
|
|
67
|
-
...k,
|
|
68
|
-
totalTokensUsed: k.totalTokensUsed,
|
|
69
|
-
displayMultiplier: k.displayMultiplier ?? 3.0,
|
|
70
|
-
displayedTokens: Math.floor(Number(k.totalTokensUsed) * (k.displayMultiplier ?? 3.0)),
|
|
71
|
-
})),
|
|
72
|
-
myRole: me.role,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import bcrypt from 'bcryptjs';
|
|
4
|
-
import { getAuthUser, requireRole } from '@/lib/auth';
|
|
5
|
-
|
|
6
|
-
const PERMISSION_FIELDS = [
|
|
7
|
-
'canCreateKey', 'canDeleteKey', 'canBlockKey',
|
|
8
|
-
'canManageTokens', 'canCreateReseller', 'canManageResellers', 'canManageUsers',
|
|
9
|
-
] as const;
|
|
10
|
-
|
|
11
|
-
type Params = { params: Promise<{ id: string }> };
|
|
12
|
-
|
|
13
|
-
export async function GET(req: Request, { params }: Params) {
|
|
14
|
-
const me = await getAuthUser();
|
|
15
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
16
|
-
const { id } = await params;
|
|
17
|
-
|
|
18
|
-
if (me.role === 'reseller' && me.id !== id) {
|
|
19
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const admin = await prisma.admin.findUnique({
|
|
23
|
-
where: { id },
|
|
24
|
-
select: {
|
|
25
|
-
id: true, username: true, name: true, role: true, isActive: true,
|
|
26
|
-
createdAt: true, updatedAt: true, blockedUntil: true, blockReason: true,
|
|
27
|
-
canCreateKey: true, canDeleteKey: true, canBlockKey: true,
|
|
28
|
-
canManageTokens: true, canCreateReseller: true, canManageResellers: true, canManageUsers: true,
|
|
29
|
-
_count: { select: { apiKeys: true } },
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
if (!admin) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
33
|
-
|
|
34
|
-
if (me.role === 'admin' && admin.role === 'super_admin') {
|
|
35
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return NextResponse.json({
|
|
39
|
-
...admin,
|
|
40
|
-
apiKeyCount: admin._count.apiKeys,
|
|
41
|
-
blockedUntil: admin.blockedUntil?.toISOString() ?? null,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function PATCH(req: Request, { params }: Params) {
|
|
46
|
-
const me = await getAuthUser();
|
|
47
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
48
|
-
const { id } = await params;
|
|
49
|
-
const body = await req.json();
|
|
50
|
-
const { action, name, role, password, isActive } = body;
|
|
51
|
-
|
|
52
|
-
const target = await prisma.admin.findUnique({ where: { id } });
|
|
53
|
-
if (!target) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
54
|
-
|
|
55
|
-
if (target.role === 'super_admin' && !requireRole(me, 'super_admin')) {
|
|
56
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (me.role === 'admin' && (target.role === 'admin' || target.role === 'super_admin') && me.id !== id) {
|
|
60
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (action === 'toggle') {
|
|
64
|
-
if (target.role === 'super_admin' && !requireRole(me, 'super_admin')) {
|
|
65
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
66
|
-
}
|
|
67
|
-
const updated = await prisma.admin.update({ where: { id }, data: { isActive: !target.isActive, blockedUntil: null } });
|
|
68
|
-
return NextResponse.json({ id: updated.id, isActive: updated.isActive });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (action === 'blockTemp') {
|
|
72
|
-
const { duration } = body; // duration in milliseconds, e.g. 3600000 for 1h
|
|
73
|
-
if (!duration || typeof duration !== 'number' || duration <= 0) {
|
|
74
|
-
return NextResponse.json({ error: 'Invalid duration' }, { status: 400 });
|
|
75
|
-
}
|
|
76
|
-
if (me.role === 'admin' && target.role === 'super_admin') {
|
|
77
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
78
|
-
}
|
|
79
|
-
const blockedUntil = new Date(Date.now() + duration);
|
|
80
|
-
const updated = await prisma.admin.update({
|
|
81
|
-
where: { id },
|
|
82
|
-
data: { blockedUntil, isActive: false },
|
|
83
|
-
});
|
|
84
|
-
return NextResponse.json({ id: updated.id, blockedUntil: updated.blockedUntil?.toISOString() ?? null, isActive: updated.isActive });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (action === 'unblockTemp') {
|
|
88
|
-
const updated = await prisma.admin.update({
|
|
89
|
-
where: { id },
|
|
90
|
-
data: { blockedUntil: null, isActive: true },
|
|
91
|
-
});
|
|
92
|
-
return NextResponse.json({ id: updated.id, blockedUntil: null, isActive: updated.isActive });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (action === 'updatePermissions') {
|
|
96
|
-
// Only admins and super_admins can update permissions
|
|
97
|
-
if (!requireRole(me, 'super_admin', 'admin')) {
|
|
98
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
99
|
-
}
|
|
100
|
-
if (me.role === 'admin' && target.role !== 'reseller') {
|
|
101
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
102
|
-
}
|
|
103
|
-
const { permissions } = body;
|
|
104
|
-
if (!permissions || typeof permissions !== 'object') {
|
|
105
|
-
return NextResponse.json({ error: 'permissions object required' }, { status: 400 });
|
|
106
|
-
}
|
|
107
|
-
const data: any = {};
|
|
108
|
-
for (const field of PERMISSION_FIELDS) {
|
|
109
|
-
if (field in permissions) {
|
|
110
|
-
data[field] = Boolean(permissions[field]);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const updated = await prisma.admin.update({ where: { id }, data });
|
|
114
|
-
return NextResponse.json({
|
|
115
|
-
id: updated.id,
|
|
116
|
-
canCreateKey: updated.canCreateKey,
|
|
117
|
-
canDeleteKey: updated.canDeleteKey,
|
|
118
|
-
canBlockKey: updated.canBlockKey,
|
|
119
|
-
canManageTokens: updated.canManageTokens,
|
|
120
|
-
canCreateReseller: updated.canCreateReseller,
|
|
121
|
-
canManageResellers: updated.canManageResellers,
|
|
122
|
-
canManageUsers: updated.canManageUsers,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (action === 'update') {
|
|
127
|
-
const data: any = {};
|
|
128
|
-
if (name !== undefined) data.name = name;
|
|
129
|
-
if (role !== undefined) {
|
|
130
|
-
if (role === 'super_admin' && !requireRole(me, 'super_admin')) {
|
|
131
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
132
|
-
}
|
|
133
|
-
data.role = role;
|
|
134
|
-
}
|
|
135
|
-
if (isActive !== undefined) data.isActive = isActive;
|
|
136
|
-
if (password) data.password = await bcrypt.hash(password, 12);
|
|
137
|
-
const updated = await prisma.admin.update({ where: { id }, data });
|
|
138
|
-
return NextResponse.json({ id: updated.id, username: updated.username, name: updated.name, role: updated.role, isActive: updated.isActive });
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export async function DELETE(req: Request, { params }: Params) {
|
|
145
|
-
const me = await getAuthUser();
|
|
146
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
147
|
-
const { id } = await params;
|
|
148
|
-
|
|
149
|
-
if (me.id === id) {
|
|
150
|
-
return NextResponse.json({ error: 'Cannot delete your own account' }, { status: 400 });
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const target = await prisma.admin.findUnique({ where: { id } });
|
|
154
|
-
if (!target) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
155
|
-
|
|
156
|
-
if (target.role === 'super_admin' && !requireRole(me, 'super_admin')) {
|
|
157
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (me.role === 'admin' && (target.role === 'admin' || target.role === 'super_admin')) {
|
|
161
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
await prisma.admin.delete({ where: { id } });
|
|
165
|
-
return NextResponse.json({ success: true });
|
|
166
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
import { getAuthUser } from '@/lib/auth';
|
|
4
|
-
|
|
5
|
-
// Assign or update allowed plans for a user
|
|
6
|
-
export async function PUT(req: Request) {
|
|
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 body = await req.json();
|
|
14
|
-
const { adminId, allowedPlanIds, defaultPlanId } = body;
|
|
15
|
-
|
|
16
|
-
if (!adminId) {
|
|
17
|
-
return NextResponse.json({ error: 'adminId is required' }, { status: 400 });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Only super_admin can manage admins and super_admin
|
|
21
|
-
const target = await prisma.admin.findUnique({ where: { id: adminId } });
|
|
22
|
-
if (!target) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
23
|
-
|
|
24
|
-
if (target.role === 'super_admin' && me.role !== 'super_admin') {
|
|
25
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
26
|
-
}
|
|
27
|
-
if (target.role === 'admin' && me.role !== 'super_admin') {
|
|
28
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Admins can only manage resellers
|
|
32
|
-
if (me.role === 'admin' && target.role !== 'reseller') {
|
|
33
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const updated = await prisma.admin.update({
|
|
37
|
-
where: { id: adminId },
|
|
38
|
-
data: {
|
|
39
|
-
allowedPlanIds: JSON.stringify(allowedPlanIds ?? []),
|
|
40
|
-
defaultPlanId: defaultPlanId ?? null,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return NextResponse.json({ success: true, allowedPlanIds, defaultPlanId });
|
|
45
|
-
}
|
|
@@ -1,202 +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 { getAuthUser } from '@/lib/auth';
|
|
6
|
-
|
|
7
|
-
const FROM_EMAIL = 'ClaudMax <onboarding@resend.dev>';
|
|
8
|
-
const TO_EMAIL = 'codewpremium@gmail.com';
|
|
9
|
-
|
|
10
|
-
function getResendClient() {
|
|
11
|
-
if (!process.env.RESEND_API_KEY) return null;
|
|
12
|
-
return new Resend(process.env.RESEND_API_KEY);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function sendAccountEmail(username: string, password: string, role: string, createdBy: string) {
|
|
16
|
-
const client = getResendClient();
|
|
17
|
-
if (!client) return;
|
|
18
|
-
|
|
19
|
-
const roleLabel = role === 'super_admin' ? 'Super Admin' : role === 'admin' ? 'Admin' : 'Reseller';
|
|
20
|
-
const html = `
|
|
21
|
-
<!DOCTYPE html>
|
|
22
|
-
<html>
|
|
23
|
-
<head><meta charset="utf-8"></head>
|
|
24
|
-
<body style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:600px;margin:0 auto;padding:20px;background:#f9fafb;">
|
|
25
|
-
<div style="background:white;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,0.1);">
|
|
26
|
-
<div style="background:linear-gradient(135deg,#5244F3,#7c3aed);padding:24px;text-align:center;">
|
|
27
|
-
<h1 style="color:white;margin:0;font-size:24px;">New ${roleLabel} Account Created</h1>
|
|
28
|
-
</div>
|
|
29
|
-
<div style="padding:24px;">
|
|
30
|
-
<p style="margin:0 0 16px;font-size:15px;color:#374151;">
|
|
31
|
-
A new <strong>${roleLabel}</strong> account has been created on ClaudMax.
|
|
32
|
-
</p>
|
|
33
|
-
<table style="width:100%;border-collapse:collapse;margin-bottom:20px;">
|
|
34
|
-
<tr>
|
|
35
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;background:#f9fafb;font-size:13px;color:#6b7280;width:120px;">Role</td>
|
|
36
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;font-size:14px;font-weight:600;color:#111827;">${roleLabel}</td>
|
|
37
|
-
</tr>
|
|
38
|
-
<tr>
|
|
39
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;background:#f9fafb;font-size:13px;color:#6b7280;">Username</td>
|
|
40
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;font-size:14px;font-family:monospace;color:#111827;">${username}</td>
|
|
41
|
-
</tr>
|
|
42
|
-
<tr>
|
|
43
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;background:#f9fafb;font-size:13px;color:#6b7280;">Password</td>
|
|
44
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;font-size:14px;font-family:monospace;color:#111827;">${password}</td>
|
|
45
|
-
</tr>
|
|
46
|
-
<tr>
|
|
47
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;background:#f9fafb;font-size:13px;color:#6b7280;">Created By</td>
|
|
48
|
-
<td style="padding:10px 12px;border:1px solid #e5e7eb;font-size:14px;color:#111827;">${createdBy}</td>
|
|
49
|
-
</tr>
|
|
50
|
-
</table>
|
|
51
|
-
<p style="margin:0 0 8px;font-size:13px;color:#6b7280;">
|
|
52
|
-
Please share these credentials securely with the account owner.
|
|
53
|
-
</p>
|
|
54
|
-
<p style="margin:0;font-size:13px;color:#6b7280;">
|
|
55
|
-
Login at: <a href="https://claudmax.pro/admin" style="color:#5244F3;">claudmax.pro/admin</a>
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
</body>
|
|
60
|
-
</html>
|
|
61
|
-
`.trim();
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
await client.emails.send({
|
|
65
|
-
from: FROM_EMAIL,
|
|
66
|
-
to: TO_EMAIL,
|
|
67
|
-
subject: `[ClaudMax] New ${roleLabel} Account: ${username}`,
|
|
68
|
-
html,
|
|
69
|
-
});
|
|
70
|
-
} catch (err) {
|
|
71
|
-
console.error('[ClaudMax] Failed to send account creation email:', err);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const PERMISSION_FIELDS = [
|
|
76
|
-
'canCreateKey', 'canDeleteKey', 'canBlockKey',
|
|
77
|
-
'canManageTokens', 'canCreateReseller', 'canManageResellers', 'canManageUsers',
|
|
78
|
-
] as const;
|
|
79
|
-
|
|
80
|
-
function hasPermission(user: { role: string }, permission: string): boolean {
|
|
81
|
-
if (user.role === 'super_admin') return true;
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function GET(req: Request) {
|
|
86
|
-
const me = await getAuthUser();
|
|
87
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
88
|
-
|
|
89
|
-
const { searchParams } = new URL(req.url);
|
|
90
|
-
const search = searchParams.get('search') ?? '';
|
|
91
|
-
const role = searchParams.get('role') ?? '';
|
|
92
|
-
const status = searchParams.get('status') ?? '';
|
|
93
|
-
|
|
94
|
-
// Resellers can only see themselves
|
|
95
|
-
if (me.role === 'reseller') {
|
|
96
|
-
const admin = await prisma.admin.findUnique({
|
|
97
|
-
where: { id: me.id },
|
|
98
|
-
select: {
|
|
99
|
-
id: true, username: true, name: true, role: true, isActive: true, createdAt: true,
|
|
100
|
-
blockedUntil: true,
|
|
101
|
-
canCreateKey: true, canDeleteKey: true, canBlockKey: true,
|
|
102
|
-
canManageTokens: true, canCreateReseller: true, canManageResellers: true, canManageUsers: true,
|
|
103
|
-
_count: { select: { apiKeys: true } },
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
return NextResponse.json({
|
|
107
|
-
users: admin ? [{ ...admin, apiKeyCount: admin._count.apiKeys, blockedUntil: admin.blockedUntil?.toISOString() ?? null }] : [],
|
|
108
|
-
total: 1, pages: 1, page: 1
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (me.role === 'admin') {
|
|
113
|
-
if (role === 'super_admin' || role === 'admin') {
|
|
114
|
-
return NextResponse.json({ users: [], total: 0, pages: 0, page: 1 });
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const where: any = {};
|
|
119
|
-
if (search) {
|
|
120
|
-
where.OR = [
|
|
121
|
-
{ username: { contains: search } },
|
|
122
|
-
{ name: { contains: search } },
|
|
123
|
-
];
|
|
124
|
-
}
|
|
125
|
-
if (role) where.role = role;
|
|
126
|
-
if (status === 'active') where.isActive = true;
|
|
127
|
-
if (status === 'inactive') where.isActive = false;
|
|
128
|
-
if (me.role === 'admin') {
|
|
129
|
-
where.role = { not: 'super_admin' };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const [users, total] = await Promise.all([
|
|
133
|
-
prisma.admin.findMany({
|
|
134
|
-
where,
|
|
135
|
-
orderBy: { createdAt: 'desc' },
|
|
136
|
-
select: {
|
|
137
|
-
id: true, username: true, name: true, role: true, isActive: true, createdAt: true,
|
|
138
|
-
blockedUntil: true,
|
|
139
|
-
canCreateKey: true, canDeleteKey: true, canBlockKey: true,
|
|
140
|
-
canManageTokens: true, canCreateReseller: true, canManageResellers: true, canManageUsers: true,
|
|
141
|
-
_count: { select: { apiKeys: true } },
|
|
142
|
-
},
|
|
143
|
-
}),
|
|
144
|
-
prisma.admin.count({ where }),
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
return NextResponse.json({
|
|
148
|
-
users: users.map(u => ({ ...u, apiKeyCount: u._count.apiKeys, blockedUntil: u.blockedUntil?.toISOString() ?? null })),
|
|
149
|
-
total,
|
|
150
|
-
pages: 1,
|
|
151
|
-
page: 1,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export async function POST(req: Request) {
|
|
156
|
-
const me = await getAuthUser();
|
|
157
|
-
if (!me) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
158
|
-
|
|
159
|
-
const { username, password, name, role, permissions } = await req.json();
|
|
160
|
-
|
|
161
|
-
if (!username || !password || !name) {
|
|
162
|
-
return NextResponse.json({ error: 'Username, password, and name are required' }, { status: 400 });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Only super_admin can create admins
|
|
166
|
-
if (role === 'admin' || role === 'super_admin') {
|
|
167
|
-
if (me.role !== 'super_admin') {
|
|
168
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const hashed = await bcrypt.hash(password, 12);
|
|
173
|
-
const effectiveRole = (role && me.role === 'super_admin') ? role : 'reseller';
|
|
174
|
-
|
|
175
|
-
const admin = await prisma.admin.create({
|
|
176
|
-
data: {
|
|
177
|
-
username,
|
|
178
|
-
password: hashed,
|
|
179
|
-
name,
|
|
180
|
-
role: effectiveRole,
|
|
181
|
-
canCreateKey: permissions?.canCreateKey ?? true,
|
|
182
|
-
canDeleteKey: permissions?.canDeleteKey ?? true,
|
|
183
|
-
canBlockKey: permissions?.canBlockKey ?? true,
|
|
184
|
-
canManageTokens: permissions?.canManageTokens ?? true,
|
|
185
|
-
canCreateReseller: permissions?.canCreateReseller ?? false,
|
|
186
|
-
canManageResellers: permissions?.canManageResellers ?? false,
|
|
187
|
-
canManageUsers: permissions?.canManageUsers ?? false,
|
|
188
|
-
keysGenLimit: permissions?.keysGenLimit ?? 10,
|
|
189
|
-
keysGenResetAt: null,
|
|
190
|
-
keysGenToday: 0,
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Send email notification asynchronously
|
|
195
|
-
sendAccountEmail(username, password, effectiveRole, me.username);
|
|
196
|
-
|
|
197
|
-
return NextResponse.json({
|
|
198
|
-
id: admin.id, username: admin.username, name: admin.name, role: admin.role,
|
|
199
|
-
isActive: admin.isActive, createdAt: admin.createdAt,
|
|
200
|
-
keysGenLimit: admin.keysGenLimit, keysGenToday: admin.keysGenToday,
|
|
201
|
-
}, { status: 201 });
|
|
202
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
|
|
4
|
-
export async function GET(req: Request, { params }: { params: Promise<{ slug: string }> }) {
|
|
5
|
-
const { slug } = await params;
|
|
6
|
-
|
|
7
|
-
const post = await prisma.post.findUnique({
|
|
8
|
-
where: { slug, published: true },
|
|
9
|
-
include: {
|
|
10
|
-
author: { select: { name: true } },
|
|
11
|
-
},
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
if (!post) {
|
|
15
|
-
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Increment views
|
|
19
|
-
await prisma.post.update({ where: { id: post.id }, data: { views: { increment: 1 } } });
|
|
20
|
-
|
|
21
|
-
return NextResponse.json(post);
|
|
22
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
|
|
4
|
-
export async function GET(req: Request) {
|
|
5
|
-
const { searchParams } = new URL(req.url);
|
|
6
|
-
const page = parseInt(searchParams.get('page') ?? '1');
|
|
7
|
-
const limit = parseInt(searchParams.get('limit') ?? '9');
|
|
8
|
-
const tag = searchParams.get('tag') ?? '';
|
|
9
|
-
const skip = (page - 1) * limit;
|
|
10
|
-
|
|
11
|
-
const where: any = { published: true };
|
|
12
|
-
if (tag) {
|
|
13
|
-
where.tags = { contains: tag };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const [posts, total] = await Promise.all([
|
|
17
|
-
prisma.post.findMany({
|
|
18
|
-
where,
|
|
19
|
-
orderBy: { publishedAt: 'desc' },
|
|
20
|
-
skip,
|
|
21
|
-
take: limit,
|
|
22
|
-
select: {
|
|
23
|
-
id: true,
|
|
24
|
-
title: true,
|
|
25
|
-
slug: true,
|
|
26
|
-
excerpt: true,
|
|
27
|
-
coverImage: true,
|
|
28
|
-
tags: true,
|
|
29
|
-
publishedAt: true,
|
|
30
|
-
seoTitle: true,
|
|
31
|
-
seoDesc: true,
|
|
32
|
-
views: true,
|
|
33
|
-
author: { select: { name: true } },
|
|
34
|
-
},
|
|
35
|
-
}),
|
|
36
|
-
prisma.post.count({ where }),
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
return NextResponse.json({ posts, total, pages: Math.ceil(total / limit), page });
|
|
40
|
-
}
|