Files
motovaultpro/backend/src/features/admin/api/admin.routes.ts
Eric Gullickson 4fc5b391e1
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
feat: Add admin vehicle management and profile vehicles display (refs #11)
- 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>
2026-01-04 13:18:38 -06:00

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 });
};