fix(ui): продолжать частичное сохранение пользователя без дублей
This commit is contained in:
1 parent
02ca48ef9c
commit
4896d8ef6f
9 files changed
+286
-113
No files matched your search
+54
-15
@@ -13,8 +13,10 @@ import { z } from "zod"
|
||||
import { requestJson, requestBlob } from "@/shared/api/http-client"
|
||||
import type { AppUser, CatalogIface, InterfaceBinding } from "@/lib/users"
|
||||
|
||||
const usersListPayload = z.object({ users: z.array(appUserReadSchema).refine(rows => new Set(rows.map(row => row.id)).size === rows.length, "Повторяющиеся пользователи в ответе API") })
|
||||
const userPayload = z.object({ user: appUserReadSchema })
|
||||
const checkedUserSchema = appUserReadSchema.refine(user => user.bindings.every(binding => binding.userId === user.id) && new Set(user.bindings.map(binding => binding.id)).size === user.bindings.length, "Некорректный контекст привязок пользователя")
|
||||
|
||||
const usersListPayload = z.object({ users: z.array(checkedUserSchema).refine(rows => new Set(rows.map(row => row.id)).size === rows.length, "Повторяющиеся пользователи в ответе API") })
|
||||
const userPayload = z.object({ user: checkedUserSchema })
|
||||
const bindingPayload = z.object({ binding: userBindingSchema })
|
||||
const catalogPayload = z.object({ interfaces: z.array(catalogInterfaceSchema) })
|
||||
|
||||
@@ -55,20 +57,50 @@ export async function listAppUsers(baseUrl: string, init?: RequestInit): Promise
|
||||
return usersListPayload.parse(payload).users.map(toFrontendUser)
|
||||
}
|
||||
|
||||
export async function createAppUser(baseUrl: string, data: AppUserCreate): Promise<AppUser> {
|
||||
export async function getAppUser(baseUrl: string, id: string, init?: RequestInit): Promise<AppUser> {
|
||||
const payload = await requestJson<unknown>(baseUrl, "/api/users/" + encodeURIComponent(id), init)
|
||||
const user = toFrontendUser(userPayload.parse(payload).user)
|
||||
if (user.id !== id) throw new Error("Не подтверждён контекст пользователя.")
|
||||
return user
|
||||
}
|
||||
|
||||
function confirmProfile(user: AppUserRead, data: AppUserCreate | AppUserUpdate) {
|
||||
for (const key of ["name", "login", "email"] as const) {
|
||||
if (data[key] !== undefined && user[key] !== data[key]!.trim()) throw new Error("Профиль не подтверждён: ответ содержит другие поля.")
|
||||
}
|
||||
for (const key of ["role", "active"] as const) {
|
||||
if (data[key] !== undefined && user[key] !== data[key]) throw new Error("Профиль не подтверждён: ответ содержит другие права или состояние.")
|
||||
}
|
||||
const canonical = (items: unknown[]) => items.map(item => JSON.stringify(Object.entries(item as object).sort(([a], [b]) => a.localeCompare(b)))).sort().join("\n")
|
||||
for (const key of ["sections", "servers"] as const) {
|
||||
if (data[key] !== undefined && canonical(user[key]) !== canonical(data[key]!)) throw new Error("Профиль не подтверждён: разрешения отличаются от отправленных.")
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAppUser(baseUrl: string, data: AppUserCreate, init?: RequestInit): Promise<AppUser> {
|
||||
const payload = await requestJson<unknown>(baseUrl, "/api/users", {
|
||||
...init,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
return toFrontendUser(userPayload.parse(payload).user)
|
||||
const parsed = userPayload.parse(payload).user
|
||||
confirmProfile(parsed, data)
|
||||
const user = toFrontendUser(parsed)
|
||||
if (user.login !== data.login.trim()) throw new Error("Создание пользователя не подтверждено: другой логин.")
|
||||
return user
|
||||
}
|
||||
|
||||
export async function updateAppUser(baseUrl: string, id: string, data: AppUserUpdate): Promise<AppUser> {
|
||||
const payload = await requestJson<unknown>(baseUrl, `/api/users/${id}`, {
|
||||
export async function updateAppUser(baseUrl: string, id: string, data: AppUserUpdate, init?: RequestInit): Promise<AppUser> {
|
||||
const payload = await requestJson<unknown>(baseUrl, `/api/users/${encodeURIComponent(id)}`, {
|
||||
...init,
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
return toFrontendUser(userPayload.parse(payload).user)
|
||||
const parsed = userPayload.parse(payload).user
|
||||
confirmProfile(parsed, data)
|
||||
const user = toFrontendUser(parsed)
|
||||
if (user.id !== id) throw new Error("Сохранение пользователя не подтверждено: другой пользователь.")
|
||||
return user
|
||||
}
|
||||
|
||||
export async function deleteAppUser(baseUrl: string, id: string, init?: RequestInit): Promise<void> {
|
||||
@@ -80,20 +112,21 @@ export async function createUserBinding(
|
||||
baseUrl: string,
|
||||
userId: string,
|
||||
data: UserBindingCreate,
|
||||
init?: RequestInit,
|
||||
): Promise<InterfaceBinding> {
|
||||
const payload = await requestJson<unknown>(baseUrl, `/api/users/${userId}/bindings`, {
|
||||
const payload = await requestJson<unknown>(baseUrl, `/api/users/${encodeURIComponent(userId)}/bindings`, {
|
||||
...init,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
return toFrontendBinding(bindingPayload.parse(payload).binding)
|
||||
const binding = toFrontendBinding(bindingPayload.parse(payload).binding)
|
||||
if (binding.userId !== userId || binding.serverId !== String(data.serverId) || binding.interfaceName !== data.interfaceName.trim() || (binding.peerPublicKey ?? "") !== (data.peerPublicKey ?? "").trim()) throw new Error("Привязка не подтверждена: ответ другого объекта.")
|
||||
return binding
|
||||
}
|
||||
|
||||
export async function deleteUserBinding(
|
||||
baseUrl: string,
|
||||
userId: string,
|
||||
bindingId: string,
|
||||
): Promise<void> {
|
||||
await requestJson<void>(baseUrl, `/api/users/${userId}/bindings/${bindingId}`, { method: "DELETE" })
|
||||
export async function deleteUserBinding(baseUrl: string, userId: string, bindingId: string, init?: RequestInit): Promise<void> {
|
||||
const response = await requestBlob(baseUrl, "/api/users/" + encodeURIComponent(userId) + "/bindings/" + encodeURIComponent(bindingId), { ...init, method: "DELETE" })
|
||||
if (response.status !== 204) throw new Error("Удаление привязки не подтверждено: ожидается HTTP 204.")
|
||||
}
|
||||
|
||||
export async function listInterfaceCatalog(
|
||||
@@ -106,3 +139,9 @@ export async function listInterfaceCatalog(
|
||||
)
|
||||
return catalogPayload.parse(payload).interfaces as CatalogInterface[]
|
||||
}
|
||||
|
||||
export function userApiError(error: unknown): string {
|
||||
if (error instanceof z.ZodError) return "Некорректный ответ API. Данные не подтверждены."
|
||||
if (error instanceof Error) return error.message.length > 500 ? error.message.slice(0, 500) + "…" : error.message
|
||||
return "Запрос не подтверждён."
|
||||
}
|
||||
Reference in new issue
Block a user