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.
Files changed (124) hide show
  1. package/claudmax-1.0.16.tgz +0 -0
  2. package/{packages/cli/index.js → index.js} +2 -0
  3. package/package.json +27 -55
  4. package/.claude/settings.local.json +0 -7
  5. package/.env.example +0 -24
  6. package/.github/workflows/publish.yml +0 -31
  7. package/README.md +0 -178
  8. package/claudmax-mcp-1.0.2.tgz +0 -0
  9. package/help +0 -0
  10. package/help-wal +0 -0
  11. package/next-env.d.ts +0 -6
  12. package/next.config.mjs +0 -43
  13. package/packages/cli/claudmax-1.0.16.tgz +0 -0
  14. package/packages/cli/package.json +0 -33
  15. package/packages/mcp/claudmax-mcp-1.0.0.tgz +0 -0
  16. package/packages/mcp/claudmax-mcp-1.0.1.tgz +0 -0
  17. package/packages/mcp/claudmax-mcp-1.0.2.tgz +0 -0
  18. package/packages/mcp/claudmax-mcp-1.0.3.tgz +0 -0
  19. package/packages/mcp/index.js +0 -129
  20. package/packages/mcp/package-lock.json +0 -1146
  21. package/packages/mcp/package.json +0 -32
  22. package/postcss.config.mjs +0 -6
  23. package/prisma/schema.prisma +0 -130
  24. package/prisma/seed.ts +0 -27
  25. package/public/favicon.svg +0 -10
  26. package/public/robots.txt +0 -10
  27. package/run_build.sh +0 -4
  28. package/scripts/migrate-plans.js +0 -98
  29. package/scripts/seed-blog.ts +0 -1014
  30. package/src/app/admin/dashboard/AdminDashboardClient.tsx +0 -1546
  31. package/src/app/admin/dashboard/page.tsx +0 -13
  32. package/src/app/admin/page.tsx +0 -132
  33. package/src/app/api/admin/auth/me/route.ts +0 -34
  34. package/src/app/api/admin/health/route.ts +0 -110
  35. package/src/app/api/admin/keys/[id]/route.ts +0 -116
  36. package/src/app/api/admin/keys/route.ts +0 -192
  37. package/src/app/api/admin/keys-list/route.ts +0 -81
  38. package/src/app/api/admin/login/route.ts +0 -72
  39. package/src/app/api/admin/logout/route.ts +0 -8
  40. package/src/app/api/admin/migrate/route.ts +0 -133
  41. package/src/app/api/admin/plans/[id]/route.ts +0 -65
  42. package/src/app/api/admin/plans/route.ts +0 -66
  43. package/src/app/api/admin/posts/[id]/route.ts +0 -81
  44. package/src/app/api/admin/posts/route.ts +0 -83
  45. package/src/app/api/admin/seed/route.ts +0 -145
  46. package/src/app/api/admin/settings/route.ts +0 -44
  47. package/src/app/api/admin/stats/route.ts +0 -74
  48. package/src/app/api/admin/users/[id]/route.ts +0 -166
  49. package/src/app/api/admin/users/plans/route.ts +0 -45
  50. package/src/app/api/admin/users/route.ts +0 -202
  51. package/src/app/api/blog/[slug]/route.ts +0 -22
  52. package/src/app/api/blog/route.ts +0 -40
  53. package/src/app/api/cron/daily-status/route.ts +0 -208
  54. package/src/app/api/support/chat/route.ts +0 -55
  55. package/src/app/api/support/chat/session/route.ts +0 -62
  56. package/src/app/api/support/chat/stream/route.ts +0 -44
  57. package/src/app/api/support/email/route.ts +0 -63
  58. package/src/app/api/tools/understand_image/route.ts +0 -113
  59. package/src/app/api/tools/upload/route.ts +0 -179
  60. package/src/app/api/tools/web_search/route.ts +0 -99
  61. package/src/app/api/v1/audio/route.ts +0 -67
  62. package/src/app/api/v1/audio/speech/route.ts +0 -73
  63. package/src/app/api/v1/chat/completions/route.ts +0 -3
  64. package/src/app/api/v1/chat/route.ts +0 -1079
  65. package/src/app/api/v1/images/generations/route.ts +0 -93
  66. package/src/app/api/v1/info/route.ts +0 -30
  67. package/src/app/api/v1/key-status/route.ts +0 -109
  68. package/src/app/api/v1/key-status/stream/route.ts +0 -135
  69. package/src/app/api/v1/messages/count_tokens/route.ts +0 -22
  70. package/src/app/api/v1/messages/route.ts +0 -807
  71. package/src/app/api/v1/models/route.ts +0 -14
  72. package/src/app/api/v1/route.ts +0 -18
  73. package/src/app/blog/BlogClient.tsx +0 -193
  74. package/src/app/blog/[slug]/page.tsx +0 -117
  75. package/src/app/blog/page.tsx +0 -20
  76. package/src/app/check-usage/CheckUsageClient.tsx +0 -186
  77. package/src/app/check-usage/layout.tsx +0 -11
  78. package/src/app/check-usage/page.tsx +0 -15
  79. package/src/app/docs/layout.tsx +0 -16
  80. package/src/app/docs/page.tsx +0 -1055
  81. package/src/app/faq/FAQClient.tsx +0 -227
  82. package/src/app/faq/page.tsx +0 -21
  83. package/src/app/globals.css +0 -75
  84. package/src/app/layout.tsx +0 -80
  85. package/src/app/page.tsx +0 -256
  86. package/src/app/reseller/ResellerClient.tsx +0 -435
  87. package/src/app/reseller/page.tsx +0 -15
  88. package/src/app/setup.ps1/route.ts +0 -79
  89. package/src/app/setup.sh/route.ts +0 -113
  90. package/src/app/sitemap.ts +0 -50
  91. package/src/app/status/StatusClient.tsx +0 -103
  92. package/src/app/status/layout.tsx +0 -11
  93. package/src/app/status/page.tsx +0 -15
  94. package/src/app/support/SupportClient.tsx +0 -411
  95. package/src/app/support/page.tsx +0 -25
  96. package/src/app/v1/chat/completions/route.ts +0 -3
  97. package/src/app/v1/chat/route.ts +0 -4
  98. package/src/app/v1/messages/route.ts +0 -3
  99. package/src/components/Footer.tsx +0 -120
  100. package/src/components/Header.tsx +0 -131
  101. package/src/components/landing/features.tsx +0 -99
  102. package/src/components/ui/badge.tsx +0 -32
  103. package/src/components/ui/button.tsx +0 -46
  104. package/src/components/ui/card.tsx +0 -50
  105. package/src/components/ui/dialog.tsx +0 -97
  106. package/src/components/ui/dropdown-menu.tsx +0 -156
  107. package/src/components/ui/input.tsx +0 -21
  108. package/src/components/ui/label.tsx +0 -15
  109. package/src/components/ui/separator.tsx +0 -22
  110. package/src/components/ui/switch.tsx +0 -27
  111. package/src/components/ui/tabs.tsx +0 -51
  112. package/src/components/ui/toast.tsx +0 -103
  113. package/src/lib/auth.ts +0 -45
  114. package/src/lib/prisma.ts +0 -20
  115. package/src/lib/providers.ts +0 -158
  116. package/src/lib/security.ts +0 -165
  117. package/src/lib/utils.ts +0 -14
  118. package/src/middleware.ts +0 -30
  119. package/tailwind.config.ts +0 -53
  120. package/tsconfig.json +0 -41
  121. package/tsconfig.tsbuildinfo +0 -1
  122. package/vercel.json +0 -8
  123. /package/{packages/cli/bin → bin}/claudmax.js +0 -0
  124. /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
- }