Skip to content

Commit 158dff2

Browse files
committed
feat: use jsonable_encoder with custom ObjectId encoder for clean REST API responses
1 parent 46c4338 commit 158dff2

File tree

1 file changed

+68
-38
lines changed

1 file changed

+68
-38
lines changed

backend/main.py

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,133 @@
11

22
from datetime import datetime
3+
from typing import Any, Dict, List
4+
35
from bson import ObjectId
4-
from database import schemas_collection
5-
from datamodel import SchemaDefinition, UpdateSchema
66
from fastapi import FastAPI, HTTPException
7+
from fastapi.encoders import jsonable_encoder
78
from fastapi.middleware.cors import CORSMiddleware
89

9-
# Initialize FastAPI application
10+
from database import schemas_collection
11+
from datamodel import SchemaDefinition, UpdateSchema
12+
13+
14+
# ---- FastAPI app & CORS ----
1015
app = FastAPI()
1116

12-
# Enable CORS for all origins (useful for frontend integration)
1317
app.add_middleware(
1418
CORSMiddleware,
15-
allow_origins=["*"],
19+
allow_origins=["*"], # adjust in production
1620
allow_credentials=True,
1721
allow_methods=["*"],
1822
allow_headers=["*"],
1923
)
2024

25+
26+
# ---- Routes ----
2127
@app.get("/schemas")
22-
async def get_all_schemas():
28+
async def get_all_schemas() -> List[Dict[str, Any]]:
2329
"""
2430
Retrieve all schema documents from the database.
25-
Converts MongoDB ObjectId to string for JSON serialization.
31+
2632
Returns:
27-
list: A list of schema documents with stringified IDs.
33+
list: A list of schema documents with JSON-safe '_id' fields (strings).
2834
"""
29-
# Fetch all schema documents from MongoDB
30-
schema_documents = list(schemas_collection.find())
31-
# Convert ObjectId to string for each document
32-
for schema_document in schema_documents:
33-
original_id = schema_document["_id"]
34-
schema_document["_id"] = str(original_id)
35-
return schema_documents
35+
docs = list(schemas_collection.find())
36+
# One-liner conversion: ObjectId -> str (applies even to nested ObjectIds)
37+
return jsonable_encoder(docs, custom_encoder={ObjectId: str})
38+
3639

3740
@app.post("/schemas")
38-
async def add_schema(schema: SchemaDefinition):
41+
async def add_schema(schema: SchemaDefinition) -> Dict[str, Any]:
3942
"""
4043
Add a new schema to the database.
44+
4145
Args:
4246
schema (SchemaDefinition): The schema data to insert.
47+
4348
Returns:
44-
dict: A dictionary with key being the ID of the inserted schema,
45-
and value the actual inserted schema.
49+
dict: A dictionary with keys:
50+
- "id": the ID of the inserted schema (string),
51+
- "schema": the actual inserted schema document as stored in the database,
52+
with JSON-safe '_id' and other BSON types.
4653
"""
47-
# Update timestamp before insertion
54+
# Set server-side timestamp
4855
schema.updated_at = datetime.utcnow()
4956

50-
# Insert schema into MongoDB
57+
# Insert and obtain new ObjectId
5158
result = schemas_collection.insert_one(schema.dict())
52-
inserted_id = result.inserted_id # ObjectId
59+
inserted_oid = result.inserted_id
60+
61+
# Fetch the stored document to return exactly what's in DB
62+
inserted_doc = schemas_collection.find_one({"_id": inserted_oid})
63+
64+
# Make the document JSON-safe (ObjectId -> str, etc.)
65+
inserted_doc_json = jsonable_encoder(inserted_doc, custom_encoder={ObjectId: str})
5366

54-
# Fetch the stored document so we return what the DB actually has
55-
inserted_doc = schemas_collection.find_one({"_id": inserted_id})
56-
# Normalize _id for JSON
57-
if inserted_doc is not None and "_id" in inserted_doc:
58-
inserted_doc["_id"] = str(inserted_doc["_id"])
67+
# Stable, tooling-friendly shape:
68+
return {"id": str(inserted_oid), "schema": inserted_doc_json}
69+
70+
# If you want ID-as-key instead, return this:
71+
# return {str(inserted_oid): inserted_doc_json}
5972

60-
# Return as { "<id>": <document> } per your requirement
61-
return {str(inserted_id): inserted_doc}
6273

6374
@app.put("/schemas/{id}")
64-
async def update_schema(id: str, update: UpdateSchema):
75+
async def update_schema(id: str, update: UpdateSchema) -> Dict[str, Any]:
6576
"""
6677
Update an existing schema by ID.
78+
6779
Args:
68-
id (str): The schema ID.
80+
id (str): The schema ID (string form of ObjectId).
6981
update (UpdateSchema): Fields to update.
82+
7083
Raises:
7184
HTTPException: If the schema is not found.
85+
7286
Returns:
73-
dict: Success message.
87+
dict: Success message and (optionally) the updated document.
7488
"""
7589
# Prepare update fields (ignore None values)
76-
update_fields = {
77-
key: value for key, value in update.dict().items() if value is not None
78-
}
90+
update_fields = {k: v for k, v in update.dict().items() if v is not None}
91+
92+
# If anything is updated, refresh timestamp
93+
if update_fields:
94+
update_fields["updated_at"] = datetime.utcnow()
7995

8096
result = schemas_collection.update_one(
8197
{"_id": ObjectId(id)},
82-
{"$set": update_fields},
98+
{"$set": update_fields} if update_fields else {}
8399
)
84-
if result.modified_count == 0 and result.matched_count == 0:
100+
101+
if result.matched_count == 0:
85102
raise HTTPException(status_code=404, detail="Schema not found")
86103

104+
# Optionally return the updated document (commented out by default)
105+
# updated_doc = schemas_collection.find_one({"_id": ObjectId(id)})
106+
# return {
107+
# "message": "Schema updated",
108+
# "schema": jsonable_encoder(updated_doc, custom_encoder={ObjectId: str}),
109+
# }
110+
87111
return {"message": "Schema updated"}
88112

113+
89114
@app.delete("/schemas/{id}")
90-
async def delete_schema(id: str):
115+
async def delete_schema(id: str) -> Dict[str, str]:
91116
"""
92117
Delete a schema by ID.
118+
93119
Args:
94-
id (str): The schema ID.
120+
id (str): The schema ID (string form of ObjectId).
121+
95122
Raises:
96123
HTTPException: If the schema is not found.
124+
97125
Returns:
98126
dict: Success message.
99127
"""
100128
result = schemas_collection.delete_one({"_id": ObjectId(id)})
129+
101130
if result.deleted_count == 0:
102131
raise HTTPException(status_code=404, detail="Schema not found")
132+
103133
return {"message": "Schema deleted"}

0 commit comments

Comments
 (0)