61 lines
3.5 KiB
TypeScript
61 lines
3.5 KiB
TypeScript
import { bindingDiffKey, userInitials, type AppUser, type AppUserForm, type InterfaceBinding } from "@/lib/users"
|
|
import { ApiClientError } from "@/shared/api/http-client"
|
|
export interface UserSaveCheckpoint { user: AppUser | null; profileSignature?: string; bindingsNeedRead?: boolean; createUncertain?: boolean }
|
|
export interface UserSaveApi {
|
|
create: (form: AppUserForm) => Promise<AppUser>
|
|
update: (id: string, form: AppUserForm) => Promise<AppUser>
|
|
read: (id: string) => Promise<AppUser>
|
|
removeBinding: (userId: string, bindingId: string) => Promise<void>
|
|
addBinding: (userId: string, binding: InterfaceBinding) => Promise<InterfaceBinding>
|
|
}
|
|
/** Подтверждённые шаги нескольких HTTP запросов, а не общая транзакция. */
|
|
export async function saveUserSteps(form: AppUserForm, checkpoint: UserSaveCheckpoint, api: UserSaveApi, current: () => boolean, changed: (state: UserSaveCheckpoint) => void): Promise<AppUser | null> {
|
|
if (!current()) return null
|
|
if (checkpoint.createUncertain) throw new Error("Результат создания неизвестен. Проверьте список пользователей перед новой попыткой; повторное создание заблокировано.")
|
|
const { bindings: desired, ...profile } = form
|
|
const signature = JSON.stringify(profile)
|
|
if (checkpoint.bindingsNeedRead && checkpoint.user) {
|
|
const user = await api.read(checkpoint.user.id)
|
|
if (!current()) return null
|
|
checkpoint.user = user; checkpoint.bindingsNeedRead = false; checkpoint.profileSignature = undefined; changed(checkpoint)
|
|
}
|
|
if (!checkpoint.user) {
|
|
try {
|
|
const user = await api.create(form)
|
|
if (!current()) return null
|
|
checkpoint.user = user; checkpoint.profileSignature = signature; changed(checkpoint)
|
|
} catch (error) {
|
|
if (current() && !(error instanceof ApiClientError && error.status >= 400 && error.status < 500)) { checkpoint.createUncertain = true; changed(checkpoint) }
|
|
throw error
|
|
}
|
|
} else if (checkpoint.profileSignature !== signature) {
|
|
const user = await api.update(checkpoint.user.id, form)
|
|
if (!current()) return null
|
|
checkpoint.user = user; checkpoint.profileSignature = signature; changed(checkpoint)
|
|
}
|
|
if (!checkpoint.user || !current()) return null
|
|
try {
|
|
const desiredKeys = new Set(desired.map(bindingDiffKey))
|
|
for (const binding of [...checkpoint.user.bindings]) {
|
|
if (desiredKeys.has(bindingDiffKey(binding))) continue
|
|
await api.removeBinding(checkpoint.user.id, binding.id)
|
|
if (!current()) return null
|
|
checkpoint.user = { ...checkpoint.user, bindings: checkpoint.user.bindings.filter(b => b.id !== binding.id) }; changed(checkpoint)
|
|
}
|
|
const knownKeys = new Set(checkpoint.user.bindings.map(bindingDiffKey))
|
|
for (const binding of desired) {
|
|
if (knownKeys.has(bindingDiffKey(binding))) continue
|
|
const added = await api.addBinding(checkpoint.user.id, binding)
|
|
if (!current()) return null
|
|
checkpoint.user = { ...checkpoint.user, bindings: [...checkpoint.user.bindings, added] }; knownKeys.add(bindingDiffKey(added)); changed(checkpoint)
|
|
}
|
|
} catch (error) {
|
|
if (current()) { checkpoint.bindingsNeedRead = true; changed(checkpoint) }
|
|
throw error
|
|
}
|
|
return checkpoint.user
|
|
}
|
|
export function userProfileRequest(form: AppUserForm) {
|
|
return { name: form.name.trim(), login: form.login.trim(), email: form.email.trim(), role: form.role, active: form.active, avatar: userInitials(form.name), sections: form.sections, servers: form.servers }
|
|
}
|