chore: refactor admin system for UUID identity (refs #213)
Migrate admin controller, routes, validation, and users controller from auth0Sub identifiers to UUID. Admin CRUD now uses admin UUID id, user management routes use user_profiles UUID. Clean up debug logging. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,13 +14,13 @@ import { pool } from '../../../core/config/database';
|
||||
import { logger } from '../../../core/logging/logger';
|
||||
import {
|
||||
listUsersQuerySchema,
|
||||
userAuth0SubSchema,
|
||||
userIdSchema,
|
||||
updateTierSchema,
|
||||
deactivateUserSchema,
|
||||
updateProfileSchema,
|
||||
promoteToAdminSchema,
|
||||
ListUsersQueryInput,
|
||||
UserAuth0SubInput,
|
||||
UserIdInput,
|
||||
UpdateTierInput,
|
||||
DeactivateUserInput,
|
||||
UpdateProfileInput,
|
||||
@@ -95,10 +95,10 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/admin/users/:auth0Sub/vehicles - Get user's vehicles (admin view)
|
||||
* GET /api/admin/users/:userId/vehicles - Get user's vehicles (admin view)
|
||||
*/
|
||||
async getUserVehicles(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -119,7 +119,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const parseResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const parseResult = userIdSchema.safeParse(request.params);
|
||||
if (!parseResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -127,14 +127,14 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = parseResult.data;
|
||||
const vehicles = await this.userProfileRepository.getUserVehiclesForAdmin(auth0Sub);
|
||||
const { userId } = parseResult.data;
|
||||
const vehicles = await this.userProfileRepository.getUserVehiclesForAdmin(userId);
|
||||
|
||||
return reply.code(200).send({ vehicles });
|
||||
} catch (error) {
|
||||
logger.error('Error getting user vehicles', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
return reply.code(500).send({
|
||||
@@ -186,10 +186,10 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/admin/users/:auth0Sub - Get single user details
|
||||
* GET /api/admin/users/:userId - Get single user details
|
||||
*/
|
||||
async getUser(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -202,7 +202,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const parseResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const parseResult = userIdSchema.safeParse(request.params);
|
||||
if (!parseResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -210,8 +210,8 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = parseResult.data;
|
||||
const user = await this.userProfileService.getUserDetails(auth0Sub);
|
||||
const { userId } = parseResult.data;
|
||||
const user = await this.userProfileService.getUserDetails(userId);
|
||||
|
||||
if (!user) {
|
||||
return reply.code(404).send({
|
||||
@@ -224,7 +224,7 @@ export class UsersController {
|
||||
} catch (error) {
|
||||
logger.error('Error getting user details', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
return reply.code(500).send({
|
||||
@@ -235,12 +235,12 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/admin/users/:auth0Sub/tier - Update subscription tier
|
||||
* PATCH /api/admin/users/:userId/tier - Update subscription tier
|
||||
* Uses subscriptionsService.adminOverrideTier() to sync both subscriptions.tier
|
||||
* and user_profiles.subscription_tier atomically
|
||||
*/
|
||||
async updateTier(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput; Body: UpdateTierInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput; Body: UpdateTierInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -253,7 +253,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const paramsResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const paramsResult = userIdSchema.safeParse(request.params);
|
||||
if (!paramsResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -270,11 +270,11 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = paramsResult.data;
|
||||
const { userId } = paramsResult.data;
|
||||
const { subscriptionTier } = bodyResult.data;
|
||||
|
||||
// Verify user exists before attempting tier change
|
||||
const currentUser = await this.userProfileService.getUserDetails(auth0Sub);
|
||||
const currentUser = await this.userProfileService.getUserDetails(userId);
|
||||
if (!currentUser) {
|
||||
return reply.code(404).send({
|
||||
error: 'Not found',
|
||||
@@ -285,34 +285,34 @@ export class UsersController {
|
||||
const previousTier = currentUser.subscriptionTier;
|
||||
|
||||
// Use subscriptionsService to update both tables atomically
|
||||
await this.subscriptionsService.adminOverrideTier(auth0Sub, subscriptionTier);
|
||||
await this.subscriptionsService.adminOverrideTier(userId, subscriptionTier);
|
||||
|
||||
// Log audit action
|
||||
await this.adminRepository.logAuditAction(
|
||||
actorId,
|
||||
'UPDATE_TIER',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
currentUser.id,
|
||||
{ previousTier, newTier: subscriptionTier }
|
||||
);
|
||||
|
||||
logger.info('User subscription tier updated via admin', {
|
||||
auth0Sub,
|
||||
userId,
|
||||
previousTier,
|
||||
newTier: subscriptionTier,
|
||||
actorAuth0Sub: actorId,
|
||||
actorId,
|
||||
});
|
||||
|
||||
// Return updated user profile
|
||||
const updatedUser = await this.userProfileService.getUserDetails(auth0Sub);
|
||||
const updatedUser = await this.userProfileService.getUserDetails(userId);
|
||||
return reply.code(200).send(updatedUser);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
|
||||
logger.error('Error updating user tier', {
|
||||
error: errorMessage,
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
if (errorMessage === 'User not found') {
|
||||
@@ -330,10 +330,10 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/admin/users/:auth0Sub/deactivate - Soft delete user
|
||||
* PATCH /api/admin/users/:userId/deactivate - Soft delete user
|
||||
*/
|
||||
async deactivateUser(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput; Body: DeactivateUserInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput; Body: DeactivateUserInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -346,7 +346,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const paramsResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const paramsResult = userIdSchema.safeParse(request.params);
|
||||
if (!paramsResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -363,11 +363,11 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = paramsResult.data;
|
||||
const { userId } = paramsResult.data;
|
||||
const { reason } = bodyResult.data;
|
||||
|
||||
const deactivatedUser = await this.userProfileService.deactivateUser(
|
||||
auth0Sub,
|
||||
userId,
|
||||
actorId,
|
||||
reason
|
||||
);
|
||||
@@ -378,7 +378,7 @@ export class UsersController {
|
||||
|
||||
logger.error('Error deactivating user', {
|
||||
error: errorMessage,
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
if (errorMessage === 'User not found') {
|
||||
@@ -410,10 +410,10 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/admin/users/:auth0Sub/reactivate - Restore deactivated user
|
||||
* PATCH /api/admin/users/:userId/reactivate - Restore deactivated user
|
||||
*/
|
||||
async reactivateUser(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -426,7 +426,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const paramsResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const paramsResult = userIdSchema.safeParse(request.params);
|
||||
if (!paramsResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -434,10 +434,10 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = paramsResult.data;
|
||||
const { userId } = paramsResult.data;
|
||||
|
||||
const reactivatedUser = await this.userProfileService.reactivateUser(
|
||||
auth0Sub,
|
||||
userId,
|
||||
actorId
|
||||
);
|
||||
|
||||
@@ -447,7 +447,7 @@ export class UsersController {
|
||||
|
||||
logger.error('Error reactivating user', {
|
||||
error: errorMessage,
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
if (errorMessage === 'User not found') {
|
||||
@@ -472,10 +472,10 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/admin/users/:auth0Sub/profile - Update user email/displayName
|
||||
* PATCH /api/admin/users/:userId/profile - Update user email/displayName
|
||||
*/
|
||||
async updateProfile(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput; Body: UpdateProfileInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput; Body: UpdateProfileInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -488,7 +488,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const paramsResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const paramsResult = userIdSchema.safeParse(request.params);
|
||||
if (!paramsResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -505,11 +505,11 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = paramsResult.data;
|
||||
const { userId } = paramsResult.data;
|
||||
const updates = bodyResult.data;
|
||||
|
||||
const updatedUser = await this.userProfileService.adminUpdateProfile(
|
||||
auth0Sub,
|
||||
userId,
|
||||
updates,
|
||||
actorId
|
||||
);
|
||||
@@ -520,7 +520,7 @@ export class UsersController {
|
||||
|
||||
logger.error('Error updating user profile', {
|
||||
error: errorMessage,
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
if (errorMessage === 'User not found') {
|
||||
@@ -538,10 +538,10 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/admin/users/:auth0Sub/promote - Promote user to admin
|
||||
* PATCH /api/admin/users/:userId/promote - Promote user to admin
|
||||
*/
|
||||
async promoteToAdmin(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput; Body: PromoteToAdminInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput; Body: PromoteToAdminInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -554,7 +554,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const paramsResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const paramsResult = userIdSchema.safeParse(request.params);
|
||||
if (!paramsResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -571,11 +571,11 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = paramsResult.data;
|
||||
const { userId } = paramsResult.data;
|
||||
const { role } = bodyResult.data;
|
||||
|
||||
// Get the user profile first to verify they exist and get their email
|
||||
const user = await this.userProfileService.getUserDetails(auth0Sub);
|
||||
// Get the user profile to verify they exist and get their email
|
||||
const user = await this.userProfileService.getUserDetails(userId);
|
||||
if (!user) {
|
||||
return reply.code(404).send({
|
||||
error: 'Not found',
|
||||
@@ -591,12 +591,15 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
// Create the admin record using the user's real auth0Sub
|
||||
// Get actor's admin record for audit trail
|
||||
const actorAdmin = await this.adminService.getAdminByUserProfileId(actorId);
|
||||
|
||||
// Create the admin record using the user's UUID
|
||||
const adminUser = await this.adminService.createAdmin(
|
||||
user.email,
|
||||
role,
|
||||
auth0Sub, // Use the real auth0Sub from the user profile
|
||||
actorId
|
||||
userId,
|
||||
actorAdmin?.id || actorId
|
||||
);
|
||||
|
||||
return reply.code(201).send(adminUser);
|
||||
@@ -605,7 +608,7 @@ export class UsersController {
|
||||
|
||||
logger.error('Error promoting user to admin', {
|
||||
error: errorMessage,
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
if (errorMessage.includes('already exists')) {
|
||||
@@ -623,10 +626,10 @@ export class UsersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/users/:auth0Sub - Hard delete user (permanent)
|
||||
* DELETE /api/admin/users/:userId - Hard delete user (permanent)
|
||||
*/
|
||||
async hardDeleteUser(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput }>,
|
||||
request: FastifyRequest<{ Params: UserIdInput }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
@@ -639,7 +642,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
// Validate path param
|
||||
const paramsResult = userAuth0SubSchema.safeParse(request.params);
|
||||
const paramsResult = userIdSchema.safeParse(request.params);
|
||||
if (!paramsResult.success) {
|
||||
return reply.code(400).send({
|
||||
error: 'Validation error',
|
||||
@@ -647,14 +650,14 @@ export class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
const { auth0Sub } = paramsResult.data;
|
||||
const { userId } = paramsResult.data;
|
||||
|
||||
// Optional reason from query params
|
||||
const reason = (request.query as any)?.reason;
|
||||
|
||||
// Hard delete user
|
||||
await this.userProfileService.adminHardDeleteUser(
|
||||
auth0Sub,
|
||||
userId,
|
||||
actorId,
|
||||
reason
|
||||
);
|
||||
@@ -667,7 +670,7 @@ export class UsersController {
|
||||
|
||||
logger.error('Error hard deleting user', {
|
||||
error: errorMessage,
|
||||
auth0Sub: request.params?.auth0Sub,
|
||||
userId: (request.params as any)?.userId,
|
||||
});
|
||||
|
||||
if (errorMessage === 'Cannot delete your own account') {
|
||||
|
||||
Reference in New Issue
Block a user