Sprint 2 : moteur de documents + sanctuaire

Backend:
- CRUD complet documents/items/versions (update, delete, accept, reject, reorder)
- Service IPFS (upload/retrieve/pin via kubo HTTP API)
- Service sanctuaire : pipeline SHA-256 + IPFS + on-chain (system.remark)
- Verification integrite des entrees sanctuaire
- Recherche par reference (document -> entrees sanctuaire)
- Serialisation deterministe des documents pour archivage
- 14 tests unitaires supplementaires (document service)

Frontend:
- 9 composants : StatusBadge, MarkdownRenderer, DiffView, ItemCard,
  ItemVersionDiff, DocumentList, SanctuaryEntry, IPFSLink, ChainAnchor
- Page detail item avec historique des versions et diff
- Page detail sanctuaire avec verification integrite
- Modal de creation de document + proposition de version
- Archivage document vers sanctuaire depuis la page detail

Documentation:
- API reference mise a jour (9 nouveaux endpoints)
- Guides utilisateur documents et sanctuaire enrichis

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-28 13:08:48 +01:00
parent 25437f24e3
commit 2bdc731639
26 changed files with 3452 additions and 397 deletions

View File

@@ -32,6 +32,20 @@ export interface Document {
items_count: number
}
export interface ItemVersion {
id: string
item_id: string
version_number: number
proposed_text: string
rationale: string | null
diff: string | null
status: string
proposed_by: string | null
reviewed_by: string | null
created_at: string
updated_at: string
}
export interface DocumentCreate {
slug: string
title: string
@@ -40,10 +54,16 @@ export interface DocumentCreate {
version?: string
}
export interface VersionProposal {
proposed_text: string
rationale?: string | null
}
interface DocumentsState {
list: Document[]
current: Document | null
items: DocumentItem[]
versions: ItemVersion[]
loading: boolean
error: string | null
}
@@ -53,6 +73,7 @@ export const useDocumentsStore = defineStore('documents', {
list: [],
current: null,
items: [],
versions: [],
loading: false,
error: null,
}),
@@ -139,11 +160,122 @@ export const useDocumentsStore = defineStore('documents', {
},
/**
* Clear the current document and items.
* Fetch all versions for a specific item within a document.
*/
async fetchItemVersions(slug: string, itemId: string) {
this.loading = true
this.error = null
try {
const { $api } = useApi()
this.versions = await $api<ItemVersion[]>(
`/documents/${slug}/items/${itemId}/versions`,
)
} catch (err: any) {
this.error = err?.data?.detail || err?.message || 'Erreur lors du chargement des versions'
} finally {
this.loading = false
}
},
/**
* Propose a new version for a document item.
*/
async proposeVersion(slug: string, itemId: string, data: VersionProposal) {
this.error = null
try {
const { $api } = useApi()
const version = await $api<ItemVersion>(
`/documents/${slug}/items/${itemId}/versions`,
{
method: 'POST',
body: data,
},
)
this.versions.unshift(version)
return version
} catch (err: any) {
this.error = err?.data?.detail || err?.message || 'Erreur lors de la proposition'
throw err
}
},
/**
* Accept a proposed version.
*/
async acceptVersion(slug: string, itemId: string, versionId: string) {
this.error = null
try {
const { $api } = useApi()
const updated = await $api<ItemVersion>(
`/documents/${slug}/items/${itemId}/versions/${versionId}/accept`,
{ method: 'POST' },
)
const idx = this.versions.findIndex(v => v.id === versionId)
if (idx >= 0) this.versions[idx] = updated
return updated
} catch (err: any) {
this.error = err?.data?.detail || err?.message || 'Erreur lors de l\'acceptation'
throw err
}
},
/**
* Reject a proposed version.
*/
async rejectVersion(slug: string, itemId: string, versionId: string) {
this.error = null
try {
const { $api } = useApi()
const updated = await $api<ItemVersion>(
`/documents/${slug}/items/${itemId}/versions/${versionId}/reject`,
{ method: 'POST' },
)
const idx = this.versions.findIndex(v => v.id === versionId)
if (idx >= 0) this.versions[idx] = updated
return updated
} catch (err: any) {
this.error = err?.data?.detail || err?.message || 'Erreur lors du rejet'
throw err
}
},
/**
* Archive a document into the Sanctuary.
*/
async archiveDocument(slug: string) {
this.error = null
try {
const { $api } = useApi()
const doc = await $api<Document>(
`/documents/${slug}/archive`,
{ method: 'POST' },
)
// Update current if viewing this document
if (this.current?.slug === slug) {
this.current = doc
}
// Update in list
const idx = this.list.findIndex(d => d.slug === slug)
if (idx >= 0) this.list[idx] = doc
return doc
} catch (err: any) {
this.error = err?.data?.detail || err?.message || 'Erreur lors de l\'archivage'
throw err
}
},
/**
* Clear the current document, items and versions.
*/
clearCurrent() {
this.current = null
this.items = []
this.versions = []
},
},
})