@@ -109,6 +109,45 @@ interface OntologyContributionState {
109109 reset : ( ) => void ;
110110}
111111
112+ // Unwrap the backend StandardResponse envelope ({ success, data }) to the
113+ // inner array, tolerating endpoints that return a bare array.
114+ function unwrapData ( body : unknown ) : any [ ] {
115+ if ( Array . isArray ( body ) ) return body ;
116+ const data = ( body as { data ?: unknown } ) ?. data ;
117+ return Array . isArray ( data ) ? data : [ ] ;
118+ }
119+
120+ // Map the snake_case OwlClass domain shape onto the client OntologyClass model.
121+ // `label` is Option<String> server-side, so fall back to the IRI; `parent_classes`
122+ // is a Vec — the client tree only models a single parent, so take the first.
123+ function mapOwlClasses ( raw : any [ ] ) : OntologyClass [ ] {
124+ return raw . map ( ( c ) => ( {
125+ iri : String ( c . iri ) ,
126+ label : c . label || String ( c . iri ) ,
127+ parentClass : Array . isArray ( c . parent_classes ) ? c . parent_classes [ 0 ] : undefined ,
128+ description : c . description ?? undefined ,
129+ annotations : c . properties && typeof c . properties === 'object' ? c . properties : undefined ,
130+ } ) ) ;
131+ }
132+
133+ // Map the snake_case OwlProperty domain shape onto OntologyProperty. The server
134+ // enum is "ObjectProperty" | "DataProperty" | "AnnotationProperty"; domain/range
135+ // are Vecs collapsed to their first member for the contribution UI.
136+ function mapOwlProperties ( raw : any [ ] ) : OntologyProperty [ ] {
137+ const typeMap : Record < string , OntologyProperty [ 'propertyType' ] > = {
138+ ObjectProperty : 'object' ,
139+ DataProperty : 'data' ,
140+ AnnotationProperty : 'annotation' ,
141+ } ;
142+ return raw . map ( ( p ) => ( {
143+ iri : String ( p . iri ) ,
144+ label : p . label || String ( p . iri ) ,
145+ domain : Array . isArray ( p . domain ) ? p . domain [ 0 ] : undefined ,
146+ range : Array . isArray ( p . range ) ? p . range [ 0 ] : undefined ,
147+ propertyType : typeMap [ p . property_type ] ?? 'object' ,
148+ } ) ) ;
149+ }
150+
112151// Build tree structure from flat class list
113152function buildClassTree ( classes : OntologyClass [ ] ) : OntologyTreeNode [ ] {
114153 const nodeMap = new Map < string , OntologyTreeNode > ( ) ;
@@ -204,16 +243,24 @@ export const useOntologyContributionStore = create<OntologyContributionState>()(
204243 set ( { loading : true , error : null } ) ;
205244
206245 try {
207- const response = await fetch ( '/api/ontology/public' ) ;
208-
209- if ( ! response . ok ) {
210- throw new Error ( `Failed to fetch ontology: ${ response . statusText } ` ) ;
246+ // The backend exposes OWL classes/properties as two public read-only
247+ // endpoints (safe GETs bypass auth via `mutations_only()`); there is no
248+ // combined `/public` route. Fetch both in parallel and map the
249+ // snake_case OwlClass/OwlProperty domain shapes onto the client model.
250+ const [ classesRes , propertiesRes ] = await Promise . all ( [
251+ fetch ( '/api/ontology/classes' ) ,
252+ fetch ( '/api/ontology/properties' ) ,
253+ ] ) ;
254+
255+ if ( ! classesRes . ok ) {
256+ throw new Error ( `Failed to fetch ontology classes: ${ classesRes . statusText } ` ) ;
257+ }
258+ if ( ! propertiesRes . ok ) {
259+ throw new Error ( `Failed to fetch ontology properties: ${ propertiesRes . statusText } ` ) ;
211260 }
212261
213- const data = await response . json ( ) ;
214-
215- const classes : OntologyClass [ ] = data . classes || [ ] ;
216- const properties : OntologyProperty [ ] = data . properties || [ ] ;
262+ const classes = mapOwlClasses ( unwrapData ( await classesRes . json ( ) ) ) ;
263+ const properties = mapOwlProperties ( unwrapData ( await propertiesRes . json ( ) ) ) ;
217264
218265 set ( {
219266 classes,
0 commit comments