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 }