22from pathlib import Path as PathLib
33from urllib .parse import quote , unquote , urlencode
44
5+ import rfc3987
56import structlog
67from fastapi import APIRouter , Depends , HTTPException , Path , Request
78from fastapi .responses import HTMLResponse , RedirectResponse
2223router = APIRouter (prefix = "/web" , include_in_schema = False )
2324
2425
26+ def _is_iri (query : str ) -> bool :
27+ """Check if query string is a valid HTTP/HTTPS IRI."""
28+ try :
29+ parsed = rfc3987 .parse (query .strip (), rule = "IRI" )
30+ return parsed .get ("scheme" ) in ("http" , "https" )
31+ except ValueError :
32+ return False
33+
34+
2535def value_for_language (value : list [dict [str , str ]], lang : str ) -> str :
2636 """Get the `@value` for a list of multilingual strings with correct `@language` value"""
2737 for dct in value :
@@ -189,10 +199,16 @@ async def web_concept_scheme_view(
189199 },
190200 )
191201 except de .ConceptSchemeNotFoundError :
192- raise HTTPException (status_code = 404 , detail = f"Concept Scheme with IRI `{ iri } ` not found" )
202+ raise HTTPException (
203+ status_code = 404 , detail = f"Concept Scheme with IRI `{ iri } ` not found"
204+ )
193205 except de .ConceptSchemesNotInDatabase as e :
194- logger .error ("Database error while fetching concept scheme" , iri = iri , error = str (e ))
195- raise HTTPException (status_code = 500 , detail = "Database error while fetching concept scheme" )
206+ logger .error (
207+ "Database error while fetching concept scheme" , iri = iri , error = str (e )
208+ )
209+ raise HTTPException (
210+ status_code = 500 , detail = "Database error while fetching concept scheme"
211+ )
196212
197213
198214def concept_view_url (
@@ -264,7 +280,9 @@ async def get_concept_and_link(iri: str) -> (str, de.Concept | str):
264280 except de .ConceptNotFoundError :
265281 return iri , iri
266282
267- relationships = await service .relationships_get (iri = decoded_iri , source = True , target = True )
283+ relationships = await service .relationships_get (
284+ iri = decoded_iri , source = True , target = True
285+ )
268286 broader = [
269287 (await get_concept_and_link (obj .target ))
270288 for obj in relationships
@@ -277,7 +295,8 @@ async def get_concept_and_link(iri: str) -> (str, de.Concept | str):
277295 ]
278296
279297 scheme_list = [
280- (request .url_for ("web_concept_view" , iri = quote (s ["@id" ])), s ) for s in concept .schemes
298+ (request .url_for ("web_concept_view" , iri = quote (s ["@id" ])), s )
299+ for s in concept .schemes
281300 ]
282301
283302 associations = await service .association_get_all (source_concept_iri = concept .id_ )
@@ -286,29 +305,27 @@ async def get_concept_and_link(iri: str) -> (str, de.Concept | str):
286305 for target in obj .target_concepts :
287306 try :
288307 url , assoc_concept = await get_concept_and_link (target ["@id" ])
289- formatted_associations .append (
290- {
291- "url" : url ,
292- "obj" : assoc_concept ,
293- "conditional" : None ,
294- "conversion" : target .get (
295- "http://qudt.org/3.0.0/schema/qudt/conversionMultiplier"
296- ),
297- }
298- )
308+ formatted_associations .append ({
309+ "url" : url ,
310+ "obj" : assoc_concept ,
311+ "conditional" : None ,
312+ "conversion" : target .get (
313+ "http://qudt.org/3.0.0/schema/qudt/conversionMultiplier"
314+ ),
315+ })
299316 except de .ConceptNotFoundError :
300- formatted_associations .append (
301- {
302- "url " : target ["@id" ],
303- "obj " : target [ "@id" ] ,
304- "conditional " : None ,
305- "conversion" : target . get (
306- "http://qudt.org/3.0.0/schema/qudt/conversionMultiplier"
307- ),
308- }
309- )
310-
311- languages = [( request . url , Language . get ( language ). display_name ( language ). title ()) ] + [
317+ formatted_associations .append ({
318+ "url" : target [ "@id" ],
319+ "obj " : target ["@id" ],
320+ "conditional " : None ,
321+ "conversion " : target . get (
322+ "http://qudt.org/3.0.0/schema/qudt/conversionMultiplier"
323+ ),
324+ })
325+
326+ languages = [
327+ ( request . url , Language . get ( language ). display_name ( language ). title ())
328+ ] + [
312329 (
313330 concept_view_url (
314331 request ,
@@ -343,7 +360,9 @@ async def get_concept_and_link(iri: str) -> (str, de.Concept | str):
343360 except de .ConceptNotFoundError :
344361 raise HTTPException (status_code = 404 , detail = f"Concept with IRI `{ iri } ` not found" )
345362 except de .ConceptSchemesNotInDatabase as e :
346- logger .error ("Database error while fetching concept" , iri = decoded_iri , error = str (e ))
363+ logger .error (
364+ "Database error while fetching concept" , iri = decoded_iri , error = str (e )
365+ )
347366 raise HTTPException (status_code = 500 , detail = "Database error while fetching concept" )
348367
349368
@@ -357,9 +376,38 @@ async def web_search(
357376 language : str = "en" ,
358377 semantic : bool = True ,
359378 search_service = Depends (get_search_service ),
379+ graph_service = Depends (get_graph_service ),
360380 settings = Depends (get_settings ),
361381) -> HTMLResponse :
362382 """Search for concepts."""
383+ # Check if query is an IRI and attempt direct lookup
384+ if query and _is_iri (query ):
385+ # Try to get concept directly
386+ try :
387+ concept = await graph_service .concept_get (iri = query )
388+ # If found, redirect to concept page
389+ return RedirectResponse (
390+ url = concept_view_url (
391+ request ,
392+ concept .id_ ,
393+ concept .schemes [0 ]["@id" ],
394+ language ,
395+ ),
396+ status_code = 303 , # See Other
397+ )
398+ except de .ConceptNotFoundError :
399+ # Not a concept, try concept scheme
400+ try :
401+ concept_scheme = await graph_service .concept_scheme_get (iri = query )
402+ # If found, redirect to concept scheme page
403+ return RedirectResponse (
404+ url = concept_scheme_view_url (request , concept_scheme .id_ , language ),
405+ status_code = 303 , # See Other
406+ )
407+ except de .ConceptSchemeNotFoundError :
408+ # IRI not found in database, fall through to regular search
409+ pass
410+
363411 try :
364412 results = []
365413 if query :
0 commit comments