From c154ef18971f4244c93abacab2562eacb942771f Mon Sep 17 00:00:00 2001 From: Hikari Date: Thu, 19 Feb 2026 20:05:57 -0800 Subject: [PATCH] feat: add admin profile editing capability Added admin endpoint and functionality for editing user profiles from the report management interface: Backend Changes: - Added PUT /api/users/:id endpoint for admin-only profile editing - Reuses existing updateUserSettings logic with slug uniqueness validation - Protected with adminGuard and csrfProtection - Proper audit logging for admin profile edits Frontend Changes: - Added adminUpdateUser() method to UserService - Updated editProfile() in admin-reports to load profile and allow bio editing - Uses prompt for quick bio editing (can be expanded to full form later) - Shows success/error toasts and refreshes report list after edit Admins can now quickly edit reported user profiles directly from the report management interface. --- api/src/app/routes/users/index.ts | 34 +++++++++++++++++++ .../admin-reports/admin-reports.component.ts | 26 ++++++++++++-- .../frontend/src/app/services/user.service.ts | 4 +++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/api/src/app/routes/users/index.ts b/api/src/app/routes/users/index.ts index 67e9c43..c7283bc 100644 --- a/api/src/app/routes/users/index.ts +++ b/api/src/app/routes/users/index.ts @@ -255,6 +255,40 @@ const usersRoutes: FastifyPluginAsync = async (app) => { return user; } ); + + app.put<{ Params: { id: string }; Body: UpdateUserSettingsBody; Reply: User | { error: string } }>( + "/:id", + { + preValidation: [app.authenticate, adminGuard], + preHandler: [app.csrfProtection], + }, + async (request, reply) => { + const { id } = request.params; + const updates = request.body; + + // If slug is being updated, check if it's unique + if (updates.slug) { + const existingUser = await userService.getUserBySlug(updates.slug); + if (existingUser && existingUser.id !== id) { + return reply.code(400).send({ error: "Slug already taken" }); + } + } + + const updatedUser = await userService.updateUserSettings(id, updates); + if (!updatedUser) { + return reply.code(404).send({ error: "User not found" }); + } + + await AuditService.logFromRequest(request, { + action: AuditAction.entryUpdate, + category: AuditCategory.admin, + targetUserId: id, + details: `Admin updated profile for user: ${updatedUser.username}`, + }); + + return updatedUser; + } + ); }; export default usersRoutes; diff --git a/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts b/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts index 2f1469a..73ca422 100644 --- a/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts +++ b/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts @@ -1529,10 +1529,30 @@ export class AdminReportsComponent implements OnInit { editProfile(report: ProfileReportWithUsers): void { const userId = report.reportedUser.id; + const username = report.reportedUser.username; - // Navigate to profile page using ObjectId - this.router.navigate(['/profile', userId]); - this.toastService.success('Navigate to profile to edit'); + // Load the full profile first to get current values + this.userService.getProfile(userId).subscribe({ + next: (profile) => { + const newBio = prompt(`Edit bio for ${username}:`, profile.bio || ''); + + if (newBio !== null) { + this.userService.adminUpdateUser(userId, { bio: newBio }).subscribe({ + next: () => { + this.toastService.success('Profile updated successfully'); + this.loadReports(); + this.closeProfileReviewModal(); + }, + error: (err) => { + this.toastService.error(err.message ?? 'Failed to update profile'); + } + }); + } + }, + error: (err) => { + this.toastService.error(err.message ?? 'Failed to load profile'); + } + }); } makeProfilePrivate(report: ProfileReportWithUsers): void { diff --git a/apps/frontend/src/app/services/user.service.ts b/apps/frontend/src/app/services/user.service.ts index 93c6ff3..45f67d9 100644 --- a/apps/frontend/src/app/services/user.service.ts +++ b/apps/frontend/src/app/services/user.service.ts @@ -85,4 +85,8 @@ export class UserService { makeProfilePrivate(userId: string): Observable { return this.api.post(`/users/${userId}/make-private`, {}); } + + adminUpdateUser(userId: string, settings: UpdateUserSettingsRequest): Observable { + return this.api.put(`/users/${userId}`, settings); + } }