Coverage for backend / app / routers / coverage.py: 100%
40 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-02 15:51 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-02 15:51 +0000
1import mimetypes
3import re
4from typing import Annotated
6from fastapi import APIRouter, Depends, HTTPException, Path
7from fastapi.responses import Response
8from redis import RedisError
9from redis.asyncio import Redis
11from app.dependencies.redis_client import get_redis_client
12from app.dependencies.auth import verify_api_key
14from app.utils.aws_storage import AWSStorage, AWSStorageError
15from app.dependencies.aws_storage import get_aws_storage
16from app.constants import BADGE_CACHE_KEY
17from app.schemas import AWSUploadSessionResponse
19SAFE_PATH_RE = re.compile(r"^(?!.*(?:^|/)\.\.(?:/|$)).*$")
21SiteId = Annotated[str, Path(pattern=r"^[a-f0-9]{12}$")]
22SafePath = Annotated[str, Path(pattern=SAFE_PATH_RE)] # ty: ignore[invalid-argument-type]
24router = APIRouter()
27@router.post(
28 "/invalidate-cache/{repo_owner}/{repo_name}/",
29 dependencies=[Depends(verify_api_key)],
30)
31async def invalidate_cache(
32 repo_owner: str,
33 repo_name: str,
34 redis_client: Annotated[Redis, Depends(get_redis_client)],
35):
36 cache_key = BADGE_CACHE_KEY.format(org=repo_owner, repo=repo_name)
37 try:
38 await redis_client.delete(cache_key)
39 except RedisError as e:
40 print(f"Error invalidating cache for {repo_owner}/{repo_name}: {e}")
41 raise HTTPException(status_code=500, detail="Failed to invalidate cache")
43 return {"status": "success"}
46@router.get("/{site_id}/{path:path}")
47async def get_file(
48 site_id: SiteId,
49 path: SafePath,
50 aws_storage: Annotated[AWSStorage, Depends(get_aws_storage)],
51):
52 if path == "" or path.endswith("/"):
53 path = path + "index.html"
55 try:
56 content = await aws_storage.get_file(site_id, path)
57 except AWSStorageError:
58 return Response(status_code=404)
60 media_type, _ = mimetypes.guess_type(path)
61 return Response(
62 content=content, media_type=media_type or "application/octet-stream"
63 )
66@router.post("/create-site/", dependencies=[Depends(verify_api_key)])
67async def create_upload_session(
68 aws_storage: Annotated[AWSStorage, Depends(get_aws_storage)],
69) -> AWSUploadSessionResponse:
70 session = await aws_storage.create_upload_session()
71 return AWSUploadSessionResponse(
72 site_id=session.site_id,
73 bucket=session.bucket,
74 region=session.region,
75 access_key_id=session.access_key_id,
76 secret_access_key=session.secret_access_key,
77 session_token=session.session_token,
78 )