1- from fastapi import FastAPI , HTTPException
1+ from fastapi import FastAPI , HTTPException , Query
22import uvicorn
33from fastapi .responses import RedirectResponse
44from pymongo import MongoClient
5+ from typing import Optional , Dict , Any
6+ from pydantic import BaseModel , Field
57
68# Connect to MongoDB.
79# TODO: Get these values from environment variables instead of hard-coding them.
@@ -23,23 +25,16 @@ def get_health():
2325 return {"web_server" : "ok" , "database" : is_database_healthy }
2426
2527
26- # TODO: Delete this endpoint once we're confident in our database connection.
27- @app .get ("/experimental/database_names" )
28- def get_database_names ():
29- r"""Get the names of all databases in the MongoDB server."""
30- db_names = mongo_client .list_database_names ()
31- return {"database_names" : db_names }
28+ @app .get ("/bertron" )
29+ def get_all_entities ():
30+ r"""Get all documents from the entities collection."""
31+ db = mongo_client ["bertron" ]
3232
33+ # Check if the collection exists
34+ if "entities" not in db .list_collection_names ():
35+ raise HTTPException (status_code = 404 , detail = "Entities collection not found" )
3336
34- @app .get ("/database/{db_name}/{collection_name}" )
35- def get_collection_data (db_name : str , collection_name : str ):
36- r"""Get all documents from a specified collection in a specified database."""
37- db = mongo_client [db_name ]
38- # Check if the collection exists in the database
39- if collection_name not in db .list_collection_names ():
40- raise HTTPException (status_code = 404 , detail = "Collection not found" )
41-
42- collection = db [collection_name ]
37+ collection = db ["entities" ]
4338 documents = list (collection .find ({}))
4439
4540 # Remove the MongoDB '_id' field from each document for JSON serialization
@@ -49,5 +44,264 @@ def get_collection_data(db_name: str, collection_name: str):
4944 return {"documents" : documents }
5045
5146
47+ class MongoDBQuery (BaseModel ):
48+ filter : Dict [str , Any ] = Field (default = {}, description = "MongoDB find query filter" )
49+ projection : Optional [Dict [str , Any ]] = Field (
50+ default = None , description = "Fields to include or exclude"
51+ )
52+ skip : Optional [int ] = Field (
53+ default = 0 , ge = 0 , description = "Number of documents to skip"
54+ )
55+ limit : Optional [int ] = Field (
56+ default = 100 , ge = 1 , le = 1000 , description = "Maximum number of documents to return"
57+ )
58+ sort : Optional [Dict [str , int ]] = Field (
59+ default = None , description = "Sort criteria (1 for ascending, -1 for descending)"
60+ )
61+
62+
63+ @app .post ("/bertron/find" )
64+ def find_entities (query : MongoDBQuery ):
65+ r"""Execute a MongoDB find operation on the entities collection with filter, projection, skip, limit, and sort options.
66+
67+ Example query body:
68+ {
69+ "filter": {"field": "value", "number_field": {"$gt": 100}},
70+ "projection": {"field1": 1, "field2": 1},
71+ "skip": 0,
72+ "limit": 100,
73+ "sort": {"field1": 1, "field2": -1}
74+ }
75+ """
76+ db = mongo_client ["bertron" ]
77+
78+ # Check if the collection exists
79+ if "entities" not in db .list_collection_names ():
80+ raise HTTPException (status_code = 404 , detail = "Entities collection not found" )
81+
82+ collection = db ["entities" ]
83+
84+ try :
85+ # Execute find with query parameters
86+ cursor = collection .find (filter = query .filter , projection = query .projection )
87+
88+ # Apply skip, limit, and sort if provided
89+ if query .sort :
90+ cursor = cursor .sort (list (query .sort .items ()))
91+ if query .skip :
92+ cursor = cursor .skip (query .skip )
93+ if query .limit :
94+ cursor = cursor .limit (query .limit )
95+
96+ # Convert cursor to list and remove MongoDB _id
97+ documents = list (cursor )
98+ for doc in documents :
99+ doc .pop ("_id" , None )
100+
101+ return {"documents" : documents , "count" : len (documents )}
102+
103+ except Exception as e :
104+ raise HTTPException (status_code = 400 , detail = f"Query error: { str (e )} " )
105+
106+
107+ @app .get ("/bertron/geo/nearby" )
108+ def find_nearby_entities (
109+ latitude : float = Query (
110+ ..., ge = - 90 , le = 90 , description = "Center latitude in degrees"
111+ ),
112+ longitude : float = Query (
113+ ..., ge = - 180 , le = 180 , description = "Center longitude in degrees"
114+ ),
115+ radius_meters : float = Query (..., gt = 0 , description = "Search radius in meters" ),
116+ ):
117+ r"""Find entities within a specified radius of a geographic point using MongoDB's $near operator.
118+
119+ This endpoint uses MongoDB's geospatial $near query which requires a 2dsphere index
120+ on the coordinates field for optimal performance.
121+
122+ Example: /bertron/geo/nearby?latitude=47.6062&longitude=-122.3321&radius_meters=10000
123+ """
124+ db = mongo_client ["bertron" ]
125+
126+ # Check if the collection exists
127+ if "entities" not in db .list_collection_names ():
128+ raise HTTPException (status_code = 404 , detail = "Entities collection not found" )
129+
130+ collection = db ["entities" ]
131+
132+ try :
133+ # Build the $near geospatial query
134+ geo_filter = {
135+ "coordinates" : {
136+ "$near" : {
137+ "$geometry" : {
138+ "type" : "Point" ,
139+ "coordinates" : [
140+ longitude ,
141+ latitude ,
142+ ], # MongoDB uses [lng, lat] format
143+ },
144+ "$maxDistance" : radius_meters ,
145+ }
146+ }
147+ }
148+
149+ # Execute find with geospatial filter and fixed projection
150+ cursor = collection .find (
151+ filter = geo_filter ,
152+ projection = {
153+ "id" : 1 ,
154+ "name" : 1 ,
155+ "uri" : 1 ,
156+ "ber_data_source" : 1 ,
157+ "coordinates" : 1 ,
158+ },
159+ )
160+
161+ # Convert cursor to list and remove MongoDB _id
162+ documents = list (cursor )
163+ for doc in documents :
164+ doc .pop ("_id" , None )
165+
166+ return {
167+ "documents" : documents ,
168+ "count" : len (documents ),
169+ "query_type" : "nearby" ,
170+ "center" : {"latitude" : latitude , "longitude" : longitude },
171+ "radius_meters" : radius_meters ,
172+ }
173+
174+ except Exception as e :
175+ raise HTTPException (status_code = 400 , detail = f"Nearby query error: { str (e )} " )
176+
177+
178+ @app .get ("/bertron/geo/bbox" )
179+ def find_entities_in_bounding_box (
180+ southwest_lat : float = Query (
181+ ..., ge = - 90 , le = 90 , description = "Southwest corner latitude"
182+ ),
183+ southwest_lng : float = Query (
184+ ..., ge = - 180 , le = 180 , description = "Southwest corner longitude"
185+ ),
186+ northeast_lat : float = Query (
187+ ..., ge = - 90 , le = 90 , description = "Northeast corner latitude"
188+ ),
189+ northeast_lng : float = Query (
190+ ..., ge = - 180 , le = 180 , description = "Northeast corner longitude"
191+ ),
192+ ):
193+ r"""Find entities within a bounding box using MongoDB's $geoWithin operator.
194+
195+ This endpoint finds all entities whose coordinates fall within the specified
196+ rectangular bounding box defined by southwest and northeast corners.
197+
198+ Example: /bertron/geo/bbox?southwest_lat=47.5&southwest_lng=-122.4&northeast_lat=47.7&northeast_lng=-122.2
199+ """
200+ db = mongo_client ["bertron" ]
201+
202+ # Check if the collection exists
203+ if "entities" not in db .list_collection_names ():
204+ raise HTTPException (status_code = 404 , detail = "Entities collection not found" )
205+
206+ collection = db ["entities" ]
207+
208+ try :
209+ # Validate bounding box coordinates
210+ if southwest_lat >= northeast_lat :
211+ raise HTTPException (
212+ status_code = 400 ,
213+ detail = "Southwest latitude must be less than northeast latitude" ,
214+ )
215+ if southwest_lng >= northeast_lng :
216+ raise HTTPException (
217+ status_code = 400 ,
218+ detail = "Southwest longitude must be less than northeast longitude" ,
219+ )
220+
221+ # Build the $geoWithin bounding box query
222+ geo_filter = {
223+ "coordinates" : {
224+ "$geoWithin" : {
225+ "$box" : [
226+ [
227+ southwest_lng ,
228+ southwest_lat ,
229+ ], # MongoDB uses [lng, lat] format
230+ [northeast_lng , northeast_lat ],
231+ ]
232+ }
233+ }
234+ }
235+
236+ # Execute find with geospatial filter and fixed projection
237+ cursor = collection .find (
238+ filter = geo_filter ,
239+ projection = {
240+ "id" : 1 ,
241+ "name" : 1 ,
242+ "uri" : 1 ,
243+ "ber_data_source" : 1 ,
244+ "coordinates" : 1 ,
245+ },
246+ )
247+
248+ # Convert cursor to list and remove MongoDB _id
249+ documents = list (cursor )
250+ for doc in documents :
251+ doc .pop ("_id" , None )
252+
253+ return {
254+ "documents" : documents ,
255+ "count" : len (documents ),
256+ "query_type" : "bounding_box" ,
257+ "bounding_box" : {
258+ "southwest" : {"latitude" : southwest_lat , "longitude" : southwest_lng },
259+ "northeast" : {"latitude" : northeast_lat , "longitude" : northeast_lng },
260+ },
261+ }
262+
263+ except Exception as e :
264+ raise HTTPException (
265+ status_code = 400 , detail = f"Bounding box query error: { str (e )} "
266+ )
267+
268+
269+ @app .get ("/bertron/{id}" )
270+ def get_entity_by_id (id : str ):
271+ r"""Get a single entity by its ID.
272+
273+ Example: /bertron/emsl:12345
274+ """
275+ db = mongo_client ["bertron" ]
276+
277+ # Check if the collection exists
278+ if "entities" not in db .list_collection_names ():
279+ raise HTTPException (status_code = 404 , detail = "Entities collection not found" )
280+
281+ collection = db ["entities" ]
282+
283+ try :
284+ # Find the entity by ID with fixed projection
285+ document = collection .find_one (
286+ filter = {"id" : id },
287+ )
288+
289+ if not document :
290+ raise HTTPException (
291+ status_code = 404 , detail = f"Entity with id '{ id } ' not found"
292+ )
293+
294+ # Remove MongoDB _id
295+ document .pop ("_id" , None )
296+
297+ return document
298+
299+ except HTTPException :
300+ # Re-raise HTTP exceptions
301+ raise
302+ except Exception as e :
303+ raise HTTPException (status_code = 400 , detail = f"Query error: { str (e )} " )
304+
305+
52306if __name__ == "__main__" :
53307 uvicorn .run (app , host = "0.0.0.0" , port = 8000 )
0 commit comments