From 7e5a8f89b567a1214acd9e04d6fbf29698f151cb Mon Sep 17 00:00:00 2001 From: mwiegand Date: Wed, 6 May 2026 20:53:50 +0200 Subject: [PATCH] docs: add server port constraint implementation plan --- .../2026-05-06-server-port-constraint.md | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-06-server-port-constraint.md diff --git a/docs/superpowers/plans/2026-05-06-server-port-constraint.md b/docs/superpowers/plans/2026-05-06-server-port-constraint.md new file mode 100644 index 0000000..671627c --- /dev/null +++ b/docs/superpowers/plans/2026-05-06-server-port-constraint.md @@ -0,0 +1,147 @@ +# Server Port Constraint Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Ensure servers cannot share the same port by enforcing uniqueness at the database level and handling the constraint violation in the web UI. + +**Architecture:** We will add a unique constraint to the `Server.port` column, generate an Alembic migration, and update the `/servers` POST route to catch `IntegrityError` when a port conflict occurs, returning a 409 status code. + +**Tech Stack:** Python, Flask, SQLAlchemy, Alembic, Pytest + +--- + +### Task 1: Add Unique Constraint to Server Port + +**Files:** +- Modify: `l4d2web/models.py` +- Modify: `l4d2web/routes/server_routes.py` +- Create: `l4d2web/alembic/versions/XXXX_make_server_port_unique.py` (via alembic) + +- [ ] **Step 1: Update the database model** + +Update `l4d2web/models.py` to add `unique=True` to the `port` column on the `Server` model. + +```python +class Server(Base): + __tablename__ = "servers" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) + blueprint_id: Mapped[int] = mapped_column(ForeignKey("blueprints.id"), nullable=False) + name: Mapped[str] = mapped_column(String(128), unique=True, nullable=False) + port: Mapped[int] = mapped_column(Integer, unique=True, nullable=False) + # ... rest of the columns +``` + +- [ ] **Step 2: Generate the Alembic migration** + +Run: `PYTHONPATH=. alembic -c l4d2web/alembic.ini revision --autogenerate -m "make server port unique"` +Expected: Creates a new migration file in `l4d2web/alembic/versions/` + +- [ ] **Step 3: Update application logic** + +Update `l4d2web/routes/server_routes.py` to catch the `IntegrityError` when creating a server. + +```python +from sqlalchemy.exc import IntegrityError +# ... other imports + +@bp.post("/servers") +@require_login +def create_server() -> Response: + # ... existing user check and payload extraction + + with session_scope() as db: + blueprint = db.scalar( + select(BlueprintModel).where( + BlueprintModel.id == int(payload["blueprint_id"]), + BlueprintModel.user_id == user.id, + ) + ) + if blueprint is None: + return Response("blueprint not found", status=404) + + server = Server( + user_id=user.id, + blueprint_id=blueprint.id, + name=str(payload["name"]), + port=int(payload["port"]), + desired_state="stopped", + actual_state="unknown", + last_error="", + ) + db.add(server) + + try: + db.flush() + except IntegrityError: + db.rollback() + return Response("port already in use", status=409) + + server_id = server.id + + if json_response: + return jsonify({"id": server_id}), 201 + return redirect(f"/servers/{server_id}") +``` + +- [ ] **Step 4: Commit** + +```bash +git add l4d2web/models.py l4d2web/routes/server_routes.py l4d2web/alembic/versions/ +git commit -m "feat: enforce unique port constraint on servers" +``` + +### Task 2: Write Verification Tests + +**Files:** +- Modify: `l4d2web/tests/test_servers.py` + +- [ ] **Step 1: Write the failing test** + +Add a test case to `l4d2web/tests/test_servers.py` that verifies the unique port constraint. + +```python +def test_create_server_duplicate_port(client, auth, db_session): + auth.login() + + # First, create a blueprint + response = client.post( + "/blueprints", + data={"name": "my-blueprint", "arguments": "[]", "config": "[]"}, + ) + assert response.status_code == 302 + + # Then create the first server + response = client.post( + "/servers", + data={"name": "server-1", "port": "27015", "blueprint_id": "1"}, + ) + assert response.status_code == 302 + + # Try to create a second server with the same port + response = client.post( + "/servers", + data={"name": "server-2", "port": "27015", "blueprint_id": "1"}, + ) + assert response.status_code == 409 + assert b"port already in use" in response.data + + # Verify the second server was not created + from l4d2web.models import Server + servers = db_session.query(Server).all() + assert len(servers) == 1 + assert servers[0].name == "server-1" +``` + +- [ ] **Step 2: Run test to verify it passes** + +Run: `pytest l4d2web/tests/test_servers.py -v` +Expected: PASS (It passes because we implemented the code in Task 1. We're doing this slightly out of TDD order to group the DB/Route changes together). + +- [ ] **Step 3: Commit** + +```bash +git add l4d2web/tests/test_servers.py +git commit -m "test: add test for duplicate port constraint" +```