All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m34s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
- Add GET /api/admin/stats endpoint for Total Vehicles widget - Add GET /api/admin/users/:auth0Sub/vehicles endpoint for user vehicle list - Update AdminUsersPage with Total Vehicles stat and expandable vehicle rows - Add My Vehicles section to SettingsPage (desktop) and MobileSettingsScreen - Update AdminUsersMobileScreen with stats header and vehicle expansion - Add defense-in-depth admin checks and error handling - Update admin README documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
360 lines
13 KiB
TypeScript
360 lines
13 KiB
TypeScript
/**
|
|
* @ai-summary Admin feature routes
|
|
* @ai-context Registers admin API endpoints with proper guards
|
|
*/
|
|
|
|
import { FastifyPluginAsync } from 'fastify';
|
|
import { AdminController } from './admin.controller';
|
|
import { UsersController } from './users.controller';
|
|
import {
|
|
CreateAdminInput,
|
|
AdminAuth0SubInput,
|
|
AuditLogsQueryInput,
|
|
BulkCreateAdminInput,
|
|
BulkRevokeAdminInput,
|
|
BulkReinstateAdminInput,
|
|
BulkDeleteCatalogInput,
|
|
CatalogEntity
|
|
} from './admin.validation';
|
|
import {
|
|
ListUsersQueryInput,
|
|
UserAuth0SubInput,
|
|
UpdateTierInput,
|
|
DeactivateUserInput,
|
|
UpdateProfileInput,
|
|
PromoteToAdminInput,
|
|
} from './users.validation';
|
|
import { CatalogController } from './catalog.controller';
|
|
import { VehicleCatalogService } from '../domain/vehicle-catalog.service';
|
|
import { CatalogImportService } from '../domain/catalog-import.service';
|
|
import { PlatformCacheService } from '../../platform/domain/platform-cache.service';
|
|
import { cacheService } from '../../../core/config/redis';
|
|
import { pool } from '../../../core/config/database';
|
|
import { CommunityStationsController } from '../../stations/api/community-stations.controller';
|
|
import { registerBackupRoutes } from '../../backup/api/backup.routes';
|
|
|
|
export const adminRoutes: FastifyPluginAsync = async (fastify) => {
|
|
const adminController = new AdminController();
|
|
const usersController = new UsersController();
|
|
|
|
// Initialize community stations dependencies
|
|
const communityStationsController = new CommunityStationsController();
|
|
|
|
// Initialize catalog dependencies
|
|
const platformCacheService = new PlatformCacheService(cacheService);
|
|
const catalogService = new VehicleCatalogService(pool, platformCacheService);
|
|
const catalogImportService = new CatalogImportService(pool, platformCacheService);
|
|
const catalogController = new CatalogController(catalogService);
|
|
catalogController.setImportService(catalogImportService);
|
|
|
|
// Admin access verification (used by frontend auth checks)
|
|
fastify.get('/admin/verify', {
|
|
preHandler: [fastify.authenticate] // Requires JWT, does NOT require admin role
|
|
}, adminController.verifyAccess.bind(adminController));
|
|
|
|
// Phase 2: Admin management endpoints
|
|
|
|
// GET /api/admin/admins - List all admin users
|
|
fastify.get('/admin/admins', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.listAdmins.bind(adminController)
|
|
});
|
|
|
|
// POST /api/admin/admins - Create new admin
|
|
fastify.post<{ Body: CreateAdminInput }>('/admin/admins', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.createAdmin.bind(adminController)
|
|
});
|
|
|
|
// PATCH /api/admin/admins/:auth0Sub/revoke - Revoke admin access
|
|
fastify.patch<{ Params: AdminAuth0SubInput }>('/admin/admins/:auth0Sub/revoke', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.revokeAdmin.bind(adminController)
|
|
});
|
|
|
|
// PATCH /api/admin/admins/:auth0Sub/reinstate - Restore revoked admin
|
|
fastify.patch<{ Params: AdminAuth0SubInput }>('/admin/admins/:auth0Sub/reinstate', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.reinstateAdmin.bind(adminController)
|
|
});
|
|
|
|
// GET /api/admin/audit-logs - Fetch audit trail
|
|
fastify.get<{ Querystring: AuditLogsQueryInput }>('/admin/audit-logs', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.getAuditLogs.bind(adminController)
|
|
});
|
|
|
|
// POST /api/admin/admins/bulk - Create multiple admins
|
|
fastify.post<{ Body: BulkCreateAdminInput }>('/admin/admins/bulk', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.bulkCreateAdmins.bind(adminController)
|
|
});
|
|
|
|
// PATCH /api/admin/admins/bulk-revoke - Revoke multiple admins
|
|
fastify.patch<{ Body: BulkRevokeAdminInput }>('/admin/admins/bulk-revoke', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.bulkRevokeAdmins.bind(adminController)
|
|
});
|
|
|
|
// PATCH /api/admin/admins/bulk-reinstate - Reinstate multiple admins
|
|
fastify.patch<{ Body: BulkReinstateAdminInput }>('/admin/admins/bulk-reinstate', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: adminController.bulkReinstateAdmins.bind(adminController)
|
|
});
|
|
|
|
// ============================================
|
|
// Admin Stats endpoint (dashboard widgets)
|
|
// ============================================
|
|
|
|
// GET /api/admin/stats - Get admin dashboard stats (total users, total vehicles)
|
|
fastify.get('/admin/stats', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.getAdminStats.bind(usersController)
|
|
});
|
|
|
|
// ============================================
|
|
// User Management endpoints (subscription tiers, deactivation)
|
|
// ============================================
|
|
|
|
// GET /api/admin/users - List all users with pagination and filters
|
|
fastify.get<{ Querystring: ListUsersQueryInput }>('/admin/users', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.listUsers.bind(usersController)
|
|
});
|
|
|
|
// GET /api/admin/users/:auth0Sub - Get single user details
|
|
fastify.get<{ Params: UserAuth0SubInput }>('/admin/users/:auth0Sub', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.getUser.bind(usersController)
|
|
});
|
|
|
|
// GET /api/admin/users/:auth0Sub/vehicles - Get user's vehicles (admin view)
|
|
fastify.get<{ Params: UserAuth0SubInput }>('/admin/users/:auth0Sub/vehicles', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.getUserVehicles.bind(usersController)
|
|
});
|
|
|
|
// PATCH /api/admin/users/:auth0Sub/tier - Update subscription tier
|
|
fastify.patch<{ Params: UserAuth0SubInput; Body: UpdateTierInput }>('/admin/users/:auth0Sub/tier', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.updateTier.bind(usersController)
|
|
});
|
|
|
|
// PATCH /api/admin/users/:auth0Sub/deactivate - Soft delete user
|
|
fastify.patch<{ Params: UserAuth0SubInput; Body: DeactivateUserInput }>('/admin/users/:auth0Sub/deactivate', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.deactivateUser.bind(usersController)
|
|
});
|
|
|
|
// PATCH /api/admin/users/:auth0Sub/reactivate - Restore deactivated user
|
|
fastify.patch<{ Params: UserAuth0SubInput }>('/admin/users/:auth0Sub/reactivate', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.reactivateUser.bind(usersController)
|
|
});
|
|
|
|
// PATCH /api/admin/users/:auth0Sub/profile - Update user email/displayName
|
|
fastify.patch<{ Params: UserAuth0SubInput; Body: UpdateProfileInput }>('/admin/users/:auth0Sub/profile', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.updateProfile.bind(usersController)
|
|
});
|
|
|
|
// PATCH /api/admin/users/:auth0Sub/promote - Promote user to admin
|
|
fastify.patch<{ Params: UserAuth0SubInput; Body: PromoteToAdminInput }>('/admin/users/:auth0Sub/promote', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.promoteToAdmin.bind(usersController)
|
|
});
|
|
|
|
// DELETE /api/admin/users/:auth0Sub - Hard delete user (permanent)
|
|
fastify.delete<{ Params: UserAuth0SubInput }>('/admin/users/:auth0Sub', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: usersController.hardDeleteUser.bind(usersController)
|
|
});
|
|
|
|
// Phase 3: Catalog CRUD endpoints
|
|
|
|
// Makes endpoints
|
|
fastify.get('/admin/catalog/makes', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.getMakes.bind(catalogController)
|
|
});
|
|
|
|
fastify.post('/admin/catalog/makes', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.createMake.bind(catalogController)
|
|
});
|
|
|
|
fastify.put('/admin/catalog/makes/:makeId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.updateMake.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/makes/:makeId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteMake.bind(catalogController)
|
|
});
|
|
|
|
// Models endpoints
|
|
fastify.get('/admin/catalog/makes/:makeId/models', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.getModels.bind(catalogController)
|
|
});
|
|
|
|
fastify.post('/admin/catalog/models', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.createModel.bind(catalogController)
|
|
});
|
|
|
|
fastify.put('/admin/catalog/models/:modelId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.updateModel.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/models/:modelId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteModel.bind(catalogController)
|
|
});
|
|
|
|
// Years endpoints
|
|
fastify.get('/admin/catalog/models/:modelId/years', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.getYears.bind(catalogController)
|
|
});
|
|
|
|
fastify.post('/admin/catalog/years', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.createYear.bind(catalogController)
|
|
});
|
|
|
|
fastify.put('/admin/catalog/years/:yearId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.updateYear.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/years/:yearId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteYear.bind(catalogController)
|
|
});
|
|
|
|
// Trims endpoints
|
|
fastify.get('/admin/catalog/years/:yearId/trims', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.getTrims.bind(catalogController)
|
|
});
|
|
|
|
fastify.post('/admin/catalog/trims', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.createTrim.bind(catalogController)
|
|
});
|
|
|
|
fastify.put('/admin/catalog/trims/:trimId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.updateTrim.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/trims/:trimId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteTrim.bind(catalogController)
|
|
});
|
|
|
|
// Engines endpoints
|
|
fastify.get('/admin/catalog/trims/:trimId/engines', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.getEngines.bind(catalogController)
|
|
});
|
|
|
|
fastify.post('/admin/catalog/engines', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.createEngine.bind(catalogController)
|
|
});
|
|
|
|
fastify.put('/admin/catalog/engines/:engineId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.updateEngine.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/engines/:engineId', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteEngine.bind(catalogController)
|
|
});
|
|
|
|
// Change logs endpoint
|
|
fastify.get('/admin/catalog/change-logs', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.getChangeLogs.bind(catalogController)
|
|
});
|
|
|
|
// Search endpoint - full-text search across vehicle_options
|
|
fastify.get('/admin/catalog/search', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.searchCatalog.bind(catalogController)
|
|
});
|
|
|
|
// Cascade delete endpoints - delete entity and all its children
|
|
fastify.delete('/admin/catalog/makes/:makeId/cascade', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteMakeCascade.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/models/:modelId/cascade', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteModelCascade.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/years/:yearId/cascade', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteYearCascade.bind(catalogController)
|
|
});
|
|
|
|
fastify.delete('/admin/catalog/trims/:trimId/cascade', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.deleteTrimCascade.bind(catalogController)
|
|
});
|
|
|
|
// Import/Export endpoints
|
|
fastify.post('/admin/catalog/import/preview', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.importPreview.bind(catalogController)
|
|
});
|
|
|
|
fastify.post('/admin/catalog/import/apply', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.importApply.bind(catalogController)
|
|
});
|
|
|
|
fastify.get('/admin/catalog/export', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.exportCatalog.bind(catalogController)
|
|
});
|
|
|
|
// Bulk delete endpoint
|
|
fastify.delete<{ Params: { entity: CatalogEntity }; Body: BulkDeleteCatalogInput }>('/admin/catalog/:entity/bulk-delete', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: catalogController.bulkDeleteCatalogEntity.bind(catalogController)
|
|
});
|
|
|
|
// Community gas station submission oversight
|
|
|
|
// GET /api/admin/community-stations - List all submissions with filters
|
|
fastify.get('/admin/community-stations', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: communityStationsController.listAllSubmissions.bind(communityStationsController)
|
|
});
|
|
|
|
// GET /api/admin/community-stations/pending - Get pending review queue
|
|
fastify.get('/admin/community-stations/pending', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: communityStationsController.getPendingQueue.bind(communityStationsController)
|
|
});
|
|
|
|
// PATCH /api/admin/community-stations/:id/review - Approve or reject submission
|
|
fastify.patch('/admin/community-stations/:id/review', {
|
|
preHandler: [fastify.requireAdmin],
|
|
handler: communityStationsController.reviewStation.bind(communityStationsController)
|
|
});
|
|
|
|
// ============================================
|
|
// Backup & Restore endpoints
|
|
// ============================================
|
|
await registerBackupRoutes(fastify, { pool });
|
|
};
|