4 Commits

7 changed files with 90 additions and 28 deletions

View File

@@ -38,7 +38,7 @@ PUBLIC_URL=http://localhost:8901
# Set `verified` to true if you don't wanna manually verify users.
EXTRA_CLAIMS='{"permissions": ["core.read", "core.play"], "verified": false}'
# This is the permissions of the first user (aka the first user is admin)
FIRST_USER_CLAIMS='{"permissions": ["users.read", "users.write", "apikeys.read", "apikeys.write", "users.delete", "core.read", "core.write", "core.play", "scanner.trigger"], "verified": true}'
FIRST_USER_CLAIMS='{"permissions": ["users.read", "users.write", "users.delete", "apikeys.read", "apikeys.write", "core.read", "core.write", "core.play", "scanner.trigger"], "verified": true}'
# Guest (meaning unlogged in users) can be:
# unauthorized (they need to connect before doing anything)

View File

@@ -211,6 +211,7 @@ func (h *Handler) createApiJwt(apikey string) (string, error) {
Time: time.Now().UTC().Add(time.Hour),
}
jwt := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
jwt.Header["kid"] = h.config.JwtKid
t, err := jwt.SignedString(h.config.JwtPrivateKey)
if err != nil {
return "", err

View File

@@ -20,5 +20,5 @@ create table scanner.requests(
status scanner.request_status not null default 'pending',
started_at timestamptz,
created_at timestamptz not null default now()::timestamptz,
constraint unique_kty unique(kind, title, year)
constraint unique_kty unique nulls not distinct (kind, title, year)
);

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import Field
@@ -18,3 +19,16 @@ class Request(Model, extra="allow"):
class Video(Model):
id: str
episodes: list[Guess.Episode]
class RequestRet(Model):
id: str
kind: Literal["episode", "movie"]
title: str
year: int | None
status: Literal[
"pending",
"running",
"failed",
]
started_at: datetime | None

View File

@@ -0,0 +1,15 @@
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
def get_health():
return {"status": "healthy"}
@router.get("/ready")
def get_ready():
# child spans (`select 1` & db connection reset) was still logged,
# since i don't really wanna deal with it, let's just do that.
return {"status": "healthy"}

View File

@@ -1,9 +1,9 @@
from typing import Annotated
from typing import Annotated, Literal
from asyncpg import Connection
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Security
from fastapi import APIRouter, BackgroundTasks, Depends, Security
from scanner.database import get_db_fapi
from scanner.models.request import RequestRet
from scanner.status import StatusService
from ..fsscan import create_scanner
from ..jwt import validate_bearer
@@ -11,6 +11,19 @@ from ..jwt import validate_bearer
router = APIRouter()
@router.get("/scan")
async def get_scan_status(
svc: Annotated[StatusService, Depends(StatusService.create)],
_: Annotated[None, Security(validate_bearer, scopes=["scanner.trigger"])],
status: Literal["pending", "running", "failed"] | None = None,
) -> list[RequestRet]:
"""
Get scan status, know what tasks are running, pending or failed.
"""
return await svc.list_requests(status=status)
@router.put(
"/scan",
status_code=204,
@@ -29,25 +42,3 @@ async def trigger_scan(
await scanner.scan()
tasks.add_task(run)
@router.get("/health")
def get_health():
return {"status": "healthy"}
@router.get("/ready")
def get_ready():
# child spans (`select 1` & db connection reset) was still logged,
# since i don't really wanna deal with it, let's just do that.
return {"status": "healthy"}
# async def get_ready(db: Annotated[Connection, Depends(get_db_fapi)]):
# try:
# _ = await db.execute("select 1")
# return {"status": "healthy", "database": "healthy"}
# except Exception as e:
# raise HTTPException(
# status_code=500, detail={"status": "unhealthy", "database": str(e)}
# )

41
scanner/scanner/status.py Normal file
View File

@@ -0,0 +1,41 @@
from typing import Literal
from asyncpg import Connection
from pydantic import TypeAdapter
from scanner.database import get_db
from .models.request import RequestRet
class StatusService:
def __init__(self, database: Connection):
self._database = database
@classmethod
async def create(cls):
async with get_db() as db:
yield StatusService(db)
async def list_requests(
self, *, status: Literal["pending", "running", "failed"] | None = None
) -> list[RequestRet]:
ret = await self._database.fetch(
f"""
select
pk::text as id,
kind,
title,
year,
status,
started_at
from
scanner.requests
order by
started_at,
pk
{"where status = $1" if status is not None else ""}
""",
*([status] if status is not None else []),
)
return TypeAdapter(list[RequestRet]).validate_python(ret)