diff --git a/dashboard/src/pages/Admin/Users/UsersListPanel.tsx b/dashboard/src/pages/Admin/Users/UsersListPanel.tsx index f88c9d60..bc226e68 100644 --- a/dashboard/src/pages/Admin/Users/UsersListPanel.tsx +++ b/dashboard/src/pages/Admin/Users/UsersListPanel.tsx @@ -115,6 +115,7 @@ interface CreateValues { } interface EditValues { + username: string; display_name?: string; email?: string; role: "admin" | "user"; @@ -997,6 +998,7 @@ export default function UsersListPanel() { const openEdit = (row: UserRow) => { setEditTarget(row); editForm.setFieldsValue({ + username: row.username, display_name: row.display_name ?? "", email: row.email ?? "", role: row.role, @@ -1038,6 +1040,7 @@ export default function UsersListPanel() { await request(`/users/${editTarget.id}`, { method: "PATCH", body: JSON.stringify({ + username: values.username.trim(), display_name: values.display_name?.trim() || null, email: values.email?.trim() || null, role: values.role, @@ -1664,6 +1667,19 @@ export default function UsersListPanel() {
{t("adminUsers.createSectionAccount")}
+ + } /> + None: (display_name, user_id), ) + def set_username(self, user_id: int, username: str) -> None: + with self._db.transaction() as conn: + conn.execute( + "UPDATE users SET username = ? WHERE id = ?", + (username, user_id), + ) + def set_email(self, user_id: int, email: str | None) -> None: with self._db.transaction() as conn: conn.execute("UPDATE users SET email = ? WHERE id = ?", (email, user_id)) diff --git a/src/octop/infra/users/manager.py b/src/octop/infra/users/manager.py index d28a90f2..2f7b5cdb 100644 --- a/src/octop/infra/users/manager.py +++ b/src/octop/infra/users/manager.py @@ -441,6 +441,25 @@ async def set_display_name(self, username: str, display_name: str | None) -> Non if current is not None: current.display_name = display_name + async def set_username(self, username: str, new_username: str) -> None: + row = self._services.user_repo.get_by_username(username) + if row is None: + raise OctopError(ErrorCode.NOT_FOUND, "user not found") + if new_username == row.username: + return + self._services.user_repo.set_username(row.id, new_username) + async with self._lock: + current = self._users.pop(username, None) + if current is not None: + current.username = new_username + self._users[new_username] = current + self._services.audit_repo.write( + actor=ACTOR_ADMIN, + action="user.set_username", + target=username, + payload=new_username, + ) + async def set_email(self, username: str, email: str | None) -> None: row = self._services.user_repo.get_by_username(username) if row is None: