|
1 | 1 |
|
2 | 2 | from datetime import datetime |
| 3 | +from typing import Any, Dict, List |
| 4 | + |
3 | 5 | from bson import ObjectId |
4 | | -from database import schemas_collection |
5 | | -from datamodel import SchemaDefinition, UpdateSchema |
6 | 6 | from fastapi import FastAPI, HTTPException |
| 7 | +from fastapi.encoders import jsonable_encoder |
7 | 8 | from fastapi.middleware.cors import CORSMiddleware |
8 | 9 |
|
9 | | -# Initialize FastAPI application |
| 10 | +from database import schemas_collection |
| 11 | +from datamodel import SchemaDefinition, UpdateSchema |
| 12 | + |
| 13 | + |
| 14 | +# ---- FastAPI app & CORS ---- |
10 | 15 | app = FastAPI() |
11 | 16 |
|
12 | | -# Enable CORS for all origins (useful for frontend integration) |
13 | 17 | app.add_middleware( |
14 | 18 | CORSMiddleware, |
15 | | - allow_origins=["*"], |
| 19 | + allow_origins=["*"], # adjust in production |
16 | 20 | allow_credentials=True, |
17 | 21 | allow_methods=["*"], |
18 | 22 | allow_headers=["*"], |
19 | 23 | ) |
20 | 24 |
|
| 25 | + |
| 26 | +# ---- Routes ---- |
21 | 27 | @app.get("/schemas") |
22 | | -async def get_all_schemas(): |
| 28 | +async def get_all_schemas() -> List[Dict[str, Any]]: |
23 | 29 | """ |
24 | 30 | Retrieve all schema documents from the database. |
25 | | - Converts MongoDB ObjectId to string for JSON serialization. |
| 31 | +
|
26 | 32 | Returns: |
27 | | - list: A list of schema documents with stringified IDs. |
| 33 | + list: A list of schema documents with JSON-safe '_id' fields (strings). |
28 | 34 | """ |
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 | + |
36 | 39 |
|
37 | 40 | @app.post("/schemas") |
38 | | -async def add_schema(schema: SchemaDefinition): |
| 41 | +async def add_schema(schema: SchemaDefinition) -> Dict[str, Any]: |
39 | 42 | """ |
40 | 43 | Add a new schema to the database. |
| 44 | +
|
41 | 45 | Args: |
42 | 46 | schema (SchemaDefinition): The schema data to insert. |
| 47 | +
|
43 | 48 | 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. |
46 | 53 | """ |
47 | | - # Update timestamp before insertion |
| 54 | + # Set server-side timestamp |
48 | 55 | schema.updated_at = datetime.utcnow() |
49 | 56 |
|
50 | | - # Insert schema into MongoDB |
| 57 | + # Insert and obtain new ObjectId |
51 | 58 | 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}) |
53 | 66 |
|
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} |
59 | 72 |
|
60 | | - # Return as { "<id>": <document> } per your requirement |
61 | | - return {str(inserted_id): inserted_doc} |
62 | 73 |
|
63 | 74 | @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]: |
65 | 76 | """ |
66 | 77 | Update an existing schema by ID. |
| 78 | +
|
67 | 79 | Args: |
68 | | - id (str): The schema ID. |
| 80 | + id (str): The schema ID (string form of ObjectId). |
69 | 81 | update (UpdateSchema): Fields to update. |
| 82 | +
|
70 | 83 | Raises: |
71 | 84 | HTTPException: If the schema is not found. |
| 85 | +
|
72 | 86 | Returns: |
73 | | - dict: Success message. |
| 87 | + dict: Success message and (optionally) the updated document. |
74 | 88 | """ |
75 | 89 | # 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() |
79 | 95 |
|
80 | 96 | result = schemas_collection.update_one( |
81 | 97 | {"_id": ObjectId(id)}, |
82 | | - {"$set": update_fields}, |
| 98 | + {"$set": update_fields} if update_fields else {} |
83 | 99 | ) |
84 | | - if result.modified_count == 0 and result.matched_count == 0: |
| 100 | + |
| 101 | + if result.matched_count == 0: |
85 | 102 | raise HTTPException(status_code=404, detail="Schema not found") |
86 | 103 |
|
| 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 | + |
87 | 111 | return {"message": "Schema updated"} |
88 | 112 |
|
| 113 | + |
89 | 114 | @app.delete("/schemas/{id}") |
90 | | -async def delete_schema(id: str): |
| 115 | +async def delete_schema(id: str) -> Dict[str, str]: |
91 | 116 | """ |
92 | 117 | Delete a schema by ID. |
| 118 | +
|
93 | 119 | Args: |
94 | | - id (str): The schema ID. |
| 120 | + id (str): The schema ID (string form of ObjectId). |
| 121 | +
|
95 | 122 | Raises: |
96 | 123 | HTTPException: If the schema is not found. |
| 124 | +
|
97 | 125 | Returns: |
98 | 126 | dict: Success message. |
99 | 127 | """ |
100 | 128 | result = schemas_collection.delete_one({"_id": ObjectId(id)}) |
| 129 | + |
101 | 130 | if result.deleted_count == 0: |
102 | 131 | raise HTTPException(status_code=404, detail="Schema not found") |
| 132 | + |
103 | 133 | return {"message": "Schema deleted"} |
0 commit comments