-
Notifications
You must be signed in to change notification settings - Fork 6
feat: Implement middleware management controllers #199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 1 commit
1ad86a5
e15269c
b227034
41c1609
42fbf9f
5fc4436
4ce4ffd
1f275bc
5bca68e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """Middleware management API package.""" | ||
|
|
||
| __all__ = ["controllers"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| """Controller implementations for middleware management endpoints. | ||
|
|
||
| These functions are referenced from the OpenAPI spec via | ||
| `x-openapi-router-controller: pro_tes.api.middlewares.controllers`. | ||
| """ | ||
| from math import ceil | ||
| import logging | ||
| from datetime import datetime, timezone | ||
| from typing import Any, Dict, List, Optional | ||
|
|
||
| from bson.objectid import ObjectId | ||
| from flask import current_app | ||
| from werkzeug.exceptions import BadRequest, NotFound, Conflict | ||
|
Copilot marked this conversation as resolved.
Outdated
|
||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def _collection(): | ||
| return ( | ||
| current_app.config.foca.db.dbs["taskStore"] # type: ignore | ||
| .collections["middlewares"] | ||
| .client | ||
| ) | ||
|
|
||
|
|
||
| def _doc_to_response(doc: Dict[str, Any]) -> Dict[str, Any]: | ||
| if doc is None: | ||
| return {} | ||
| result = {k: v for (k, v) in doc.items() if k != "_id"} | ||
| if "_id" in doc and doc["_id"] is not None: | ||
| result["id"] = str(doc["_id"]) | ||
| return result | ||
|
|
||
|
|
||
| def ListMiddlewares(page_size: int = 50, | ||
| page: int = 0, | ||
| sort_by: str = "order", | ||
| sort_order: str = "asc", | ||
| source: Optional[str] = None) -> Dict[str, Any]: | ||
|
Comment on lines
+44
to
+48
|
||
| """List middlewares with pagination and sorting.""" | ||
| if page_size < 1 or page_size > 100: | ||
| raise BadRequest("`page_size` must be between 1 and 100") | ||
| if page < 0: | ||
| raise BadRequest("`page` must be >= 0") | ||
|
|
||
| coll = _collection() | ||
|
|
||
| query: Dict[str, Any] = {} | ||
| if source: | ||
| query["source.type"] = source | ||
|
|
||
| total = coll.count_documents(query) | ||
| # sort | ||
| direction = 1 if sort_order == "asc" else -1 | ||
| cursor = coll.find(query, {"_id": True, "name": True, "source": True, "order": True, "config": True, "created_at": True, "updated_at": True}) | ||
| cursor = cursor.sort(sort_by, direction).skip(page * page_size).limit(page_size) | ||
|
|
||
| middlewares = [_doc_to_response(doc) for doc in cursor] | ||
|
|
||
| total_pages = ceil(total / page_size) if total > 0 else 0 | ||
| return { | ||
| "middlewares": middlewares, | ||
| "pagination": { | ||
| "page": page, | ||
| "page_size": page_size, | ||
| "total": total, | ||
| "total_pages": total_pages, | ||
| }, | ||
| } | ||
|
|
||
|
|
||
| def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: | ||
| """Add a middleware to the end of the execution stack.""" | ||
| if not isinstance(body, dict): | ||
| raise BadRequest("Request body must be an object") | ||
|
|
||
| coll = _collection() | ||
|
|
||
| # Prepare document | ||
| now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") | ||
| doc: Dict[str, Any] = {} | ||
| doc.update(body) | ||
| doc["created_at"] = now | ||
| doc["updated_at"] = now | ||
|
Copilot marked this conversation as resolved.
|
||
|
|
||
| # assign order = max(order)+1 | ||
| last = coll.find({}, {"order": True}).sort("order", -1).limit(1) | ||
| try: | ||
| last_doc = next(last, None) | ||
| except Exception: | ||
| last_doc = None | ||
| doc["order"] = 0 if not last_doc else int(last_doc.get("order", 0)) + 1 | ||
|
|
||
| # Insert | ||
| try: | ||
| res = coll.insert_one(doc) | ||
| except Exception as exc: | ||
| logger.exception("Failed to insert middleware") | ||
| raise | ||
|
Comment on lines
+131
to
+136
|
||
|
|
||
| return { | ||
| "id": str(res.inserted_id), | ||
| "order": doc["order"], | ||
| "message": "Middleware added successfully", | ||
| } | ||
|
Copilot marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| def GetMiddleware(middleware_id: str) -> Dict[str, Any]: | ||
| """Return middleware details by id.""" | ||
| try: | ||
| oid = ObjectId(middleware_id) | ||
| except Exception: | ||
| raise BadRequest("Invalid middleware id") | ||
|
|
||
| coll = _collection() | ||
| doc = coll.find_one({"_id": oid}, {"_id": True, "name": True, "source": True, "order": True, "config": True, "created_at": True, "updated_at": True}) | ||
| if doc is None: | ||
| raise NotFound(f"Middleware with ID '{middleware_id}' not found") | ||
| return _doc_to_response(doc) | ||
|
|
||
|
|
||
| def UpdateMiddleware(middleware_id: str, body: Dict[str, Any]) -> Dict[str, Any]: | ||
| """Partially update middleware (only `name` and `config`).""" | ||
| if not isinstance(body, dict): | ||
| raise BadRequest("Request body must be an object") | ||
| allowed = {"name", "config"} | ||
| update_fields = {k: v for (k, v) in body.items() if k in allowed} | ||
| if not update_fields: | ||
| raise BadRequest("Only `name` and `config` can be updated") | ||
|
|
||
| try: | ||
| oid = ObjectId(middleware_id) | ||
| except Exception: | ||
| raise BadRequest("Invalid middleware id") | ||
|
|
||
| update_fields["updated_at"] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") | ||
| coll = _collection() | ||
| updated = coll.find_one_and_update( | ||
| {"_id": oid}, | ||
| {"$set": update_fields}, | ||
| projection={"_id": True, "name": True, "source": True, "order": True, "config": True, "created_at": True, "updated_at": True}, | ||
| return_document=True, | ||
| ) | ||
| if updated is None: | ||
| raise NotFound(f"Middleware with ID '{middleware_id}' not found") | ||
| return _doc_to_response(updated) | ||
|
|
||
|
|
||
|
|
||
| def DeleteMiddleware(middleware_id: str) -> tuple: | ||
| """Delete middleware by id.""" | ||
| try: | ||
| oid = ObjectId(middleware_id) | ||
| except Exception: | ||
| raise BadRequest("Invalid middleware id") | ||
|
|
||
| coll = _collection() | ||
| res = coll.delete_one({"_id": oid}) | ||
| if res.deleted_count == 0: | ||
| raise NotFound(f"Middleware with ID '{middleware_id}' not found") | ||
| # Return empty body with 204 status code (Connexion will use this) | ||
| return ("", 204) | ||
|
|
||
|
|
||
| def ReorderMiddlewares(body: Dict[str, Any]) -> Dict[str, Any]: | ||
| """Reorder middleware stack by provided ordered list of IDs.""" | ||
| if not isinstance(body, dict) or "ordered_ids" not in body: | ||
| raise BadRequest("Request body must contain `ordered_ids` array") | ||
| ordered_ids = body["ordered_ids"] | ||
| if not isinstance(ordered_ids, list) or len(ordered_ids) == 0: | ||
| raise BadRequest("`ordered_ids` must be a non-empty array of ids") | ||
|
|
||
| coll = _collection() | ||
| # Fetch existing ids | ||
| existing = list(coll.find({}, {"_id": True})) | ||
| existing_ids = {str(d["_id"]): d for d in existing} | ||
|
Comment on lines
+237
to
+238
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: The Since only the keys are used later, Suggested implementation: coll = _collection()
# Fetch existing ids
existing = list(coll.find({}, {"_id": True}))
existing_ids = {str(d["_id"]) for d in existing}
# Validate that all provided ids are syntactically valid ObjectId strings
for mid in ordered_ids:
try:
ObjectId(mid)
except Exception:
raise BadRequest(f"Invalid id in ordered_ids: {mid}")
if set(ordered_ids) != existing_ids:
raise BadRequest("`ordered_ids` must contain exactly all middleware ids") # Apply new order
updated_docs: List[Dict[str, Any]] = []
now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
for idx, mid in enumerate(ordered_ids):
# At this point, all ids are known to be valid ObjectId strings
oid = ObjectId(mid) |
||
|
|
||
| if set(ordered_ids) != set(existing_ids.keys()): | ||
| raise BadRequest("`ordered_ids` must contain exactly all middleware ids") | ||
|
Copilot marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Apply new order | ||
| updated_docs: List[Dict[str, Any]] = [] | ||
| now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") | ||
| for idx, mid in enumerate(ordered_ids): | ||
| try: | ||
| oid = ObjectId(mid) | ||
| except Exception: | ||
| raise BadRequest(f"Invalid id in ordered_ids: {mid}") | ||
| coll.update_one({"_id": oid}, {"$set": {"order": idx, "updated_at": now}}) | ||
| doc = coll.find_one({"_id": oid}, {"_id": True, "name": True, "source": True, "order": True, "config": True, "created_at": True, "updated_at": True}) | ||
| updated_docs.append(_doc_to_response(doc)) | ||
|
|
||
| return { | ||
| "message": "Middleware stack reordered successfully", | ||
| "middlewares": updated_docs, | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.