Skip to content

Commit ad479cd

Browse files
committed
fix(backend): resolve memory leak and db pagination in catalog API
Refactored the /objects endpoint to query the SpaceObject schema directly, enabling SQL-level pagination and resolving the missing integer division bug in page calculations.
1 parent 120b23b commit ad479cd

1 file changed

Lines changed: 41 additions & 24 deletions

File tree

backend/api/v1/endpoints/catalog.py

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import datetime
99

1010
from database.session import get_db
11-
from models.db_models import Satellite, Debris
11+
from models.db_models import Satellite, Debris, SpaceObject
1212
from app.core.exceptions import (
1313
ExternalServiceError,
1414
NotFoundError,
@@ -114,6 +114,30 @@ def _serialize_debris(deb: Debris) -> Dict[str, Any]:
114114
}
115115

116116

117+
@router.get("/objects", response_model=APIResponse[List[Dict[str, Any]]])
118+
def _serialize_space_object(obj: SpaceObject) -> Dict[str, Any]:
119+
epoch = obj.epoch
120+
raan, arg_of_perigee, mean_anomaly = _orbital_angles(obj.tle_line2, obj)
121+
return {
122+
"id": str(obj.id),
123+
"name": obj.objectName or "",
124+
"catalog_number": obj.noradId or "",
125+
"cospar_id": obj.cospar_id,
126+
"classification": obj.objectType or "UNKNOWN",
127+
"epoch": epoch if isinstance(epoch, str) else (epoch.isoformat() if epoch else None),
128+
"inclination": obj.inclination,
129+
"eccentricity": obj.eccentricity,
130+
"semimajor_axis": obj.semimajor_axis,
131+
"raan": raan,
132+
"arg_of_perigee": arg_of_perigee,
133+
"mean_anomaly": mean_anomaly,
134+
"mean_motion": obj.mean_motion,
135+
"period": obj.period,
136+
"has_tle": bool(obj.tle_line1 and obj.tle_line2),
137+
"updated_at": obj.updated_at.isoformat() if obj.updated_at else None,
138+
}
139+
140+
117141
@router.get("/objects", response_model=APIResponse[List[Dict[str, Any]]])
118142
def list_space_objects(
119143
page: int = Query(1, ge=1),
@@ -123,43 +147,36 @@ def list_space_objects(
123147
db: Session = Depends(get_db),
124148
):
125149
if classification and classification.upper() not in VALID_CLASSIFICATIONS:
126-
raise ValidationError(
127-
f"'{classification}' is not a valid object classification.",
150+
raise ValidationError(f"'{classification}' is not a valid object classification.",
128151
details={"field": "classification", "value": classification, "allowed": list(VALID_CLASSIFICATIONS)},
129152
)
130153

131154
cls = classification.upper() if classification else None
132155
pattern = f"%{search}%" if search else None
133156

134-
all_docs: List[Dict[str, Any]] = []
135-
136-
if cls != "DEBRIS":
137-
q = db.query(Satellite)
138-
if cls:
139-
q = q.filter(Satellite.objectType == cls)
140-
if pattern:
141-
q = q.filter(Satellite.objectName.ilike(pattern) | Satellite.noradId.ilike(pattern))
142-
all_docs.extend(_serialize_sat(s) for s in q.all())
143-
144-
if cls in (None, "DEBRIS"):
145-
q = db.query(Debris)
146-
if pattern:
147-
q = q.filter(Debris.objectName.ilike(pattern) | Debris.noradId.ilike(pattern))
148-
all_docs.extend(_serialize_debris(d) for d in q.all())
157+
# FIX: Query the unified SpaceObject schema to enable DB-level pagination
158+
query = db.query(SpaceObject)
159+
160+
if cls:
161+
query = query.filter(SpaceObject.objectType == cls)
162+
if pattern:
163+
query = query.filter(SpaceObject.objectName.ilike(pattern) | SpaceObject.noradId.ilike(pattern))
149164

150-
total = len(all_docs)
165+
total = query.count()
151166
offset = (page - 1) * size
152-
pages = (total + size - 1) // size if total else 1
167+
168+
# FIX: Apply offset and limit directly to the SQL query
169+
records = query.offset(offset).limit(size).all()
170+
171+
# FIX: Correct the pagination math
172+
pages = (total + size - 1) // size
153173

154174
return APIResponse(
155175
success=True,
156176
message=f"Orbital catalog — {total} objects tracked",
157-
data=all_docs[offset: offset + size],
177+
data=[_serialize_space_object(r) for r in records],
158178
pagination=PaginationSchema(page=page, size=size, total=total, pages=pages),
159179
)
160-
161-
162-
@router.get("/objects/{catalog_number}", response_model=APIResponse[Dict[str, Any]])
163180
def get_space_object(catalog_number: str, db: Session = Depends(get_db)):
164181
if catalog_number.isdecimal():
165182
catalog_number = str(int(catalog_number))

0 commit comments

Comments
 (0)