mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-11 00:02:13 +00:00
544 lines
15 KiB
JavaScript
544 lines
15 KiB
JavaScript
import { apiRequest } from './client'
|
|
import { readContributorName } from '../utils/contributorName.js'
|
|
|
|
export async function resolveTreeBySlug(slug) {
|
|
const data = await apiRequest(`/api/arbre/${encodeURIComponent(slug)}`)
|
|
return {
|
|
id: data?.tree?.id || '',
|
|
slug: data?.tree?.slug || '',
|
|
name: data?.tree?.name || '',
|
|
description: data?.tree?.description || null,
|
|
}
|
|
}
|
|
|
|
export async function unlockTreeAccess(treeId, password, userToken = '') {
|
|
const headers = {}
|
|
if (userToken) {
|
|
headers.Authorization = `Bearer ${userToken}`
|
|
}
|
|
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/access/unlock`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({ password }),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
// Rattache à un compte un accès partagé déjà ouvert (jeton d'arbre en poche), sans redemander le mot de passe
|
|
export async function linkTreeAccess(treeId, accessToken, userToken) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/access/link`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${userToken}`,
|
|
},
|
|
body: JSON.stringify({ accessToken }),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function fetchTreeGraph(treeId, token) {
|
|
// Prénom retenu sur cet appareil : le serveur l'inscrit au journal des visites
|
|
const visitorName = readContributorName()
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/graph?t=${Date.now()}`, {
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
...(visitorName ? { 'X-Visitor-Name': encodeURIComponent(visitorName) } : {}),
|
|
},
|
|
})
|
|
|
|
return {
|
|
treeId: data?.treeId || treeId,
|
|
rootPersonId: data?.rootPersonId || null,
|
|
graph: data?.graph || { persons: [], unions: [], filiations: [], medias: [] },
|
|
mediaToken: data?.mediaToken || '',
|
|
}
|
|
}
|
|
|
|
// Frise de vie d'une personne : événements, précisions de date, lieux des unions
|
|
export async function fetchPersonLife(treeId, personId, token) {
|
|
return apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/life`, {
|
|
method: 'GET',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
}
|
|
|
|
// Moments saisis dans l'onglet Vie (administrateurs)
|
|
export async function createLifeEvent(treeId, personId, payload, token) {
|
|
return apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/life/events`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function updateLifeEvent(treeId, eventId, payload, token) {
|
|
return apiRequest(`/trees/${encodeURIComponent(treeId)}/life/events/${encodeURIComponent(eventId)}`, {
|
|
method: 'PATCH',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function deleteLifeEvent(treeId, eventId, token) {
|
|
return apiRequest(`/trees/${encodeURIComponent(treeId)}/life/events/${encodeURIComponent(eventId)}`, {
|
|
method: 'DELETE',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
}
|
|
|
|
export async function listTrees(token) {
|
|
const data = await apiRequest('/trees', {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return {
|
|
trees: Array.isArray(data?.trees) ? data.trees : [],
|
|
lastOpenedTree: data?.lastOpenedTree || null,
|
|
}
|
|
}
|
|
|
|
export async function getTree(treeId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return {
|
|
tree: data?.tree || null,
|
|
role: data?.role || '',
|
|
}
|
|
}
|
|
|
|
export async function createTree(payload, token) {
|
|
const data = await apiRequest('/trees', {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.tree
|
|
}
|
|
|
|
export async function updateTree(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.tree
|
|
}
|
|
|
|
export async function deleteTree(treeId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function createPerson(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.person
|
|
}
|
|
|
|
export async function updatePerson(treeId, personId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.person
|
|
}
|
|
|
|
export async function deletePerson(treeId, personId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function createUnion(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/unions`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.union
|
|
}
|
|
|
|
export async function createParentChildLink(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/links`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.link
|
|
}
|
|
|
|
export async function deleteUnion(treeId, unionId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/unions/${encodeURIComponent(unionId)}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
// Ordre d'une fratrie : chaque enfant, dans l'ordre voulu, avec ses liens vers ses parents
|
|
export async function reorderSiblings(treeId, order, token) {
|
|
return apiRequest(`/trees/${encodeURIComponent(treeId)}/links/order`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({ order }),
|
|
})
|
|
}
|
|
|
|
// Lien parent-enfant modifié (type de filiation : naissance ou adoption)
|
|
export async function updateParentChildLink(treeId, linkId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/links/${encodeURIComponent(linkId)}`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.link
|
|
}
|
|
|
|
export async function deleteParentChildLink(treeId, linkId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/links/${encodeURIComponent(linkId)}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function rotateTreePasswords(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/access/passwords`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function fetchSharePassword(treeId, token) {
|
|
return apiRequest(`/trees/${encodeURIComponent(treeId)}/access/passwords`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function rememberSharePassword(treeId, password, token) {
|
|
return apiRequest(`/trees/${encodeURIComponent(treeId)}/access/passwords/remember`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({ password }),
|
|
})
|
|
}
|
|
|
|
export async function fetchTreeStats(treeId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/stats`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return data?.stats || null
|
|
}
|
|
|
|
export async function fetchTreeSettings(treeId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/settings`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return data?.settings || null
|
|
}
|
|
|
|
export async function updateTreeSettings(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/settings`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data?.settings || null
|
|
}
|
|
|
|
export async function listTreeAuditLogs(treeId, token, options = {}) {
|
|
const queryParams = new URLSearchParams()
|
|
|
|
if (options.limit) {
|
|
queryParams.set('limit', String(options.limit))
|
|
}
|
|
|
|
if (options.offset !== undefined && options.offset !== null) {
|
|
queryParams.set('offset', String(options.offset))
|
|
}
|
|
|
|
if (options.action) {
|
|
queryParams.set('action', options.action)
|
|
}
|
|
|
|
const query = queryParams.toString()
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/audit${query ? `?${query}` : ''}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return {
|
|
logs: Array.isArray(data?.logs) ? data.logs : [],
|
|
nextOffset: typeof data?.nextOffset === 'number' ? data.nextOffset : null,
|
|
}
|
|
}
|
|
|
|
// Journal des visites (administrateurs de l'arbre)
|
|
export async function listTreeVisits(treeId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/visits`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return {
|
|
summary: data?.summary || { last7: { visits: 0, visitors: 0 }, last30: { visits: 0, visitors: 0 } },
|
|
recent: Array.isArray(data?.recent) ? data.recent : [],
|
|
}
|
|
}
|
|
|
|
export async function submitContributionSession(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/contributions/sessions`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function listContributionSessions(treeId, token, status = 'pending') {
|
|
const query = status ? `?status=${encodeURIComponent(status)}` : ''
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/contributions/sessions${query}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return Array.isArray(data?.sessions) ? data.sessions : []
|
|
}
|
|
|
|
export async function reviewContributionSession(treeId, sessionId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/contributions/sessions/${encodeURIComponent(sessionId)}/review`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function uploadAnnotationPhoto(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/annotation-photos`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data // { photoId, photoPath }
|
|
}
|
|
|
|
export async function uploadPersonMedia(treeId, personId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/media`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data.media
|
|
}
|
|
|
|
export async function reorderPersonMedia(treeId, personId, orderedMediaIds, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/media/reorder`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({ orderedMediaIds }),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function deletePersonMedia(treeId, personId, mediaId, token) {
|
|
const data = await apiRequest(
|
|
`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/media/${encodeURIComponent(mediaId)}`,
|
|
{
|
|
method: 'DELETE',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
},
|
|
)
|
|
|
|
return data
|
|
}
|
|
|
|
// Modifier un souvenir (titre, moment de vie, texte de citation) : administrateurs seulement
|
|
export async function updatePersonMedia(treeId, personId, mediaId, payload, token) {
|
|
const data = await apiRequest(
|
|
`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/media/${encodeURIComponent(mediaId)}`,
|
|
{
|
|
method: 'PATCH',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
},
|
|
)
|
|
|
|
return data
|
|
}
|
|
|
|
export async function uploadPersonAvatar(treeId, personId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/avatar`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
// Cadrage du portrait ({ x, y, zoom }) ou null pour le cadrage par défaut
|
|
export async function saveAvatarCrop(treeId, personId, crop, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/avatar/crop`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({ crop }),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function deletePersonAvatar(treeId, personId, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/persons/${encodeURIComponent(personId)}/avatar`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function batchAnnotations(treeId, payload, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/annotations/batch`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
return data
|
|
}
|
|
|
|
export async function exportGedcom(treeId, token) {
|
|
const { API_BASE_URL } = await import('./client')
|
|
const resp = await fetch(`${API_BASE_URL}/trees/${encodeURIComponent(treeId)}/export/gedcom`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
if (!resp.ok) {
|
|
throw new Error('export_failed')
|
|
}
|
|
const blob = await resp.blob()
|
|
const disposition = resp.headers.get('Content-Disposition') ?? ''
|
|
const filenameMatch = disposition.match(/filename="([^"]+)"/)
|
|
const filename = filenameMatch ? filenameMatch[1] : 'arbre.ged'
|
|
return { blob, filename }
|
|
}
|
|
|
|
export async function importGedcom(treeId, fileContent, token) {
|
|
const data = await apiRequest(`/trees/${encodeURIComponent(treeId)}/import/gedcom`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify({ content: fileContent }),
|
|
})
|
|
return data
|
|
}
|