From 70306039313b3e8c6468147be67b044889f8fbb6 Mon Sep 17 00:00:00 2001 From: Abir Molla Date: Sat, 23 May 2026 06:10:45 +0600 Subject: [PATCH 1/3] Fix: Correct indentation in items.py CRUD endpoints --- app/routers/items.py | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/app/routers/items.py b/app/routers/items.py index 467d0f5..4c5a1a4 100644 --- a/app/routers/items.py +++ b/app/routers/items.py @@ -10,19 +10,20 @@ class ItemCreate(BaseModel): - name: str - description: str = "" + name: str + description: str = "" @router.get("/items") async def list_items(user: dict = Depends(get_current_user)): - """List all items for the authenticated user (cached 5 min).""" - user_id = user["sub"] - cache_key = f"items:{user_id}" - cached = await cache_get(cache_key) - if cached is not None: - return {"items": cached, "cached": True} - db = get_supabase() + """List all items for the authenticated user (cached 5 min).""" + user_id = user["sub"] + cache_key = f"items:{user_id}" + cached = await cache_get(cache_key) + if cached is not None: + return {"items": cached, "cached": True} + + db = get_supabase() result = db.table("items").select("*").eq("user_id", user_id).execute() await cache_set(cache_key, result.data, ttl_seconds=300) return {"items": result.data, "cached": False} @@ -30,30 +31,35 @@ async def list_items(user: dict = Depends(get_current_user)): @router.post("/items", status_code=201) async def create_item(item: ItemCreate, user: dict = Depends(get_current_user)): - """Create a new item for the authenticated user.""" + """Create a new item for the authenticated user.""" user_id = user["sub"] db = get_supabase() result = db.table("items").insert({ - "name": item.name, - "description": item.description, - "user_id": user_id, + "name": item.name, + "description": item.description, + "user_id": user_id, }).execute() + if not result.data: - raise HTTPException(status_code=500, detail="Failed to create item") - from app.cache import get_redis + raise HTTPException(status_code=500, detail="Failed to create item") + + from app.cache import get_redis r = get_redis() await r.delete(f"items:{user_id}") + return result.data[0] @router.delete("/items/{item_id}", status_code=204) async def delete_item(item_id: str, user: dict = Depends(get_current_user)): - """Delete an item (only if it belongs to the authenticated user).""" + """Delete an item (only if it belongs to the authenticated user).""" user_id = user["sub"] db = get_supabase() result = db.table("items").delete().eq("id", item_id).eq("user_id", user_id).execute() + if not result.data: - raise HTTPException(status_code=404, detail="Item not found") - from app.cache import get_redis + raise HTTPException(status_code=404, detail="Item not found") + + from app.cache import get_redis r = get_redis() await r.delete(f"items:{user_id}") From a8942a96e91fce2ddf78000225f6ed4e41613ab2 Mon Sep 17 00:00:00 2001 From: Abir Molla Date: Sat, 23 May 2026 06:11:28 +0600 Subject: [PATCH 2/3] Fix: Correct indentation in auth.py JWT verification --- app/auth.py | 63 ++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/app/auth.py b/app/auth.py index cbb9e53..c4ac444 100644 --- a/app/auth.py +++ b/app/auth.py @@ -18,45 +18,48 @@ async def _get_jwks() -> dict: - global _jwks_cache, _jwks_fetched_at - now = time.time() - if _jwks_cache and (now - _jwks_fetched_at) < JWKS_TTL: - return _jwks_cache - async with httpx.AsyncClient() as client: - resp = await client.get(settings.clerk_jwks_url, timeout=10) - resp.raise_for_status() - _jwks_cache = resp.json() + """Fetch and cache JWKS from Clerk.""" + global _jwks_cache, _jwks_fetched_at + now = time.time() + if _jwks_cache and (now - _jwks_fetched_at) < JWKS_TTL: + return _jwks_cache + + async with httpx.AsyncClient() as client: + resp = await client.get(settings.clerk_jwks_url, timeout=10) + resp.raise_for_status() + _jwks_cache = resp.json() + _jwks_fetched_at = now return _jwks_cache async def get_current_user( - credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme), + credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme), ) -> dict: - """Verifies Clerk JWT and returns decoded payload with user info.""" + """Verifies Clerk JWT and returns decoded payload with user info.""" token = credentials.credentials jwks = await _get_jwks() try: - header = jwt.get_unverified_header(token) - kid = header.get("kid") - key = next( - (k for k in jwks.get("keys", []) if k.get("kid") == kid), None - ) - if key is None: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="No matching JWK found for token kid", - ) - - public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key) - payload = jwt.decode( - token, public_key, algorithms=["RS256"], - options={"verify_aud": False}, - ) - return payload - -except jwt.ExpiredSignatureError: + header = jwt.get_unverified_header(token) + kid = header.get("kid") + key = next( + (k for k in jwks.get("keys", []) if k.get("kid") == kid), None + ) + if key is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="No matching JWK found for token kid", + ) + + public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key) + payload = jwt.decode( + token, public_key, algorithms=["RS256"], + options={"verify_aud": False}, + ) + return payload + + except jwt.ExpiredSignatureError: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired") -except jwt.InvalidTokenError as exc: + except jwt.InvalidTokenError as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Invalid token: {exc}") From 3621492e0b6038c112e56788b63a924b3b8009ae Mon Sep 17 00:00:00 2001 From: Abir Molla Date: Sat, 23 May 2026 08:10:22 +0600 Subject: [PATCH 3/3] Add GitHub Actions workflow for Python application This workflow installs Python dependencies, runs linting with flake8, and executes tests using pytest. --- .github/workflows/python-app.yml | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..1168bd9 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,39 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest