feat: add admin user system with role-based access control
Implement comprehensive admin user system for Kubernetes deployment: Backend: - Add isAdmin field to User model for role-based permissions - Create adminAuth middleware to protect admin-only routes - Protect 11 routes across rewards, cache, streets, and analytics endpoints - Update setup-couchdb.js to seed default admin user at deployment Kubernetes: - Add ADMIN_EMAIL and ADMIN_PASSWORD to secrets.yaml - Add ADMIN_EMAIL to configmap.yaml for non-sensitive config - Create couchdb-init-job.yaml for automated database initialization - Update secrets.yaml.example with admin user documentation Frontend: - Create AdminRoute component for admin-only page protection - Create comprehensive AdminDashboard with 5 tabs: * Overview: Platform statistics and quick actions * Users: List, search, manage admin status, delete users * Streets: Create, edit, delete streets * Rewards: Create, edit, toggle, delete rewards * Content: Moderate posts and events - Add Admin navigation link in Navbar (visible only to admins) - Integrate admin routes in App.js Default admin user: - Email: will@wills-portal.com - Created automatically by K8s init job at deployment Routes protected: - POST/PUT/DELETE /api/rewards (catalog management) - POST /api/streets (street creation) - DELETE /api/cache (cache operations) - GET /api/analytics/* (platform statistics) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
const User = require("../models/User");
|
||||
|
||||
module.exports = async function (req, res, next) {
|
||||
try {
|
||||
const user = await User.findById(req.user.id);
|
||||
|
||||
if (!user || !user.isAdmin) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
msg: "Access denied. Admin privileges required."
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
console.error("Admin auth error:", err.message);
|
||||
return res.status(500).json({ success: false, msg: "Server error" });
|
||||
}
|
||||
};
|
||||
@@ -47,8 +47,9 @@ class User {
|
||||
|
||||
|
||||
// --- Gamification & App Data ---
|
||||
this.isPremium = data.isPremium || false;
|
||||
this.points = Math.max(0, data.points || 0);
|
||||
this.isPremium = data.isPremium || false;
|
||||
this.isAdmin = data.isAdmin || false;
|
||||
this.points = Math.max(0, data.points || 0);
|
||||
this.adoptedStreets = data.adoptedStreets || [];
|
||||
this.completedTasks = data.completedTasks || [];
|
||||
this.posts = data.posts || [];
|
||||
@@ -205,10 +206,11 @@ class User {
|
||||
location: this.location,
|
||||
website: this.website,
|
||||
social: this.social,
|
||||
privacySettings: this.privacySettings,
|
||||
preferences: this.preferences,
|
||||
isPremium: this.isPremium,
|
||||
points: this.points,
|
||||
privacySettings: this.privacySettings,
|
||||
preferences: this.preferences,
|
||||
isPremium: this.isPremium,
|
||||
isAdmin: this.isAdmin,
|
||||
points: this.points,
|
||||
adoptedStreets: this.adoptedStreets,
|
||||
completedTasks: this.completedTasks,
|
||||
posts: this.posts,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require("express");
|
||||
const auth = require("../middleware/auth");
|
||||
const adminAuth = require("../middleware/adminAuth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const { getCacheMiddleware, invalidateCacheByPattern } = require("../middleware/cache");
|
||||
const couchdbService = require("../services/couchdbService");
|
||||
@@ -77,6 +78,7 @@ const groupByTimePeriod = (data, groupBy = "day", dateField = "createdAt") => {
|
||||
router.get(
|
||||
"/overview",
|
||||
auth,
|
||||
adminAuth,
|
||||
getCacheMiddleware(300), // Cache for 5 minutes
|
||||
asyncHandler(async (req, res) => {
|
||||
const { timeframe = "all" } = req.query;
|
||||
@@ -249,6 +251,7 @@ router.get(
|
||||
router.get(
|
||||
"/activity",
|
||||
auth,
|
||||
adminAuth,
|
||||
getCacheMiddleware(300), // Cache for 5 minutes
|
||||
asyncHandler(async (req, res) => {
|
||||
const { timeframe = "30d", groupBy = "day" } = req.query;
|
||||
@@ -335,6 +338,7 @@ router.get(
|
||||
router.get(
|
||||
"/top-contributors",
|
||||
auth,
|
||||
adminAuth,
|
||||
getCacheMiddleware(300), // Cache for 5 minutes
|
||||
asyncHandler(async (req, res) => {
|
||||
const { limit = 10, timeframe = "all", metric = "points" } = req.query;
|
||||
@@ -472,6 +476,7 @@ router.get(
|
||||
router.get(
|
||||
"/street-stats",
|
||||
auth,
|
||||
adminAuth,
|
||||
getCacheMiddleware(300), // Cache for 5 minutes
|
||||
asyncHandler(async (req, res) => {
|
||||
const { timeframe = "all" } = req.query;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require("express");
|
||||
const auth = require("../middleware/auth");
|
||||
const adminAuth = require("../middleware/adminAuth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const { getCacheStats, clearCache } = require("../middleware/cache");
|
||||
|
||||
@@ -31,6 +32,7 @@ router.get(
|
||||
router.delete(
|
||||
"/",
|
||||
auth,
|
||||
adminAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
clearCache();
|
||||
res.json({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const express = require("express");
|
||||
const Reward = require("../models/Reward");
|
||||
const auth = require("../middleware/auth");
|
||||
const adminAuth = require("../middleware/adminAuth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const {
|
||||
createRewardValidation,
|
||||
@@ -28,6 +29,7 @@ router.get(
|
||||
router.post(
|
||||
"/",
|
||||
auth,
|
||||
adminAuth,
|
||||
createRewardValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { name, description, cost, isPremium } = req.body;
|
||||
@@ -102,6 +104,7 @@ router.get(
|
||||
router.put(
|
||||
"/:id",
|
||||
auth,
|
||||
adminAuth,
|
||||
rewardIdValidation,
|
||||
createRewardValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
@@ -126,6 +129,7 @@ router.put(
|
||||
router.delete(
|
||||
"/:id",
|
||||
auth,
|
||||
adminAuth,
|
||||
rewardIdValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const reward = await Reward.findById(req.params.id);
|
||||
@@ -229,6 +233,7 @@ router.get(
|
||||
router.patch(
|
||||
"/:id/toggle",
|
||||
auth,
|
||||
adminAuth,
|
||||
rewardIdValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const updatedReward = await Reward.toggleActiveStatus(req.params.id);
|
||||
@@ -240,6 +245,7 @@ router.patch(
|
||||
router.post(
|
||||
"/bulk",
|
||||
auth,
|
||||
adminAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { rewards } = req.body;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ const Street = require("../models/Street");
|
||||
const User = require("../models/User");
|
||||
const couchdbService = require("../services/couchdbService");
|
||||
const auth = require("../middleware/auth");
|
||||
const adminAuth = require("../middleware/adminAuth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const {
|
||||
createStreetValidation,
|
||||
@@ -103,6 +104,7 @@ router.get(
|
||||
router.post(
|
||||
"/",
|
||||
auth,
|
||||
adminAuth,
|
||||
createStreetValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { name, location } = req.body;
|
||||
|
||||
Reference in New Issue
Block a user