From 24554615e5bcdb5af7692b0aa38f694d3b3c9c87 Mon Sep 17 00:00:00 2001 From: Yonatan Hen Date: Fri, 31 Jul 2026 17:54:27 +0300 Subject: [PATCH 1/4] feat(users): self-service account management and public profiles Extends the already-shipped PATCH/DELETE /api/v1/users/:id API with username and email editing, current-password verification before a password change, OAuth accounts setting an initial password, and cascade-deleting a user's posts/comments/likes on account deletion. Deletion requires re-entering the password (or, for OAuth-only accounts, typing the username) as confirmation. Adds the client side that never existed for this API: /account (self, editable) and /users/:id (anyone, read-only), reached by making every username byline in the app clickable. --- apps/client/src/api/users.ts | 21 ++- .../src/components/layouts/PageShell.tsx | 4 +- .../src/components/patterns/ChatRoom.test.tsx | 9 +- .../src/components/patterns/ChatRoom.tsx | 6 +- .../patterns/CommentThread.test.tsx | 5 +- .../src/components/patterns/CommentThread.tsx | 6 +- .../src/components/patterns/PostCard.tsx | 4 +- apps/client/src/hooks/use-users.ts | 34 ++++ apps/client/src/pages/AccountPage.tsx | 114 ++++++++++++ apps/client/src/pages/PostPage.tsx | 4 +- apps/client/src/pages/UserProfilePage.tsx | 39 ++++ apps/client/src/routes.tsx | 4 + apps/server/src/lib/services/user.test.ts | 167 +++++++++++++++++- apps/server/src/lib/services/user.ts | 97 ++++++++-- apps/server/src/routes/v1/users.test.ts | 60 ++++++- apps/server/src/routes/v1/users.ts | 26 ++- packages/zod-shared/src/schemas/user.ts | 48 +++-- 17 files changed, 599 insertions(+), 49 deletions(-) create mode 100644 apps/client/src/hooks/use-users.ts create mode 100644 apps/client/src/pages/AccountPage.tsx create mode 100644 apps/client/src/pages/UserProfilePage.tsx diff --git a/apps/client/src/api/users.ts b/apps/client/src/api/users.ts index 8333248e0..9f70ca67a 100644 --- a/apps/client/src/api/users.ts +++ b/apps/client/src/api/users.ts @@ -1,8 +1,22 @@ import { request } from './client.js' import type { z } from 'zod' -import { UpdateUserSchema } from '@blog/zod-shared' +import { DeleteUserSchema, UpdateUserSchema } from '@blog/zod-shared' -export type UserProfile = { id: string; username: string; bio?: string; avatar?: string } +/** + * `email`/`hasPassword`/`oauthProvider` are only ever present when the + * requester is viewing their own account — see userService.getPublicProfile's + * viewerId gate. Anyone else's profile arrives without them. + */ +export type UserProfile = { + id: string + username: string + bio?: string + image?: string + createdAt: string + email?: string + hasPassword?: boolean + oauthProvider?: 'google' | 'facebook' | null +} export const usersApi = { get: (id: string) => request(`/api/v1/users/${id}`), @@ -10,5 +24,6 @@ export const usersApi = { update: (id: string, input: z.infer) => request(`/api/v1/users/${id}`, { method: 'PATCH', body: JSON.stringify(input) }), - remove: (id: string) => request(`/api/v1/users/${id}`, { method: 'DELETE' }), + remove: (id: string, confirmation: z.infer) => + request(`/api/v1/users/${id}`, { method: 'DELETE', body: JSON.stringify(confirmation) }), } diff --git a/apps/client/src/components/layouts/PageShell.tsx b/apps/client/src/components/layouts/PageShell.tsx index fba99bd2d..21087014e 100644 --- a/apps/client/src/components/layouts/PageShell.tsx +++ b/apps/client/src/components/layouts/PageShell.tsx @@ -39,7 +39,9 @@ export function PageShell({ children }: { children: React.ReactNode }) { New post