From 99ed8fea549eb23ef2e94471921a551f0ffcdb78 Mon Sep 17 00:00:00 2001 From: magdo Date: Mon, 27 Oct 2025 20:35:07 +0100 Subject: [PATCH] "activate user admin" --- .../src/Api/routers/adminRouter.ts | 70 +++++++++++++------ .../src/Application/Services/DIContainer.ts | 9 +++ .../User/commands/ActivateUserCommand.ts | 3 + .../commands/ActivateUserCommandHandler.ts | 12 ++++ .../src/Domain/IRepository/IUserRepository.ts | 1 + .../Repository/UserRepository.ts | 20 ++++++ 6 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 SerpentRace_Backend/src/Application/User/commands/ActivateUserCommand.ts create mode 100644 SerpentRace_Backend/src/Application/User/commands/ActivateUserCommandHandler.ts diff --git a/SerpentRace_Backend/src/Api/routers/adminRouter.ts b/SerpentRace_Backend/src/Api/routers/adminRouter.ts index f413e203..46a3861d 100644 --- a/SerpentRace_Backend/src/Api/routers/adminRouter.ts +++ b/SerpentRace_Backend/src/Api/routers/adminRouter.ts @@ -141,32 +141,32 @@ router.get('/users/:userId', }); // Search users including soft-deleted ones -// router.get('/users/search/:searchTerm', -// adminRequired, -// ValidationMiddleware.validateStringLength({ searchTerm: { min: 2, max: 100 } }), -// async (req: Request, res: Response) => { -// try { -// const { searchTerm } = req.params; -// const includeDeleted = req.query.includeDeleted === 'true'; +router.get('/users/search/:searchTerm', + adminRequired, + ValidationMiddleware.validateStringLength({ searchTerm: { min: 2, max: 100 } }), + async (req: Request, res: Response) => { + try { + const { searchTerm } = req.params; + const includeDeleted = req.query.includeDeleted === 'true'; -// logRequest('Admin search users endpoint accessed', req, res, { searchTerm, includeDeleted }); + logRequest('Admin search users endpoint accessed', req, res, { searchTerm, includeDeleted }); -// const users = includeDeleted -// ? await container.userRepository.searchIncludingDeleted(searchTerm) -// : await container.userRepository.search(searchTerm); + const users = includeDeleted + ? await container.userRepository.searchIncludingDeleted(searchTerm) + : await container.userRepository.search(searchTerm); -// logRequest('Admin user search completed', req, res, { -// searchTerm, -// resultCount: Array.isArray(users) ? users.length : (users.totalCount || 0), -// includeDeleted -// }); + logRequest('Admin user search completed', req, res, { + searchTerm, + resultCount: Array.isArray(users) ? users.length : (users.totalCount || 0), + includeDeleted + }); -// res.json(users); -// } catch (error) { -// logError('Admin search users endpoint error', error as Error, req, res); -// res.status(500).json({ error: 'Internal server error' }); -// } -// }); + res.json(users); + } catch (error) { + logError('Admin search users endpoint error', error as Error, req, res); + res.status(500).json({ error: 'Internal server error' }); + } +}); // Update any user (admin only) router.patch('/users/:userId', @@ -213,6 +213,32 @@ router.patch('/users/:userId', } }); +// Activate user (admin only) +router.post('/users/:userId/activate', + adminRequired, + ValidationMiddleware.validateUUIDFormat(['userId']), + async (req: Request, res: Response) => { + try { + const targetUserId = req.params.userId; + const adminUserId = (req as any).user.userId; + + logRequest('Admin activate user endpoint accessed', req, res, { adminUserId, targetUserId }); + + const result = await container.activateUserCommandHandler.execute({ id: targetUserId }); + + if (!result) { + return res.status(404).json({ error: 'User not found' }); + } + + logAuth('User activated by admin', targetUserId, { adminUserId }, req, res); + res.json({ message: 'User activated successfully', user: result }); + + } catch (error) { + logError('Admin activate user endpoint error', error as Error, req, res); + res.status(500).json({ error: 'Internal server error' }); + } +}); + // Deactivate user (admin only) router.post('/users/:userId/deactivate', adminRequired, diff --git a/SerpentRace_Backend/src/Application/Services/DIContainer.ts b/SerpentRace_Backend/src/Application/Services/DIContainer.ts index 7c6af860..d4a5fd36 100644 --- a/SerpentRace_Backend/src/Application/Services/DIContainer.ts +++ b/SerpentRace_Backend/src/Application/Services/DIContainer.ts @@ -39,6 +39,7 @@ import { ProcessOrgAuthCallbackCommandHandler } from '../Organization/commands/P import { CreateContactCommandHandler } from '../Contact/commands/CreateContactCommandHandler'; import { UpdateContactCommandHandler } from '../Contact/commands/UpdateContactCommandHandler'; import { DeleteContactCommandHandler } from '../Contact/commands/DeleteContactCommandHandler'; +import { ActivateUserCommandHandler } from '../User/commands/ActivateUserCommandHandler'; // Query Handlers import { GetUserByIdQueryHandler } from '../User/queries/GetUserByIdQueryHandler'; @@ -121,6 +122,7 @@ export class DIContainer { private _updateContactCommandHandler: UpdateContactCommandHandler | null = null; private _deleteContactCommandHandler: DeleteContactCommandHandler | null = null; private _generateBoardCommandHandler: GenerateBoardCommandHandler | null = null; + private _activateUserCommandHandler: ActivateUserCommandHandler | null = null; // Query Handlers private _getUserByIdQueryHandler: GetUserByIdQueryHandler | null = null; @@ -306,6 +308,13 @@ export class DIContainer { return this._deactivateUserCommandHandler; } + public get activateUserCommandHandler(): ActivateUserCommandHandler { + if (!this._activateUserCommandHandler) { + this._activateUserCommandHandler = new ActivateUserCommandHandler(this.userRepository); + } + return this._activateUserCommandHandler; + } + public get deleteUserCommandHandler(): DeleteUserCommandHandler { if (!this._deleteUserCommandHandler) { this._deleteUserCommandHandler = new DeleteUserCommandHandler(this.userRepository); diff --git a/SerpentRace_Backend/src/Application/User/commands/ActivateUserCommand.ts b/SerpentRace_Backend/src/Application/User/commands/ActivateUserCommand.ts new file mode 100644 index 00000000..0cad88e6 --- /dev/null +++ b/SerpentRace_Backend/src/Application/User/commands/ActivateUserCommand.ts @@ -0,0 +1,3 @@ +export interface ActivateUserCommand { + id: string; +} diff --git a/SerpentRace_Backend/src/Application/User/commands/ActivateUserCommandHandler.ts b/SerpentRace_Backend/src/Application/User/commands/ActivateUserCommandHandler.ts new file mode 100644 index 00000000..b4a22ab0 --- /dev/null +++ b/SerpentRace_Backend/src/Application/User/commands/ActivateUserCommandHandler.ts @@ -0,0 +1,12 @@ +import { IUserRepository } from '../../../Domain/IRepository/IUserRepository'; +import { ActivateUserCommand } from './ActivateUserCommand'; + + +export class ActivateUserCommandHandler { + constructor(private readonly userRepo: IUserRepository) {} + + async execute(cmd: ActivateUserCommand): Promise { + await this.userRepo.activate(cmd.id); + return true; + } +} \ No newline at end of file diff --git a/SerpentRace_Backend/src/Domain/IRepository/IUserRepository.ts b/SerpentRace_Backend/src/Domain/IRepository/IUserRepository.ts index cf64f295..4db923b0 100644 --- a/SerpentRace_Backend/src/Domain/IRepository/IUserRepository.ts +++ b/SerpentRace_Backend/src/Domain/IRepository/IUserRepository.ts @@ -7,4 +7,5 @@ export interface IUserRepository extends IPaginatedRepository; findByToken(token: string): Promise; deactivate(id: string): Promise; + activate(id: string): Promise; } diff --git a/SerpentRace_Backend/src/Infrastructure/Repository/UserRepository.ts b/SerpentRace_Backend/src/Infrastructure/Repository/UserRepository.ts index 3563e993..40d772ae 100644 --- a/SerpentRace_Backend/src/Infrastructure/Repository/UserRepository.ts +++ b/SerpentRace_Backend/src/Infrastructure/Repository/UserRepository.ts @@ -345,5 +345,25 @@ export class UserRepository implements IUserRepository { } } + async activate(id: string) { + const startTime = Date.now(); + try { + await this.repo.update(id, { state: UserState.VERIFIED_REGULAR }); + const result = await this.findById(id); + logDatabase('User activated successfully', `update(${id}, { state: VERIFIED_REGULAR })`, Date.now() - startTime, { + userId: id, + success: !!result + }); + return result; + } + catch (error) { + logError('UserRepository.activate error', error as Error); + // Handle invalid UUID format + if (error instanceof Error && error.message.includes('invalid input syntax for type uuid')) { + throw new Error('Invalid user ID format'); + } + throw new Error('Failed to activate user in database'); + } + } }