11"""Catalogs extension."""
22
3- from typing import List , Type , Union
3+ from typing import List , Type
44
55import attr
6- from fastapi import APIRouter , FastAPI , Request
6+ from fastapi import APIRouter , FastAPI , HTTPException , Request
77from fastapi .responses import JSONResponse
88from starlette .responses import Response
99
10+ from stac_fastapi .core .models import Catalog
11+ from stac_fastapi .types import stac as stac_types
1012from stac_fastapi .types .core import BaseCoreClient
1113from stac_fastapi .types .extension import ApiExtension
12- from stac_fastapi .types .stac import LandingPage
1314
1415
1516@attr .s
1617class CatalogsExtension (ApiExtension ):
1718 """Catalogs Extension.
1819
19- The Catalogs extension adds a /catalogs endpoint that returns the root catalog.
20+ The Catalogs extension adds a /catalogs endpoint that returns the root catalog
21+ containing child links to all catalogs in the database.
2022 """
2123
2224 client : BaseCoreClient = attr .ib (default = None )
@@ -25,40 +27,229 @@ class CatalogsExtension(ApiExtension):
2527 router : APIRouter = attr .ib (default = attr .Factory (APIRouter ))
2628 response_class : Type [Response ] = attr .ib (default = JSONResponse )
2729
28- def register (self , app : FastAPI ) -> None :
30+ def register (self , app : FastAPI , settings = None ) -> None :
2931 """Register the extension with a FastAPI application.
3032
3133 Args:
3234 app: target FastAPI application.
33-
34- Returns:
35- None
35+ settings: extension settings (unused for now).
3636 """
37- response_model = (
38- self .settings .get ("response_model" )
39- if isinstance (self .settings , dict )
40- else getattr (self .settings , "response_model" , None )
41- )
37+ self .settings = settings or {}
4238
4339 self .router .add_api_route (
4440 path = "/catalogs" ,
4541 endpoint = self .catalogs ,
4642 methods = ["GET" ],
47- response_model = LandingPage if response_model else None ,
43+ response_model = Catalog ,
44+ response_class = self .response_class ,
45+ summary = "Get Root Catalog" ,
46+ description = "Returns the root catalog containing links to all catalogs." ,
47+ tags = ["Catalogs" ],
48+ )
49+
50+ # Add endpoint for creating catalogs
51+ self .router .add_api_route (
52+ path = "/catalogs" ,
53+ endpoint = self .create_catalog ,
54+ methods = ["POST" ],
55+ response_model = Catalog ,
56+ response_class = self .response_class ,
57+ status_code = 201 ,
58+ summary = "Create Catalog" ,
59+ description = "Create a new STAC catalog." ,
60+ tags = ["Catalogs" ],
61+ )
62+
63+ # Add endpoint for getting individual catalogs
64+ self .router .add_api_route (
65+ path = "/catalogs/{catalog_id}" ,
66+ endpoint = self .get_catalog ,
67+ methods = ["GET" ],
68+ response_model = Catalog ,
4869 response_class = self .response_class ,
49- summary = "Get Catalogs " ,
50- description = "Returns the root catalog." ,
70+ summary = "Get Catalog " ,
71+ description = "Get a specific STAC catalog by ID ." ,
5172 tags = ["Catalogs" ],
5273 )
74+
75+ # Add endpoint for getting collections in a catalog
76+ self .router .add_api_route (
77+ path = "/catalogs/{catalog_id}/collections" ,
78+ endpoint = self .get_catalog_collections ,
79+ methods = ["GET" ],
80+ response_model = stac_types .Collections ,
81+ response_class = self .response_class ,
82+ summary = "Get Catalog Collections" ,
83+ description = "Get collections linked from a specific catalog." ,
84+ tags = ["Catalogs" ],
85+ )
86+
5387 app .include_router (self .router , tags = ["Catalogs" ])
5488
55- async def catalogs (self , request : Request ) -> Union [ LandingPage , Response ] :
56- """Get catalogs.
89+ async def catalogs (self , request : Request ) -> Catalog :
90+ """Get root catalog with links to all catalogs.
5791
5892 Args:
5993 request: Request object.
6094
6195 Returns:
62- The root catalog (landing page) .
96+ Root catalog containing child links to all catalogs in the database .
6397 """
64- return await self .client .landing_page (request = request )
98+ base_url = str (request .base_url )
99+
100+ # Get all catalogs from database
101+ catalogs , _ , _ = await self .client .database .get_all_catalogs (
102+ token = None ,
103+ limit = 1000 , # Large limit to get all catalogs
104+ request = request ,
105+ sort = [{"field" : "id" , "direction" : "asc" }],
106+ )
107+
108+ # Create child links to each catalog
109+ child_links = []
110+ for catalog in catalogs :
111+ child_links .append (
112+ {
113+ "rel" : "child" ,
114+ "href" : f"{ base_url } catalogs/{ catalog .id } " ,
115+ "type" : "application/json" ,
116+ "title" : catalog .title or catalog .id ,
117+ }
118+ )
119+
120+ # Create root catalog
121+ root_catalog = {
122+ "type" : "Catalog" ,
123+ "stac_version" : "1.0.0" ,
124+ "id" : "root" ,
125+ "title" : "Root Catalog" ,
126+ "description" : "Root catalog containing all available catalogs" ,
127+ "links" : [
128+ {
129+ "rel" : "self" ,
130+ "href" : f"{ base_url } catalogs" ,
131+ "type" : "application/json" ,
132+ },
133+ {
134+ "rel" : "root" ,
135+ "href" : f"{ base_url } catalogs" ,
136+ "type" : "application/json" ,
137+ },
138+ {
139+ "rel" : "parent" ,
140+ "href" : base_url .rstrip ("/" ),
141+ "type" : "application/json" ,
142+ },
143+ ]
144+ + child_links ,
145+ }
146+
147+ return Catalog (** root_catalog )
148+
149+ async def create_catalog (self , catalog : Catalog , request : Request ) -> Catalog :
150+ """Create a new catalog.
151+
152+ Args:
153+ catalog: The catalog to create.
154+ request: Request object.
155+
156+ Returns:
157+ The created catalog.
158+ """
159+ # Convert STAC catalog to database format
160+ db_catalog = self .client .catalog_serializer .stac_to_db (catalog , request )
161+
162+ # Create the catalog in the database
163+ await self .client .database .create_catalog (db_catalog .model_dump ())
164+
165+ # Return the created catalog
166+ return catalog
167+
168+ async def get_catalog (self , catalog_id : str , request : Request ) -> Catalog :
169+ """Get a specific catalog by ID.
170+
171+ Args:
172+ catalog_id: The ID of the catalog to retrieve.
173+ request: Request object.
174+
175+ Returns:
176+ The requested catalog.
177+ """
178+ try :
179+ # Get the catalog from the database
180+ db_catalog = await self .client .database .find_catalog (catalog_id )
181+
182+ # Convert to STAC format
183+ catalog = self .client .catalog_serializer .db_to_stac (db_catalog , request )
184+
185+ return catalog
186+ except Exception :
187+ raise HTTPException (
188+ status_code = 404 , detail = f"Catalog { catalog_id } not found"
189+ )
190+
191+ async def get_catalog_collections (
192+ self , catalog_id : str , request : Request
193+ ) -> stac_types .Collections :
194+ """Get collections linked from a specific catalog.
195+
196+ Args:
197+ catalog_id: The ID of the catalog.
198+ request: Request object.
199+
200+ Returns:
201+ Collections object containing collections linked from the catalog.
202+ """
203+ try :
204+ # Get the catalog from the database
205+ db_catalog = await self .client .database .find_catalog (catalog_id )
206+
207+ # Convert to STAC format to access links
208+ catalog = self .client .catalog_serializer .db_to_stac (db_catalog , request )
209+
210+ # Extract collection IDs from catalog links
211+ collection_ids = []
212+ if hasattr (catalog , "links" ) and catalog .links :
213+ for link in catalog .links :
214+ if link .get ("rel" ) in ["child" , "item" ]:
215+ # Extract collection ID from href
216+ href = link .get ("href" , "" )
217+ # Look for patterns like /collections/{id} or collections/{id}
218+ if "/collections/" in href :
219+ collection_id = href .split ("/collections/" )[- 1 ].split ("/" )[
220+ 0
221+ ]
222+ if collection_id and collection_id not in collection_ids :
223+ collection_ids .append (collection_id )
224+
225+ # Fetch the collections
226+ collections = []
227+ for coll_id in collection_ids :
228+ try :
229+ collection = await self .client .get_collection (
230+ coll_id , request = request
231+ )
232+ collections .append (collection )
233+ except Exception :
234+ # Skip collections that can't be found
235+ continue
236+
237+ # Return in Collections format
238+ base_url = str (request .base_url )
239+ return stac_types .Collections (
240+ collections = collections ,
241+ links = [
242+ {"rel" : "root" , "type" : "application/json" , "href" : base_url },
243+ {"rel" : "parent" , "type" : "application/json" , "href" : base_url },
244+ {
245+ "rel" : "self" ,
246+ "type" : "application/json" ,
247+ "href" : f"{ base_url } catalogs/{ catalog_id } /collections" ,
248+ },
249+ ],
250+ )
251+
252+ except Exception :
253+ raise HTTPException (
254+ status_code = 404 , detail = f"Catalog { catalog_id } not found"
255+ )
0 commit comments