-
Notifications
You must be signed in to change notification settings - Fork 12
Admin dashboard #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Admin dashboard #45
Conversation
…nouncements, featured, health\n\n- RBAC + admin middleware; /admin layout\n- User mgmt (list/ban/delete), audit logs\n- Content moderation queue (filters, bulk, featured)\n- Tools approval workflow (notes, request-changes)\n- Announcements (publish/schedule) + public API\n- System health widgets; rate-limit fallback\n- Backup/restore UI + backup listing\n- Models & APIs (User, Content, Tool, Report, Log)\n\nAuth: temp hardcoded admin@local/admin123; NEXTAUTH_SECRET required\nSpam check gated with ENABLE_SPAM_CHECK=true (off by default)
…om this PR; restore .env.example and ignore backups/
|
@shreyannandanwar is attempting to deploy a commit to the rishabh's projects Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThis PR introduces a complete admin dashboard system with NextAuth-based authentication, multiple admin management pages, API routes for CRUD operations on content/tools/users/announcements, six new database models, middleware route protection, a login page, and updated dependencies including Next.js 16, antd, and next-auth. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant LoginPage as Login Page
participant NextAuth as NextAuth API
participant SessionDB as Session/JWT
participant Middleware as Middleware
participant AdminPage as Admin Page
participant AdminAPI as Admin API
participant DB as Database
participant Log as Log Model
User->>LoginPage: Enter email/password
LoginPage->>NextAuth: signIn('credentials', {email, password})
NextAuth->>SessionDB: Validate credentials against authOptions
alt Credentials Valid
NextAuth->>SessionDB: Create JWT with isAdmin flag
SessionDB-->>NextAuth: Return session
NextAuth-->>LoginPage: Success
LoginPage->>AdminPage: Navigate to /admin
else Invalid
NextAuth-->>LoginPage: Error
LoginPage->>User: Show alert
end
Note over Middleware: On subsequent /admin requests
AdminPage->>Middleware: Request /admin/content
Middleware->>SessionDB: Get JWT token
alt Token exists && isAdmin=true
Middleware-->>AdminPage: Allow
AdminPage->>AdminAPI: GET /api/admin/content?status=pending
else Token missing || isAdmin=false
Middleware-->>AdminPage: Redirect to /login or 403
end
AdminAPI->>AdminAPI: adminMiddleware check
AdminAPI->>DB: Query Content where status='pending'
DB-->>AdminAPI: Return documents
AdminAPI-->>AdminPage: JSON response
User->>AdminPage: Click Approve on content
AdminPage->>AdminAPI: PUT /api/admin/content {id, action: 'approve'}
AdminAPI->>DB: Update content.status = 'approved'
AdminAPI->>Log: Create Log {action: 'CONTENT_APPROVED', userId, details}
DB-->>AdminAPI: Success
Log-->>AdminAPI: Log saved
AdminAPI-->>AdminPage: Success message
AdminPage->>AdminPage: Refresh table
sequenceDiagram
actor Admin
participant BackupPage as Backup Page
participant BackupAPI as /api/admin/backup
participant RestoreAPI as /api/admin/restore
participant FileSystem as File System
participant MongoDB as MongoDB
participant Session as Session
Admin->>BackupPage: Select collection 'users'
Admin->>BackupPage: Click Create Backup
BackupPage->>Session: Get current session
BackupPage->>BackupAPI: POST /api/admin/backup?collection=users
BackupAPI->>FileSystem: Create /backups directory
BackupAPI->>MongoDB: db.collection('users').find()
MongoDB-->>BackupAPI: Return all user docs
BackupAPI->>FileSystem: Write users-backup-{timestamp}.json
FileSystem-->>BackupAPI: File saved
BackupAPI-->>BackupPage: {filename: 'users-backup-...'}
BackupPage->>BackupPage: Update backupFile, refresh list
Admin->>BackupPage: Select backup file
Admin->>BackupPage: Click Restore
BackupPage->>RestoreAPI: POST /api/admin/restore?collection=users&file=users-backup-...
RestoreAPI->>FileSystem: Read backup file
FileSystem-->>RestoreAPI: JSON data
RestoreAPI->>MongoDB: collection.insertMany(data)
MongoDB-->>RestoreAPI: Inserted
RestoreAPI-->>BackupPage: Success message
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Areas requiring extra attention:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Warning Tools execution failed with the following error: Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error) Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Contributor License Agreement (CLA)Thank you for your contribution to ToolBox. By submitting this pull request, you automatically agree to our Contributor License Agreement (CLA). Key Terms
Full AgreementPlease review the complete CLA for all terms and conditions. QuestionsIf you have questions about the CLA, please comment on this pull request or open a discussion. By continuing with this pull request, you confirm your acceptance of the CLA. |
PR Compliance Guide 🔍(Compliance updated until commit 6680560)Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label Previous compliance checksCompliance check up to commit 6680560
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 33
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
package.json (1)
62-62: Update eslint-config-next to match next version.
eslint-config-next@13.5.1is pinned to an older version whilenextis upgraded to^16.0.1. This version mismatch can cause linting rules to be inconsistent with the runtime environment and miss Next.js 16-specific best practices.Apply this diff to align the versions:
- "eslint-config-next": "13.5.1", + "eslint-config-next": "^16.0.1",
🧹 Nitpick comments (17)
lib/rate-limit.ts (1)
53-53: Avoidas anytype assertion.The
as anycast suggests a type mismatch between the config window format and whatRatelimit.slidingWindowexpects.Consider properly typing the window parameter or using a type-safe conversion:
- limiter: Ratelimit.slidingWindow(config.requests, config.window as any), + limiter: Ratelimit.slidingWindow(config.requests, config.window as "1h" | "1m" | "1d"),Or check the Upstash Ratelimit documentation for the expected type.
lib/db/connection.ts (1)
20-26: Improve options typing.The validation logic is sound, but the
as anycast on line 24 bypasses type safety. Consider using proper typing for Mongoose connection options.Apply this diff:
- const opts = { bufferCommands: false } as any; + const opts: mongoose.ConnectOptions = { bufferCommands: false };app/login/page.tsx (1)
25-48: Consider accessibility and security improvements.The login form would benefit from:
- Proper form labels (currently using placeholders only)
- Password visibility toggle
- HTTPS enforcement documentation
- Better styling approach (CSS modules instead of inline styles)
pages/api/tools/submit.ts (1)
16-22: Consider using Mongoose timestamps instead of manual createdAt.If the Tool schema has
timestamps: trueenabled, Mongoose will automatically managecreatedAtandupdatedAtfields, eliminating the need to manually set them.If timestamps are enabled in the Tool schema, apply this diff:
const tool = new Tool({ name, description, userId: (session.user as any).id, status: 'pending', - createdAt: new Date(), });lib/db/models/Report.ts (1)
3-13: LGTM! Consider adding indexes for query performance.The schema is well-defined with proper references, required fields, and automatic timestamps. The model registration pattern correctly prevents duplication errors.
For better query performance, consider adding indexes on frequently queried fields:
const ReportSchema = new Schema( { userId: { type: Schema.Types.ObjectId, ref: 'User', required: true }, - contentId: { type: Schema.Types.ObjectId, ref: 'Content', required: true }, + contentId: { type: Schema.Types.ObjectId, ref: 'Content', required: true, index: true }, reason: { type: String, required: true }, }, { timestamps: true }, );app/admin/logs/page.tsx (1)
34-39: Format dates and improve userId display.The Time column shows raw date strings (likely ISO format), and the User column shows ObjectId strings instead of meaningful user information. This creates a poor user experience.
Apply this diff to format dates and improve the display:
const columns = [ - { title: 'Time', dataIndex: 'createdAt' }, + { + title: 'Time', + dataIndex: 'createdAt', + render: (date: string) => date ? new Date(date).toLocaleString() : '-' + }, - { title: 'User', dataIndex: 'userId' }, + { + title: 'User', + dataIndex: 'userId', + render: (id: string) => id || 'System' + }, { title: 'Action', dataIndex: 'action' }, { title: 'Details', dataIndex: 'details' }, ];Note: Ideally, the API should populate user details (name/email) instead of returning just the ObjectId. Consider modifying the API route to use
.populate('userId', 'name email')for better UX.components/admin/DashboardLayout.tsx (1)
16-23: Drive menu selection from the current pathname.
defaultSelectedKeyslocks the highlight on/admin, so navigation to tools/users/etc. never updates the active item. Switching toselectedKeysbacked byusePathname()keeps the sider in sync with the router.-'use client'; - -import { Layout, Menu } from 'antd'; -import Link from 'next/link'; +'use client'; + +import { Layout, Menu } from 'antd'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; @@ -const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { +const navItems = [ + { key: '/admin', label: <Link href="/admin">Overview</Link> }, + { key: '/admin/users', label: <Link href="/admin/users">User Management</Link> }, + { key: '/admin/tools', label: <Link href="/admin/tools">Tools Approval</Link> }, + { key: '/admin/content', label: <Link href="/admin/content">Content Moderation</Link> }, + { key: '/admin/announcements', label: <Link href="/admin/announcements">Announcements</Link> }, + { key: '/admin/featured', label: <Link href="/admin/featured">Featured</Link> }, + { key: '/admin/backup', label: <Link href="/admin/backup">Backup/Restore</Link> }, +]; + +const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const pathname = usePathname(); + const selected = navItems.some(item => pathname.startsWith(item.key)) ? pathname : '/admin'; return ( <Layout> @@ - <Menu mode="inline" defaultSelectedKeys={['/admin']} items={[ - { key: '/admin/users', label: <Link href="/admin/users">User Management</Link> }, - { key: '/admin/tools', label: <Link href="/admin/tools">Tools Approval</Link> }, - { key: '/admin/content', label: <Link href="/admin/content">Content Moderation</Link> }, - { key: '/admin/announcements', label: <Link href="/admin/announcements">Announcements</Link> }, - { key: '/admin/featured', label: <Link href="/admin/featured">Featured</Link> }, - { key: '/admin/backup', label: <Link href="/admin/backup">Backup/Restore</Link> }, - ]} /> + <Menu mode="inline" selectedKeys={[selected]} items={navItems} />pages/api/admin/backup-list.ts (1)
11-24: Consider adding size/count limits to prevent resource exhaustion.While the implementation correctly reads the backup directory and gathers file metadata, there's no limit on the number of files returned. A directory with thousands of backup files could cause performance issues or memory exhaustion.
Consider adding pagination or a limit:
try { const dir = path.join(process.cwd(), 'backups'); const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => [] as any); const stats = await Promise.all( entries .filter((e: any) => e.isFile()) + .slice(0, 100) // Limit to most recent 100 backups .map(async (e: any) => { const p = path.join(dir, e.name); const s = await fs.stat(p); return { name: e.name, mtime: s.mtime.toISOString?.() || String(s.mtime), size: s.size }; }) ); stats.sort((a, b) => (a.mtime > b.mtime ? -1 : 1)); + const limited = stats.slice(0, 100); - return res.status(200).json({ files: stats }); + return res.status(200).json({ files: limited, total: stats.length }); } catch (err) {pages/tools/submit.tsx (1)
9-21: Add loading state and improve type safety.The form submission lacks a loading indicator and uses
anyfor the values parameter, reducing type safety and user feedback.Apply this diff:
+interface ToolSubmitValues { + name: string; + description: string; +} + const ToolSubmitPage = () => { const [form] = Form.useForm(); const router = useRouter(); + const [loading, setLoading] = useState(false); - const handleSubmit = async (values: any) => { + const handleSubmit = async (values: ToolSubmitValues) => { + setLoading(true); const res = await fetch('/api/tools/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values), }); + setLoading(false); if (res.ok) { message.success('Tool submitted for approval'); router.push('/tools'); } else { message.error('Failed to submit tool'); } };Update the button to show loading state:
<Form.Item> - <Button type="primary" htmlType="submit"> + <Button type="primary" htmlType="submit" loading={loading}> Submit </Button> </Form.Item>app/admin/announcements/page.tsx (1)
56-63: Consider adding update and delete operations.The table displays announcements but only provides creation functionality. Admins may need to edit or remove announcements after creation.
Consider adding Action column with Edit and Delete buttons:
{ title: 'Actions', render: (_: any, record: AnnouncementRow) => ( <Space> <Button size="small" onClick={() => handleEdit(record)}>Edit</Button> <Button size="small" danger onClick={() => handleDelete(record._id)}>Delete</Button> </Space> ) }app/admin/backup/page.tsx (1)
32-48: Validate collection name format.The collection name input accepts any string without validation, which could lead to unexpected behavior or errors when creating backups.
Add validation before making the backup request:
const handleBackup = async () => { if (!collection) return message.error('Enter collection'); + if (!/^[a-zA-Z0-9_-]+$/.test(collection)) { + return message.error('Collection name can only contain letters, numbers, hyphens, and underscores'); + } setBusy(true);pages/api/admin/content.ts (2)
28-33: Validate ObjectIds before bulk update.The bulk update operation doesn't validate that the provided IDs are valid MongoDB ObjectIds, which could cause database errors or unexpected behavior.
Add validation:
+import mongoose from 'mongoose'; + // Bulk update if (Array.isArray(ids) && ids.length > 0) { + // Validate all IDs are valid ObjectIds + const invalidIds = ids.filter(id => !mongoose.Types.ObjectId.isValid(id)); + if (invalidIds.length > 0) { + return res.status(400).json({ error: 'Invalid IDs provided' }); + } + const update: any = action === 'approve' ? { status: 'approved' } : { status: 'rejected' }; const result = await Content.updateMany({ _id: { $in: ids } }, { $set: update }); return res.status(200).json({ message: 'Bulk content updated', matched: (result as any).matchedCount ?? 0, modified: (result as any).modifiedCount ?? 0 }); }
52-54: Handle potential logging failures gracefully.If the session is null or the Log.create fails, the operation succeeds but the audit trail is lost. Consider handling this more explicitly.
const session = await getServerSession(req, res, authOptions); - await Log.create({ userId: (session?.user as any)?.id, action: `CONTENT_${action.toUpperCase()}` as any, details: JSON.stringify({ id }) }); + try { + await Log.create({ + userId: (session?.user as any)?.id || 'unknown', + action: `CONTENT_${action.toUpperCase()}` as any, + details: JSON.stringify({ id }) + }); + } catch (logError) { + console.error('Failed to create audit log:', logError); + // Continue - don't fail the request if logging fails + } return res.status(200).json({ message: 'Content updated' });pages/api/admin/users.ts (1)
13-16: Consider implementing field-level access control for sensitive data.The GET endpoint exposes email addresses for all users. Depending on privacy requirements, you may want to limit exposure of PII or add pagination for large user bases.
Consider adding pagination and optionally filtering sensitive fields:
if (req.method === 'GET') { + const { page = '1', limit = '50' } = req.query as { page?: string; limit?: string }; + const skip = (parseInt(page) - 1) * parseInt(limit); + - const users = await User.find().select('name email role banned isAdmin createdAt').lean(); - return res.status(200).json(users); + const [users, total] = await Promise.all([ + User.find() + .select('name email role banned isAdmin createdAt') + .sort({ createdAt: -1 }) + .skip(skip) + .limit(parseInt(limit)) + .lean(), + User.countDocuments() + ]); + + return res.status(200).json({ users, total, page: parseInt(page), limit: parseInt(limit) }); }app/admin/featured/page.tsx (3)
77-100: Optimize state updates to prevent unnecessary re-renders.Lines 79, 86, and 93 recreate the entire rows array on every input change, which causes all rows to re-render. For large lists, this can cause performance issues.
Consider using a keyed update approach or debouncing:
// Add at the top of the component import { useCallback } from 'react'; // Create an update helper const updateRow = useCallback((id: string, updates: Partial<Row>) => { setRows(prev => prev.map(x => x._id === id ? { ...x, ...updates } : x)); }, []); // Then in columns: { title: 'Order', dataIndex: 'featuredOrder', render: (_: any, r: Row, idx: number) => ( <InputNumber value={r.featuredOrder ?? idx} onChange={(v) => updateRow(r._id, { featuredOrder: Number(v) })} /> ) }, { title: 'Start', dataIndex: 'featuredStart', render: (_: any, r: Row) => ( <DatePicker value={r.featuredStart ? dayjs(r.featuredStart) : undefined} onChange={(d) => updateRow(r._id, { featuredStart: d?.toISOString() })} showTime /> ) }, { title: 'End', dataIndex: 'featuredEnd', render: (_: any, r: Row) => ( <DatePicker value={r.featuredEnd ? dayjs(r.featuredEnd) : undefined} onChange={(d) => updateRow(r._id, { featuredEnd: d?.toISOString() })} showTime /> ) },
40-58: Validate date ranges before saving.The
saveRowfunction doesn't validate thatfeaturedStartis beforefeaturedEnd, which could lead to invalid scheduling configurations.Add validation:
const saveRow = async (r: Row) => { + // Validate date range + if (r.featuredStart && r.featuredEnd) { + const start = new Date(r.featuredStart); + const end = new Date(r.featuredEnd); + if (start >= end) { + message.error('Start date must be before end date'); + return; + } + } + const body: any = { featured: true, featuredOrder: r.featuredOrder ?? 0, featuredStart: r.featuredStart || undefined, featuredEnd: r.featuredEnd || undefined, };
60-75: Clarify fallback behavior for missing featuredOrder.Line 62 falls back to the array index when
featuredOrderis undefined. This could cause unexpected ordering if some items have explicit orders and others don't.Consider making the fallback behavior explicit or normalizing orders:
const saveAllOrder = async () => { setSaving(true); - const order = rows.map((r, i) => ({ id: r._id, featuredOrder: r.featuredOrder ?? i })); + // Normalize order: assign sequential order if missing + const order = rows.map((r, i) => ({ + id: r._id, + featuredOrder: typeof r.featuredOrder === 'number' ? r.featuredOrder : i + })); const res = await fetch('/api/admin/content', {Alternatively, warn the user if any items lack explicit order values before saving.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (41)
.env.example(1 hunks).gitignore(1 hunks)app/admin/announcements/page.tsx(1 hunks)app/admin/backup/page.tsx(1 hunks)app/admin/content/page.tsx(1 hunks)app/admin/featured/page.tsx(1 hunks)app/admin/layout.tsx(1 hunks)app/admin/logs/page.tsx(1 hunks)app/admin/page.tsx(1 hunks)app/admin/tools/page.tsx(1 hunks)app/admin/users/page.tsx(1 hunks)app/api/announcements/route.ts(1 hunks)app/api/auth/[...nextauth]/route.ts(1 hunks)app/login/page.tsx(1 hunks)components/admin/DashboardLayout.tsx(1 hunks)lib/auth.ts(1 hunks)lib/db/connection.ts(1 hunks)lib/db/models/Announcement.ts(1 hunks)lib/db/models/Content.ts(1 hunks)lib/db/models/Log.ts(1 hunks)lib/db/models/Report.ts(1 hunks)lib/db/models/Tool.ts(1 hunks)lib/db/models/User.ts(1 hunks)lib/middleware/adminMiddleware.ts(1 hunks)lib/rate-limit.ts(2 hunks)middleware.ts(1 hunks)next.config.js(2 hunks)package.json(3 hunks)pages/api/admin/announcements.ts(1 hunks)pages/api/admin/backup-list.ts(1 hunks)pages/api/admin/backup.ts(1 hunks)pages/api/admin/content.ts(1 hunks)pages/api/admin/content/[id]/featured.ts(1 hunks)pages/api/admin/logs.ts(1 hunks)pages/api/admin/restore.ts(1 hunks)pages/api/admin/tools.ts(1 hunks)pages/api/admin/users.ts(1 hunks)pages/api/report.ts(1 hunks)pages/api/tools/submit.ts(1 hunks)pages/tools/submit.tsx(1 hunks)tsconfig.json(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-24T06:31:35.476Z
Learnt from: aryadharmadhikari
Repo: rishabh3562/ToolBox PR: 41
File: package.json:65-65
Timestamp: 2025-10-24T06:31:35.476Z
Learning: Next.js 16.0.0 was released as stable on October 21, 2025, and is the recommended version for production use.
Applied to files:
package.json
🧬 Code graph analysis (15)
pages/api/admin/restore.ts (3)
pages/api/admin/backup.ts (1)
handler(8-47)lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)
app/api/announcements/route.ts (1)
lib/db/connection.ts (1)
connectDB(16-36)
pages/api/admin/logs.ts (2)
lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)
app/api/auth/[...nextauth]/route.ts (1)
lib/auth.ts (1)
authOptions(4-55)
pages/api/admin/users.ts (3)
lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)lib/auth.ts (2)
session(47-53)authOptions(4-55)
pages/api/admin/tools.ts (5)
pages/api/tools/submit.ts (1)
handler(7-28)pages/api/admin/content.ts (1)
handler(9-58)lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)lib/auth.ts (2)
session(47-53)authOptions(4-55)
pages/api/tools/submit.ts (2)
lib/auth.ts (2)
session(47-53)authOptions(4-55)lib/db/connection.ts (1)
connectDB(16-36)
pages/api/admin/content/[id]/featured.ts (4)
pages/api/admin/content.ts (1)
handler(9-58)lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)lib/auth.ts (2)
session(47-53)authOptions(4-55)
pages/api/admin/backup-list.ts (3)
pages/api/admin/backup.ts (1)
handler(8-47)pages/api/admin/restore.ts (1)
handler(8-31)lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)
lib/middleware/adminMiddleware.ts (1)
lib/auth.ts (2)
session(47-53)authOptions(4-55)
pages/api/admin/backup.ts (4)
pages/api/admin/backup-list.ts (1)
handler(6-29)pages/api/admin/restore.ts (1)
handler(8-31)lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)
pages/api/admin/announcements.ts (2)
lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)
pages/api/admin/content.ts (5)
pages/api/admin/content/[id]/featured.ts (1)
handler(9-31)pages/api/admin/tools.ts (1)
handler(9-59)lib/middleware/adminMiddleware.ts (1)
adminMiddleware(21-41)lib/db/connection.ts (1)
connectDB(16-36)lib/auth.ts (2)
session(47-53)authOptions(4-55)
pages/api/report.ts (3)
pages/api/tools/submit.ts (1)
handler(7-28)lib/auth.ts (2)
session(47-53)authOptions(4-55)lib/db/connection.ts (1)
connectDB(16-36)
lib/db/models/Tool.ts (1)
types/tool.ts (1)
Tool(1-7)
🔇 Additional comments (27)
next.config.js (2)
3-4: LGTM! Necessary change for API routes and server-side features.Commenting out
output: "export"is essential for this PR's admin dashboard functionality, as static export mode is incompatible with API routes, NextAuth authentication, and server-side rendering features being introduced.
14-17: Verify necessity of the turbopack configuration.The comment suggests this is needed for "multiple lockfiles" in a monorepo-like setup, but the PR summary doesn't indicate a workspace or monorepo structure. This configuration may be unnecessary if you're not actually using a monorepo setup.
Run the following script to check if multiple lockfiles or workspace configuration exists:
If there are no multiple lockfiles or workspace setup, consider removing this configuration to avoid unnecessary complexity.
package.json (2)
67-67: Next.js 16 upgrade aligns with stable release; verify app compatibility.Upgrading Next.js from 13.5.1 to ^16.0.1 is well-timed with the stable Oct 21 release. However, major version upgrades often require configuration adjustments (e.g.,
next.config.js, middleware, API routes) and may introduce breaking changes.Please verify:
- Next.js 16 migration guide has been reviewed (breaking changes in App Router, API routes, middleware).
next.config.jsis compatible with Next.js 16 (check for deprecated config options).- NextAuth integration works with Next.js 16 (session/auth patterns may differ).
Based on learnings, Next.js 16.0.0 is production-ready.
68-68: Verify next-auth@^4.24.13 compatibility with Next.js 16.NextAuth v4 may have compatibility constraints with Next.js 16. While v4.24.13 appears recent, confirm that the session/auth patterns used in the admin dashboard work correctly with Next.js 16 middleware and API routes.
Please verify:
- NextAuth v4 is compatible with Next.js 16 (check official NextAuth documentation or release notes).
- Session authentication flows work correctly in the new Next.js 16 environment.
- Middleware guards (mentioned in PR objectives) are compatible with Next.js 16's middleware system.
lib/rate-limit.ts (4)
42-43: LGTM: Stricter Redis validation improves security.The enhanced condition now requires both credentials and enforces
https://URLs, which strengthens the production environment check.
44-49: LGTM: Type assertions are safe here.The
as stringassertions are protected by the guard condition on line 42 that ensures both values are truthy.
93-101: LGTM: Module-level initialization is appropriate.Creating rate limiters at module initialization time is correct. The limiters are reused across requests within the same process/instance.
59-89: Verify serverless/multi-instance behavior.In serverless deployments (e.g., Vercel, AWS Lambda) or horizontally scaled applications, each instance maintains its own in-memory
bucketsMap. This means the effective rate limit is multiplied by the number of concurrent instances.For example, with 10 serverless instances and a limit of 60 requests/minute, users could actually make up to 600 requests/minute (60 per instance).
Please verify and document this behavior, especially if the application is deployed in a serverless or multi-instance environment. Consider:
- Documenting this limitation in code comments or README
- Recommending Redis for production deployments
- Adding a warning log when the in-memory limiter is used in production
.gitignore (1)
18-20: LGTM!The addition of the
/backups/directory to.gitignoreis appropriate for excluding generated backup files from version control.tsconfig.json (1)
15-15: LGTM!The configuration changes align with Next.js best practices:
moduleResolution: "node"for Node.js compatibility,jsx: "react-jsx"for the modern JSX transform, andbaseUrl: "."for cleaner imports.Also applies to: 18-18, 20-20
app/admin/layout.tsx (1)
1-5: Verify authentication protection.This layout component doesn't include authentication checks. Ensure that the admin routes are protected by middleware or that the
DashboardLayoutcomponent handles authentication/authorization.Run the following script to verify admin route protection:
app/api/auth/[...nextauth]/route.ts (1)
1-5: LGTM!Standard NextAuth App Router setup. The implementation correctly imports and uses the shared
authOptionsconfiguration.pages/api/admin/backup-list.ts (1)
6-9: LGTM: Access control and method validation properly implemented.The admin middleware enforcement and method check follow the established pattern in this PR.
pages/api/admin/backup.ts (1)
16-18: Verify backup filename doesn't cause path traversal.While the current implementation generates the filename from timestamp and collection name, ensure the collection name sanitization (suggested in another comment) is implemented to prevent potential path traversal attacks.
After implementing collection name validation, verify that malicious collection names cannot escape the backups directory:
pages/api/admin/tools.ts (5)
1-11: LGTM!The imports and handler setup follow best practices with proper admin authentication and database connection.
13-20: LGTM!The GET handler implements sensible defaults, case-insensitive search, and uses
.lean()for optimized query performance.
37-44: LGTM!Proper validation and status update logic.
53-56: LGTM!Proper save and audit logging.
58-59: LGTM!Standard method not allowed handling.
app/admin/tools/page.tsx (4)
15-23: LGTM!State initialization follows React best practices.
25-89: LGTM!The data fetching and action functions follow good patterns with proper error handling, loading states, and user feedback via Ant Design messages.
91-112: LGTM!Table configuration provides good UX with visual status indicators and confirmation dialogs for destructive actions.
114-145: LGTM!The control panel and table rendering provide intuitive filtering and bulk action capabilities.
app/admin/content/page.tsx (4)
1-22: LGTM!Interface and state setup are well-structured with appropriate fields for content management.
24-103: LGTM!Data fetching and action functions follow consistent patterns with proper error handling and user feedback. The
toggleFeaturedfunction provides intuitive content highlighting capabilities.
105-124: LGTM!Table configuration provides excellent UX with inline featured toggle and confirmation for destructive actions.
126-160: LGTM!The UI provides intuitive filtering and bulk operations with a clean, maintainable structure that's simpler than the tools page (appropriately so, given the different workflow requirements).
| # Core | ||
| MONGODB_URI=mongodb://127.0.0.1:27017/toolbox | ||
| NEXTAUTH_URL=http://localhost:3000 | ||
| NEXTAUTH_SECRET=change-me |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace weak default with a strong placeholder.
The NEXTAUTH_SECRET default value "change-me" is weak and could be accidentally deployed to production. Consider using a placeholder that makes it more obvious this must be changed, such as "REPLACE_WITH_SECURE_RANDOM_STRING_MIN_32_CHARS".
Apply this diff:
-NEXTAUTH_SECRET=change-me
+NEXTAUTH_SECRET=REPLACE_WITH_SECURE_RANDOM_STRING_MIN_32_CHARS📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| NEXTAUTH_SECRET=change-me | |
| NEXTAUTH_SECRET=REPLACE_WITH_SECURE_RANDOM_STRING_MIN_32_CHARS |
🤖 Prompt for AI Agents
In .env.example around line 6, the NEXTAUTH_SECRET default value is the weak
"change-me"; replace it with a clearly identifiable strong-placeholder to avoid
accidental deployment, e.g. set NEXTAUTH_SECRET to a descriptive token like
REPLACE_WITH_SECURE_RANDOM_STRING_MIN_32_CHARS and add a short comment reminding
developers to generate and use a secure random string of at least 32 characters
in production.
| # Dev admin (Credentials provider) | ||
| ADMIN_EMAIL=admin@local | ||
| ADMIN_PASSWORD=admin123 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security: Clarify these are development-only credentials.
While the comment indicates "Dev admin", having actual working credentials in .env.example poses a risk if developers copy this file without modification or if these credentials are used in production. Consider adding a more prominent warning or using obvious placeholder values.
Apply this diff to make it more obvious these are unsafe defaults:
-# Dev admin (Credentials provider)
-ADMIN_EMAIL=admin@local
-ADMIN_PASSWORD=admin123
+# Dev admin (Credentials provider) - MUST BE CHANGED FOR PRODUCTION
+# WARNING: These are insecure defaults for local development only
+ADMIN_EMAIL=admin@local
+ADMIN_PASSWORD=admin123📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Dev admin (Credentials provider) | |
| ADMIN_EMAIL=admin@local | |
| ADMIN_PASSWORD=admin123 | |
| # Dev admin (Credentials provider) - MUST BE CHANGED FOR PRODUCTION | |
| # WARNING: These are insecure defaults for local development only | |
| ADMIN_EMAIL=admin@local | |
| ADMIN_PASSWORD=admin123 | |
🤖 Prompt for AI Agents
In .env.example around lines 8 to 11, the dev admin credentials are real-looking
and may be copied into environments; replace ADMIN_EMAIL and ADMIN_PASSWORD with
non-working placeholder values (e.g., ADMIN_EMAIL=your-admin@example.com,
ADMIN_PASSWORD=CHANGE_ME) and add a prominent comment above these lines stating
these are development-only unsafe defaults that must be changed before use (do
not commit real credentials and never use these in production).
| const fetchAnnouncements = async () => { | ||
| const res = await fetch('/api/admin/announcements'); | ||
| if (res.ok) { | ||
| const data = await res.json(); | ||
| setRows(data); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for failed fetch requests.
The fetchAnnouncements function doesn't handle failed responses, which could leave the UI in an inconsistent state or hide errors from the user.
Apply this diff:
const fetchAnnouncements = async () => {
const res = await fetch('/api/admin/announcements');
if (res.ok) {
const data = await res.json();
setRows(data);
+ } else {
+ message.error('Failed to load announcements');
+ setRows([]);
}
};Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/admin/announcements/page.tsx around lines 21 to 27, fetchAnnouncements
lacks error handling for non-ok responses and network failures; wrap the fetch
in a try/catch, check res.ok and throw or handle when it's false, and handle
errors by setting an error state or displaying a user-facing message (and
optionally clear or keep rows), ensuring you don't assume success before calling
res.json(); also ensure any loading state is updated in both success and failure
paths.
| const handleRestore = async () => { | ||
| if (!collection || !backupFile) return message.error('Enter collection and file'); | ||
| setBusy(true); | ||
| try { | ||
| const res = await fetch(`/api/admin/restore?collection=${encodeURIComponent(collection)}&file=${encodeURIComponent(backupFile)}`, { method: 'POST' }); | ||
| const data = await res.json().catch(() => ({})); | ||
| if (res.ok) { | ||
| message.success('Restore completed'); | ||
| } else { | ||
| message.error(data.error || 'Failed to restore'); | ||
| } | ||
| } finally { | ||
| setBusy(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add confirmation dialog for destructive restore operation.
The restore operation can overwrite or corrupt database data but executes immediately without user confirmation. This is a critical safety concern for production environments.
Add a confirmation modal before executing restore:
+import { Button, Input, Space, message, Table, Modal } from 'antd';
const handleRestore = async () => {
if (!collection || !backupFile) return message.error('Enter collection and file');
+
+ Modal.confirm({
+ title: 'Confirm Restore',
+ content: `This will restore "${collection}" from "${backupFile}". This operation may overwrite existing data. Are you sure?`,
+ okText: 'Restore',
+ okType: 'danger',
+ onOk: async () => {
setBusy(true);
try {
const res = await fetch(`/api/admin/restore?collection=${encodeURIComponent(collection)}&file=${encodeURIComponent(backupFile)}`, { method: 'POST' });
const data = await res.json().catch(() => ({}));
if (res.ok) {
message.success('Restore completed');
} else {
message.error(data.error || 'Failed to restore');
}
} finally {
setBusy(false);
}
+ },
+ });
- setBusy(true);
- try {
- const res = await fetch(`/api/admin/restore?collection=${encodeURIComponent(collection)}&file=${encodeURIComponent(backupFile)}`, { method: 'POST' });
- const data = await res.json().catch(() => ({}));
- if (res.ok) {
- message.success('Restore completed');
- } else {
- message.error(data.error || 'Failed to restore');
- }
- } finally {
- setBusy(false);
- }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleRestore = async () => { | |
| if (!collection || !backupFile) return message.error('Enter collection and file'); | |
| setBusy(true); | |
| try { | |
| const res = await fetch(`/api/admin/restore?collection=${encodeURIComponent(collection)}&file=${encodeURIComponent(backupFile)}`, { method: 'POST' }); | |
| const data = await res.json().catch(() => ({})); | |
| if (res.ok) { | |
| message.success('Restore completed'); | |
| } else { | |
| message.error(data.error || 'Failed to restore'); | |
| } | |
| } finally { | |
| setBusy(false); | |
| } | |
| }; | |
| const handleRestore = async () => { | |
| if (!collection || !backupFile) return message.error('Enter collection and file'); | |
| Modal.confirm({ | |
| title: 'Confirm Restore', | |
| content: `This will restore "${collection}" from "${backupFile}". This operation may overwrite existing data. Are you sure?`, | |
| okText: 'Restore', | |
| okType: 'danger', | |
| onOk: async () => { | |
| setBusy(true); | |
| try { | |
| const res = await fetch(`/api/admin/restore?collection=${encodeURIComponent(collection)}&file=${encodeURIComponent(backupFile)}`, { method: 'POST' }); | |
| const data = await res.json().catch(() => ({})); | |
| if (res.ok) { | |
| message.success('Restore completed'); | |
| } else { | |
| message.error(data.error || 'Failed to restore'); | |
| } | |
| } finally { | |
| setBusy(false); | |
| } | |
| }, | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In app/admin/backup/page.tsx around lines 50 to 64, the restore handler executes
immediately and can destructively overwrite data; add a confirmation step before
proceeding. Update handleRestore to show a confirmation dialog (e.g.,
Modal.confirm or window.confirm) that warns the user about irreversible data
changes, requires explicit confirmation, and only proceeds to setBusy(true) and
call the restore API when the user confirms; cancel should abort without
changing state. Ensure the confirmation text is clear and that error/success
handling and setBusy(false) remain unchanged in the existing try/finally flow.
| const fetchLogs = async () => { | ||
| setLoading(true); | ||
| const res = await fetch('/api/admin/logs'); | ||
| if (res.ok) { | ||
| const data = await res.json(); | ||
| setRows(data); | ||
| } else { | ||
| setRows([]); | ||
| } | ||
| setLoading(false); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add user-visible error handling.
The fetch function silently fails by setting an empty array. Users won't know if the logs failed to load or if there are genuinely no logs.
Consider adding user-visible error feedback:
+import { message } from 'antd';
const fetchLogs = async () => {
setLoading(true);
const res = await fetch('/api/admin/logs');
if (res.ok) {
const data = await res.json();
setRows(data);
} else {
+ message.error('Failed to load audit logs');
setRows([]);
}
setLoading(false);
};Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/admin/logs/page.tsx around lines 18 to 28, the fetchLogs function
swallows failures by setting rows to an empty array so the UI cannot distinguish
an error from having no logs; modify fetchLogs to catch network/response errors,
set a new error state (e.g., setError with a user-friendly message), and only
clear rows on success, and ensure loading is toggled in finally; update the
component UI to render the error message (and optionally a retry button) when
error state is present so users see visible feedback when logs fail to load.
| if (req.method === 'PATCH') { | ||
| const { id, banned, role, isAdmin } = req.body as { id?: string; banned?: boolean; role?: string; isAdmin?: boolean }; | ||
| if (!id) return res.status(400).json({ error: 'Missing id' }); | ||
| const updates: any = {}; | ||
| if (typeof banned !== 'undefined') updates.banned = !!banned; | ||
| if (typeof role !== 'undefined') updates.role = role; | ||
| if (typeof isAdmin !== 'undefined') updates.isAdmin = !!isAdmin; | ||
| const user = await User.findByIdAndUpdate(id, updates, { new: true }); | ||
| if (!user) return res.status(404).json({ error: 'User not found' }); | ||
|
|
||
| const session = await getServerSession(req, res, authOptions); | ||
| await Log.create({ userId: (session?.user as any)?.id, action: 'USER_UPDATE', details: JSON.stringify({ id, updates }) }); | ||
| return res.status(200).json({ message: 'User updated' }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add safeguards for admin privilege modifications.
The PATCH operation allows modifying isAdmin status without restrictions. An admin could accidentally demote themselves or all other admins, causing a complete lockout.
Apply this diff:
if (req.method === 'PATCH') {
const { id, banned, role, isAdmin } = req.body as { id?: string; banned?: boolean; role?: string; isAdmin?: boolean };
if (!id) return res.status(400).json({ error: 'Missing id' });
+
+ const session = await getServerSession(req, res, authOptions);
+ const currentUserId = (session?.user as any)?.id;
+
+ // Prevent self-demotion from admin
+ if (id === currentUserId && typeof isAdmin !== 'undefined' && !isAdmin) {
+ return res.status(400).json({ error: 'Cannot remove your own admin privileges' });
+ }
+
const updates: any = {};
if (typeof banned !== 'undefined') updates.banned = !!banned;
if (typeof role !== 'undefined') updates.role = role;
if (typeof isAdmin !== 'undefined') updates.isAdmin = !!isAdmin;
const user = await User.findByIdAndUpdate(id, updates, { new: true });
if (!user) return res.status(404).json({ error: 'User not found' });
- const session = await getServerSession(req, res, authOptions);
await Log.create({ userId: (session?.user as any)?.id, action: 'USER_UPDATE', details: JSON.stringify({ id, updates }) });
return res.status(200).json({ message: 'User updated' });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (req.method === 'PATCH') { | |
| const { id, banned, role, isAdmin } = req.body as { id?: string; banned?: boolean; role?: string; isAdmin?: boolean }; | |
| if (!id) return res.status(400).json({ error: 'Missing id' }); | |
| const updates: any = {}; | |
| if (typeof banned !== 'undefined') updates.banned = !!banned; | |
| if (typeof role !== 'undefined') updates.role = role; | |
| if (typeof isAdmin !== 'undefined') updates.isAdmin = !!isAdmin; | |
| const user = await User.findByIdAndUpdate(id, updates, { new: true }); | |
| if (!user) return res.status(404).json({ error: 'User not found' }); | |
| const session = await getServerSession(req, res, authOptions); | |
| await Log.create({ userId: (session?.user as any)?.id, action: 'USER_UPDATE', details: JSON.stringify({ id, updates }) }); | |
| return res.status(200).json({ message: 'User updated' }); | |
| } | |
| if (req.method === 'PATCH') { | |
| const { id, banned, role, isAdmin } = req.body as { id?: string; banned?: boolean; role?: string; isAdmin?: boolean }; | |
| if (!id) return res.status(400).json({ error: 'Missing id' }); | |
| const session = await getServerSession(req, res, authOptions); | |
| const currentUserId = (session?.user as any)?.id; | |
| // Prevent self-demotion from admin | |
| if (id === currentUserId && typeof isAdmin !== 'undefined' && !isAdmin) { | |
| return res.status(400).json({ error: 'Cannot remove your own admin privileges' }); | |
| } | |
| const updates: any = {}; | |
| if (typeof banned !== 'undefined') updates.banned = !!banned; | |
| if (typeof role !== 'undefined') updates.role = role; | |
| if (typeof isAdmin !== 'undefined') updates.isAdmin = !!isAdmin; | |
| const user = await User.findByIdAndUpdate(id, updates, { new: true }); | |
| if (!user) return res.status(404).json({ error: 'User not found' }); | |
| await Log.create({ userId: (session?.user as any)?.id, action: 'USER_UPDATE', details: JSON.stringify({ id, updates }) }); | |
| return res.status(200).json({ message: 'User updated' }); | |
| } |
| if (req.method === 'DELETE') { | ||
| const { id } = req.query as { id?: string }; | ||
| if (!id) return res.status(400).json({ error: 'Missing id' }); | ||
| const user = await User.findByIdAndDelete(id); | ||
| if (!user) return res.status(404).json({ error: 'User not found' }); | ||
|
|
||
| const session = await getServerSession(req, res, authOptions); | ||
| await Log.create({ userId: (session?.user as any)?.id, action: 'USER_DELETE', details: JSON.stringify({ id }) }); | ||
| return res.status(200).json({ message: 'User deleted' }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent deletion of self and other admins.
The DELETE operation allows any admin to delete any user, including themselves or other admins. This could lead to complete lockout if all admin accounts are deleted or self-deletion accidents.
Apply this diff:
if (req.method === 'DELETE') {
const { id } = req.query as { id?: string };
if (!id) return res.status(400).json({ error: 'Missing id' });
+
+ const session = await getServerSession(req, res, authOptions);
+ const currentUserId = (session?.user as any)?.id;
+
+ // Prevent self-deletion
+ if (id === currentUserId) {
+ return res.status(400).json({ error: 'Cannot delete your own account' });
+ }
+
+ // Check if target user is an admin
+ const targetUser = await User.findById(id);
+ if (!targetUser) return res.status(404).json({ error: 'User not found' });
+
+ if (targetUser.isAdmin) {
+ return res.status(403).json({ error: 'Cannot delete admin accounts' });
+ }
+
- const user = await User.findByIdAndDelete(id);
- if (!user) return res.status(404).json({ error: 'User not found' });
+ await User.findByIdAndDelete(id);
- const session = await getServerSession(req, res, authOptions);
await Log.create({ userId: (session?.user as any)?.id, action: 'USER_DELETE', details: JSON.stringify({ id }) });
return res.status(200).json({ message: 'User deleted' });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (req.method === 'DELETE') { | |
| const { id } = req.query as { id?: string }; | |
| if (!id) return res.status(400).json({ error: 'Missing id' }); | |
| const user = await User.findByIdAndDelete(id); | |
| if (!user) return res.status(404).json({ error: 'User not found' }); | |
| const session = await getServerSession(req, res, authOptions); | |
| await Log.create({ userId: (session?.user as any)?.id, action: 'USER_DELETE', details: JSON.stringify({ id }) }); | |
| return res.status(200).json({ message: 'User deleted' }); | |
| } | |
| if (req.method === 'DELETE') { | |
| const { id } = req.query as { id?: string }; | |
| if (!id) return res.status(400).json({ error: 'Missing id' }); | |
| const session = await getServerSession(req, res, authOptions); | |
| const currentUserId = (session?.user as any)?.id; | |
| // Prevent self-deletion | |
| if (id === currentUserId) { | |
| return res.status(400).json({ error: 'Cannot delete your own account' }); | |
| } | |
| // Check if target user is an admin | |
| const targetUser = await User.findById(id); | |
| if (!targetUser) return res.status(404).json({ error: 'User not found' }); | |
| if (targetUser.isAdmin) { | |
| return res.status(403).json({ error: 'Cannot delete admin accounts' }); | |
| } | |
| await User.findByIdAndDelete(id); | |
| await Log.create({ userId: (session?.user as any)?.id, action: 'USER_DELETE', details: JSON.stringify({ id }) }); | |
| return res.status(200).json({ message: 'User deleted' }); | |
| } |
| if (req.method === 'POST') { | ||
| const { contentId, reason } = req.body as { contentId?: string; reason?: string }; | ||
| if (!contentId || !reason) return res.status(400).json({ error: 'Missing contentId or reason' }); | ||
| const report = new Report({ | ||
| userId: (session.user as any).id, | ||
| contentId, | ||
| reason, | ||
| createdAt: new Date(), | ||
| }); | ||
| await report.save(); | ||
| return res.status(200).json({ message: 'Report submitted' }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add validation and duplicate prevention.
The endpoint should validate that:
- The contentId refers to an actual content document
- The user hasn't already reported this content
- The reason input is sanitized
Example improvements:
if (req.method === 'POST') {
const { contentId, reason } = req.body as { contentId?: string; reason?: string };
if (!contentId || !reason) return res.status(400).json({ error: 'Missing contentId or reason' });
// Validate contentId format
if (!mongoose.Types.ObjectId.isValid(contentId)) {
return res.status(400).json({ error: 'Invalid contentId' });
}
// Check for duplicate report
const existing = await Report.findOne({
userId: session.user.id,
contentId
});
if (existing) {
return res.status(409).json({ error: 'You have already reported this content' });
}
// Sanitize reason (limit length)
const sanitizedReason = reason.trim().substring(0, 500);
const report = new Report({
userId: session.user.id,
contentId,
reason: sanitizedReason,
createdAt: new Date(),
});
await report.save();
return res.status(200).json({ message: 'Report submitted' });
}🤖 Prompt for AI Agents
In pages/api/report.ts around lines 12 to 23, the POST handler currently accepts
contentId and reason without verifying the content exists, preventing duplicate
reports, or sanitizing input; update it to (1) validate contentId format with
mongoose.Types.ObjectId.isValid(contentId) and return 400 on invalid id, (2)
confirm a Content document with that id exists and return 404 if not found, (3)
check Report.findOne({ userId: (session.user as any).id, contentId }) and return
409 if an existing report is found, and (4) sanitize the reason by trimming and
limiting length (e.g., substring(0,500)) before saving; keep appropriate status
codes and error messages and ensure you import mongoose and reference the
correct Content model.
| const { contentId, reason } = req.body as { contentId?: string; reason?: string }; | ||
| if (!contentId || !reason) return res.status(400).json({ error: 'Missing contentId or reason' }); | ||
| const report = new Report({ | ||
| userId: (session.user as any).id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix type safety issue.
The type assertion (session.user as any).id bypasses type checking and could fail at runtime if the session structure differs from expectations. Define proper types for the session user object.
Create a type definition file or extend next-auth types:
// types/next-auth.d.ts
import 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
id: string;
email?: string | null;
name?: string | null;
image?: string | null;
isAdmin?: boolean;
}
}
}Then use it safely:
- userId: (session.user as any).id,
+ userId: session.user.id,🤖 Prompt for AI Agents
In pages/api/report.ts around line 16, the code uses (session.user as any).id
which bypasses type safety; define or extend NextAuth Session types (e.g.,
create types/next-auth.d.ts augmenting next-auth's Session.user with id:string
and optional fields) and import/ensure the type file is included by tsconfig so
session has the precise type; then replace the assertion with session.user.id
(or properly narrow/check session.user) to avoid any any casts and preserve
compile-time safety.
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
| const session = await getServerSession(req, res, authOptions); | ||
| if (!session?.user) return res.status(401).json({ error: 'Unauthorized' }); | ||
| await connectDB(); | ||
|
|
||
| if (req.method === 'POST') { | ||
| const { name, description } = req.body as { name?: string; description?: string }; | ||
| if (!name || !description) return res.status(400).json({ error: 'Missing name or description' }); | ||
|
|
||
| const tool = new Tool({ | ||
| name, | ||
| description, | ||
| userId: (session.user as any).id, | ||
| status: 'pending', | ||
| createdAt: new Date(), | ||
| }); | ||
| await tool.save(); | ||
| return res.status(200).json({ message: 'Tool submitted' }); | ||
| } | ||
|
|
||
| return res.status(405).json({ error: 'Method not allowed' }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for database operations.
The handler lacks try-catch blocks around database operations. Errors during connectDB() or tool.save() will result in unhandled exceptions.
Apply this diff to add error handling:
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getServerSession(req, res, authOptions);
if (!session?.user) return res.status(401).json({ error: 'Unauthorized' });
- await connectDB();
if (req.method === 'POST') {
- const { name, description } = req.body as { name?: string; description?: string };
- if (!name || !description) return res.status(400).json({ error: 'Missing name or description' });
-
- const tool = new Tool({
- name,
- description,
- userId: (session.user as any).id,
- status: 'pending',
- createdAt: new Date(),
- });
- await tool.save();
- return res.status(200).json({ message: 'Tool submitted' });
+ try {
+ await connectDB();
+ const { name, description } = req.body as { name?: string; description?: string };
+ if (!name || !description) return res.status(400).json({ error: 'Missing name or description' });
+
+ const tool = new Tool({
+ name,
+ description,
+ userId: (session.user as any).id,
+ status: 'pending',
+ createdAt: new Date(),
+ });
+ await tool.save();
+ return res.status(200).json({ message: 'Tool submitted' });
+ } catch (error) {
+ console.error('Error submitting tool:', error);
+ return res.status(500).json({ error: 'Failed to submit tool' });
+ }
}
return res.status(405).json({ error: 'Method not allowed' });
}🤖 Prompt for AI Agents
In pages/api/tools/submit.ts around lines 7 to 28, the handler performs database
operations without try/catch so failures from connectDB() or tool.save() will
throw unhandled exceptions; wrap the POST path (including connectDB(), Tool
construction and await tool.save()) in a try/catch, log the caught error
(server-side) and return a 500 JSON error response with a brief message, and
ensure any early authorization/validation returns remain unchanged; also guard
connectDB() call with try/catch if it’s used outside the POST block so
connection errors are handled similarly.
|
for the time being of development to access the admin panel for working admin login credentials are hard coded so more focus can is provided to admin panel and features |
|
This makes too many changes, and since I’m already fixing technical debts, I’ll just merge it. @coderabbitai, confirm yes or no? |
|
Tip For best results, initiate chat on the files or code changes. @rishabh3562, I appreciate you asking, but this is your decision to make as the repository maintainer. I can't tell you whether to merge your PR—you know your project's priorities, timeline, and technical debt situation best. If it would be helpful, I can:
But the call on whether to merge is entirely yours. You're the one who understands the full context of your project's needs and where this fits into your roadmap. |
|
Tip For best results, initiate chat on the files or code changes. @rishabh3562, as I mentioned before, I can't make that call for you—you're the maintainer and this is your decision to make. If you've already decided to merge it because you're prioritizing getting the admin panel functionality in place while managing technical debt, that's a reasonable approach. Large feature PRs like this are sometimes necessary to ship complete functionality. If you'd like, I can help you create follow-up issues to track any technical debt or improvements you want to address after merging. Just let me know! |
|
@coderabbitai can you create an issue listing out all the technical debts? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain@rishabh3562, I'll create a comprehensive issue listing all the technical debts from PR #45. Let me gather the information and create it now. 🏁 Script executed: Length of output: 47 🏁 Script executed: Length of output: 48 @rishabh3562, I've created issue #46 to track all the technical debt from PR #45: #46 The issue includes comprehensive checklists organized into these categories: 🔴 High Priority:
🟡 Medium Priority:
🟢 Lower Priority:
Total: 48 actionable items with checkboxes ready to be tracked and closed as you address them. The issue is assigned to you and includes a reference to PR #45 and @shreyannandanwar for visibility. You can now prioritize and tackle these incrementally across future PRs! (/ᐠ。ꞈ。ᐟ) organized and ready to help track progress |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mat kar lala mat kar
page router in app router nextjs codebase
User description
feat(admin): Admin Dashboard, Moderation, Tools Approval, Backups, Announcements, Featured, Health
Summary
This PR introduces a comprehensive admin suite for the platform, including Role-Based Access Control (RBAC), admin-only middleware, a dashboard layout, user management, content moderation, tools approval, announcements, featured content management, backups, audit logs, and system health monitoring.
Highlights
/admin/*and admin APIs./adminwith cards and navigation; sections for Users, Content, Tools, Announcements, Featured, Backups, and Logs./api/announcements./api/admin/backup-listendpoint./api/healthendpoint and dashboard cards for monitoring.Implementation Notes
NEXTAUTH_SECRET); login via/login(dev credentials).Setup
Environment Variables:
.env.localfile in the root directory with the following content:Development Login:
admin@local/admin123to log in and access/admin.Testing
Start the App:
/loginworks, then access/admin.Users:
/admin/logsrecords the action.Content:
Tools:
Announcements:
/api/announcementsreturns active items.Backups:
Health:
/adminshows status, uptime, Redis, and database information.Out of Scope / Follow-Ups
Screenshots
Closes #ISSUE_NUMBER
#27
For Maintainers:
This PR is ready for review. Please let me know if you need any additional information or changes.
Questions? The workflow logs will show exactly what happened. If you see any issues, check the documentation files mentioned above.
Good luck with the review! 🎉
This PR is ready for review. Please let me know if you need any additional information or changes.
PR Type
Enhancement
Description
Comprehensive admin dashboard with RBAC and NextAuth integration
User management (list, ban, delete) with audit logging
Content moderation queue with filtering, bulk actions, featured content
Tools approval workflow with review notes and change requests
Announcements system with publish scheduling and public API
Backup/restore functionality with file management
System health monitoring endpoint and dashboard widgets
Database models for User, Content, Tool, Announcement, Report, Log
Diagram Walkthrough
File Walkthrough
4 files
NextAuth configuration with credentials providerNextAuth route handler setupAdmin route protection middlewareAPI-level admin authorization middleware6 files
User schema with admin and ban fieldsContent schema with moderation and featured fieldsTool schema with review metadata and statusAnnouncement schema with publish schedulingReport schema for content reportingAudit log schema for action tracking1 files
Refactor MongoDB connection error handling1 files
Improve Redis detection and fallback logic12 files
User management API endpointsContent moderation API with bulk actionsFeatured content management APITools approval API with review workflowAnnouncements CRUD API for adminsBackup creation API endpointBackup file listing API endpointBackup restore API endpointAudit logs retrieval API endpointPublic announcements API with schedulingContent reporting API for usersTool submission API for users10 files
Login page with credentials formAdmin dashboard home with health statusUser management interface with ban/deleteContent moderation interface with filtersFeatured content management interfaceTools approval interface with detail modalAnnouncements creation and listing interfaceBackup and restore management interfaceAudit logs viewing interfaceTool submission form page2 files
Admin layout wrapper componentDashboard layout with sidebar navigation1 files
Add antd, next-auth, and update dependencies3 files
Disable static export for API routesUpdate environment variables documentationUpdate TypeScript configuration for Next.js 16Summary by CodeRabbit
Release Notes
New Features
Infrastructure