From 1ad86a5e3ba6f9eb208c0d0061f95d2048cf92e1 Mon Sep 17 00:00:00 2001 From: Keshav Dayal Date: Thu, 18 Jun 2026 08:14:04 +0530 Subject: [PATCH 1/9] Implement middleware management controllers --- docs/middleware.md | 13 ++ pro_tes/api/middlewares/__init__.py | 3 + pro_tes/api/middlewares/controllers.py | 195 +++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 pro_tes/api/middlewares/__init__.py create mode 100644 pro_tes/api/middlewares/controllers.py diff --git a/docs/middleware.md b/docs/middleware.md index 1602322..cf6f2e8 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -31,4 +31,17 @@ pro_tes/ docs/ └── middleware.md (This documentation) +``` + +## Implementation Status + +The middleware management controller layer is now implemented in +[`pro_tes/api/middlewares/controllers.py`](../pro_tes/api/middlewares/controllers.py) +and wired through the OpenAPI configuration in +[`pro_tes/config.yaml`](../pro_tes/config.yaml). + +Validated with: + +```bash +pytest tests/unitTest/pro_tes/api/middlewares/test_controllers.py -v ``` \ No newline at end of file diff --git a/pro_tes/api/middlewares/__init__.py b/pro_tes/api/middlewares/__init__.py new file mode 100644 index 0000000..7269fd5 --- /dev/null +++ b/pro_tes/api/middlewares/__init__.py @@ -0,0 +1,3 @@ +"""Middleware management API package.""" + +__all__ = ["controllers"] \ No newline at end of file diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py new file mode 100644 index 0000000..47f4799 --- /dev/null +++ b/pro_tes/api/middlewares/controllers.py @@ -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 + +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]: + """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 + + # 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 + + return { + "id": str(res.inserted_id), + "order": doc["order"], + "message": "Middleware added successfully", + } + + +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} + + if set(ordered_ids) != set(existing_ids.keys()): + 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): + 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, + } \ No newline at end of file From e15269c4bd42e8ab407f885fec5b8fb0850e28ed Mon Sep 17 00:00:00 2001 From: Keshav Dayal Date: Thu, 18 Jun 2026 08:24:28 +0530 Subject: [PATCH 2/9] feat: fix lint issues --- pro_tes/api/middlewares/__init__.py | 2 +- pro_tes/api/middlewares/controllers.py | 96 +++++++++++++++++++++----- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/pro_tes/api/middlewares/__init__.py b/pro_tes/api/middlewares/__init__.py index 7269fd5..b1b8a1f 100644 --- a/pro_tes/api/middlewares/__init__.py +++ b/pro_tes/api/middlewares/__init__.py @@ -1,3 +1,3 @@ """Middleware management API package.""" -__all__ = ["controllers"] \ No newline at end of file +__all__ = ["controllers"] diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py index 47f4799..de35d71 100644 --- a/pro_tes/api/middlewares/controllers.py +++ b/pro_tes/api/middlewares/controllers.py @@ -10,7 +10,7 @@ from bson.objectid import ObjectId from flask import current_app -from werkzeug.exceptions import BadRequest, NotFound, Conflict +from werkzeug.exceptions import BadRequest, NotFound logger = logging.getLogger(__name__) @@ -32,6 +32,15 @@ def _doc_to_response(doc: Dict[str, Any]) -> Dict[str, Any]: return result +def _utc_now() -> str: + return ( + datetime.now(timezone.utc) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z") + ) + + def ListMiddlewares(page_size: int = 50, page: int = 0, sort_by: str = "order", @@ -50,10 +59,21 @@ def ListMiddlewares(page_size: int = 50, 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) + 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) + cursor = cursor.skip(page * page_size).limit(page_size) middlewares = [_doc_to_response(doc) for doc in cursor] @@ -77,7 +97,7 @@ def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: coll = _collection() # Prepare document - now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") + now = _utc_now() doc: Dict[str, Any] = {} doc.update(body) doc["created_at"] = now @@ -94,7 +114,7 @@ def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: # Insert try: res = coll.insert_one(doc) - except Exception as exc: + except Exception: logger.exception("Failed to insert middleware") raise @@ -113,18 +133,34 @@ def GetMiddleware(middleware_id: str) -> Dict[str, Any]: 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}) + 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]: +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} + 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") @@ -133,12 +169,20 @@ def UpdateMiddleware(middleware_id: str, body: Dict[str, Any]) -> Dict[str, Any] except Exception: raise BadRequest("Invalid middleware id") - update_fields["updated_at"] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") + update_fields["updated_at"] = _utc_now() 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}, + projection={ + "_id": True, + "name": True, + "source": True, + "order": True, + "config": True, + "created_at": True, + "updated_at": True, + }, return_document=True, ) if updated is None: @@ -162,7 +206,7 @@ def DeleteMiddleware(middleware_id: str) -> tuple: def ReorderMiddlewares(body: Dict[str, Any]) -> Dict[str, Any]: - """Reorder middleware stack by provided ordered list of IDs.""" + """Reorder middleware stack by ordered 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"] @@ -170,26 +214,40 @@ def ReorderMiddlewares(body: Dict[str, Any]) -> Dict[str, Any]: 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} if set(ordered_ids) != set(existing_ids.keys()): - raise BadRequest("`ordered_ids` must contain exactly all middleware 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") + now = _utc_now() 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}) + 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, - } \ No newline at end of file + } From b227034e9bb5b2216a49bf08600cbc54bf7a1c34 Mon Sep 17 00:00:00 2001 From: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:30:23 +0530 Subject: [PATCH 3/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> --- docs/middleware.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/middleware.md b/docs/middleware.md index cf6f2e8..b0af268 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -40,8 +40,5 @@ The middleware management controller layer is now implemented in and wired through the OpenAPI configuration in [`pro_tes/config.yaml`](../pro_tes/config.yaml). -Validated with: - -```bash -pytest tests/unitTest/pro_tes/api/middlewares/test_controllers.py -v -``` \ No newline at end of file +Unit tests for the middleware management controllers are expected under +tests/unitTest/pro_tes/api/middlewares/ (e.g. test_controllers.py). \ No newline at end of file From 41c1609e69c7fd3ce6cc9d70fe3b240843c23100 Mon Sep 17 00:00:00 2001 From: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:30:51 +0530 Subject: [PATCH 4/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> --- pro_tes/api/middlewares/controllers.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py index de35d71..58a9f76 100644 --- a/pro_tes/api/middlewares/controllers.py +++ b/pro_tes/api/middlewares/controllers.py @@ -100,6 +100,23 @@ def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: now = _utc_now() doc: Dict[str, Any] = {} doc.update(body) + doc.pop("_id", None) + doc.pop("id", None) + + source_val = doc.get("source") + if isinstance(source_val, dict) and source_val.get("entry_point"): + doc["class_path"] = source_val["entry_point"] + elif isinstance(source_val, list): + entry_points = [ + s.get("entry_point") + for s in source_val + if isinstance(s, dict) and s.get("entry_point") + ] + doc["class_path"] = "|".join(entry_points) if entry_points else None + + if not doc.get("class_path"): + raise BadRequest("`source.entry_point` is required") + doc["created_at"] = now doc["updated_at"] = now From 42fbf9feacabb62fd8b6fb1296645ba29718e31f Mon Sep 17 00:00:00 2001 From: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:31:05 +0530 Subject: [PATCH 5/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> --- pro_tes/api/middlewares/controllers.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py index 58a9f76..90fd387 100644 --- a/pro_tes/api/middlewares/controllers.py +++ b/pro_tes/api/middlewares/controllers.py @@ -135,11 +135,14 @@ def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: logger.exception("Failed to insert middleware") raise - return { - "id": str(res.inserted_id), - "order": doc["order"], - "message": "Middleware added successfully", - } + return ( + { + "id": str(res.inserted_id), + "order": doc["order"], + "message": "Middleware added successfully", + }, + 201, + ) def GetMiddleware(middleware_id: str) -> Dict[str, Any]: From 5fc44369e01a0a7e16ec4485f285e45473e18e5e Mon Sep 17 00:00:00 2001 From: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:31:18 +0530 Subject: [PATCH 6/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Keshav Dayal <115068840+keshxvdayal@users.noreply.github.com> --- pro_tes/api/middlewares/controllers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py index 90fd387..2ffc67f 100644 --- a/pro_tes/api/middlewares/controllers.py +++ b/pro_tes/api/middlewares/controllers.py @@ -237,6 +237,9 @@ def ReorderMiddlewares(body: Dict[str, Any]) -> Dict[str, Any]: existing = list(coll.find({}, {"_id": True})) existing_ids = {str(d["_id"]): d for d in existing} + if len(ordered_ids) != len(set(ordered_ids)): + raise BadRequest("`ordered_ids` must not contain duplicate ids") + if set(ordered_ids) != set(existing_ids.keys()): raise BadRequest( "`ordered_ids` must contain exactly all middleware ids" From 4ce4ffd1dd39cdfe5c8e7cfe3e611cebc78f1525 Mon Sep 17 00:00:00 2001 From: Keshav Dayal Date: Thu, 18 Jun 2026 08:34:59 +0530 Subject: [PATCH 7/9] feat:lint fixing rephrasing --- pro_tes/api/middlewares/controllers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py index 2ffc67f..718571d 100644 --- a/pro_tes/api/middlewares/controllers.py +++ b/pro_tes/api/middlewares/controllers.py @@ -146,7 +146,7 @@ def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: def GetMiddleware(middleware_id: str) -> Dict[str, Any]: - """Return middleware details by id.""" + """Retrieve middleware details by id.""" try: oid = ObjectId(middleware_id) except Exception: From 1f275bc8bec110901af692db2c914e09ddaeb779 Mon Sep 17 00:00:00 2001 From: Keshav Dayal Date: Thu, 18 Jun 2026 08:40:13 +0530 Subject: [PATCH 8/9] feat: flake8 lint issue fix --- pro_tes/api/middlewares/controllers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py index 718571d..b185c2b 100644 --- a/pro_tes/api/middlewares/controllers.py +++ b/pro_tes/api/middlewares/controllers.py @@ -174,7 +174,7 @@ def UpdateMiddleware( middleware_id: str, body: Dict[str, Any], ) -> Dict[str, Any]: - """Partially update middleware (only `name` and `config`).""" + """Update middleware partially (only `name` and `config`).""" if not isinstance(body, dict): raise BadRequest("Request body must be an object") allowed = {"name", "config"} From 5bca68ea860ba830a60a92636febc6c7f785e982 Mon Sep 17 00:00:00 2001 From: Keshav Dayal Date: Thu, 18 Jun 2026 08:45:40 +0530 Subject: [PATCH 9/9] feat: typecheck with mypy issue fix --- pro_tes/api/middlewares/controllers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pro_tes/api/middlewares/controllers.py b/pro_tes/api/middlewares/controllers.py index b185c2b..77d1581 100644 --- a/pro_tes/api/middlewares/controllers.py +++ b/pro_tes/api/middlewares/controllers.py @@ -89,7 +89,7 @@ def ListMiddlewares(page_size: int = 50, } -def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: +def AddMiddleware(body: Dict[str, Any]) -> tuple: """Add a middleware to the end of the execution stack.""" if not isinstance(body, dict): raise BadRequest("Request body must be an object") @@ -108,7 +108,7 @@ def AddMiddleware(body: Dict[str, Any]) -> Dict[str, Any]: doc["class_path"] = source_val["entry_point"] elif isinstance(source_val, list): entry_points = [ - s.get("entry_point") + s["entry_point"] for s in source_val if isinstance(s, dict) and s.get("entry_point") ]