replace legacy ratings route with group ratings
This commit is contained in:
parent
a39a0e6442
commit
75391b1ca2
6 changed files with 207 additions and 16 deletions
106
unwind/web.py
106
unwind/web.py
|
|
@ -1,6 +1,6 @@
|
|||
import base64
|
||||
import binascii
|
||||
import logging
|
||||
import secrets
|
||||
from json.decoder import JSONDecodeError
|
||||
from typing import Literal, Optional
|
||||
|
||||
from starlette.applications import Starlette
|
||||
|
|
@ -12,6 +12,7 @@ from starlette.authentication import (
|
|||
UnauthenticatedUser,
|
||||
requires,
|
||||
)
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
|
|
@ -20,7 +21,9 @@ from starlette.routing import Mount, Route
|
|||
from . import config, db
|
||||
from .db import close_connection_pool, find_ratings, open_connection_pool
|
||||
from .middleware.responsetime import ResponseTimeMiddleware
|
||||
from .models import Movie, asplain
|
||||
from .models import Group, Movie, asplain
|
||||
from .types import ULID
|
||||
from .utils import b64encode, phc_compare, phc_scrypt
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -81,7 +84,22 @@ def as_int(x, *, max: int = None, min: Optional[int] = 1, default: int = None):
|
|||
return default
|
||||
|
||||
|
||||
async def ratings(request):
|
||||
def as_ulid(s: str) -> ULID:
|
||||
try:
|
||||
return ULID(s)
|
||||
except ValueError:
|
||||
raise HTTPException(422, "Not a valid ULID.")
|
||||
|
||||
|
||||
async def get_ratings_for_group(request):
|
||||
group_id = as_ulid(request.path_params["group_id"])
|
||||
group = await db.get(Group, id=str(group_id))
|
||||
|
||||
if not group:
|
||||
return not_found()
|
||||
|
||||
user_ids = {u["id"] for u in group.users}
|
||||
|
||||
params = request.query_params
|
||||
rows = await find_ratings(
|
||||
title=params.get("title"),
|
||||
|
|
@ -91,6 +109,7 @@ async def ratings(request):
|
|||
include_unrated=truthy(params.get("include_unrated")),
|
||||
yearcomp=yearcomp(params["year"]) if "year" in params else None,
|
||||
limit_rows=as_int(params.get("per_page"), max=10, default=5),
|
||||
user_ids=user_ids,
|
||||
)
|
||||
|
||||
aggr = {}
|
||||
|
|
@ -107,7 +126,7 @@ async def ratings(request):
|
|||
"media_type": r["media_type"],
|
||||
},
|
||||
)
|
||||
if r["user_score"] is not None:
|
||||
if r["user_score"] is not None and r["user_id"] in user_ids:
|
||||
mov["user_scores"].append(r["user_score"])
|
||||
|
||||
resp = tuple(aggr.values())
|
||||
|
|
@ -115,7 +134,16 @@ async def ratings(request):
|
|||
return JSONResponse(resp)
|
||||
|
||||
|
||||
not_found = JSONResponse({"error": "Not Found"}, status_code=404)
|
||||
def unauthorized(reason: str = "Unauthorized"):
|
||||
return JSONResponse({"error": reason}, status_code=401)
|
||||
|
||||
|
||||
def forbidden(reason: str = "Forbidden"):
|
||||
return JSONResponse({"error": reason}, status_code=403)
|
||||
|
||||
|
||||
def not_found(reason: str = "Not Found"):
|
||||
return JSONResponse({"error": reason}, status_code=404)
|
||||
|
||||
|
||||
async def get_movies(request):
|
||||
|
|
@ -148,16 +176,73 @@ async def set_rating_for_user(request):
|
|||
|
||||
@requires(["authenticated", "admin"])
|
||||
async def add_group(request):
|
||||
pass
|
||||
if not await request.body():
|
||||
data = {}
|
||||
else:
|
||||
try:
|
||||
data = await request.json()
|
||||
except JSONDecodeError:
|
||||
raise HTTPException(422, "Invalid JSON content.")
|
||||
|
||||
try:
|
||||
name = data["name"]
|
||||
except KeyError as err:
|
||||
raise HTTPException(422, f"Missing data for key: {err.args[0]}")
|
||||
|
||||
# XXX restrict name
|
||||
|
||||
secret = secrets.token_bytes()
|
||||
|
||||
group = Group(name=name, secret=phc_scrypt(secret))
|
||||
await db.add(group)
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"secret": b64encode(secret),
|
||||
"group": asplain(group),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@requires(["authenticated", "admin"])
|
||||
async def add_user_to_group(request):
|
||||
request.path_params["group_id"]
|
||||
group_id = as_ulid(request.path_params["group_id"])
|
||||
group = await db.get(Group, id=str(group_id))
|
||||
|
||||
if not group:
|
||||
return not_found()
|
||||
|
||||
async def get_ratings_for_group(request):
|
||||
request.path_params["group_id"]
|
||||
is_allowed = "admin" in request.auth.scopes or phc_compare(
|
||||
secret=request.user.token, phc_string=group.secret
|
||||
)
|
||||
if not is_allowed:
|
||||
return forbidden()
|
||||
|
||||
if not await request.body():
|
||||
data = {}
|
||||
else:
|
||||
try:
|
||||
data = await request.json()
|
||||
except JSONDecodeError:
|
||||
raise HTTPException(422, "Invalid JSON content.")
|
||||
|
||||
try:
|
||||
name = data["name"]
|
||||
user_id = data["id"]
|
||||
except KeyError as err:
|
||||
raise HTTPException(422, f"Missing data for key: {err.args[0]}")
|
||||
|
||||
# XXX check if user exists
|
||||
# XXX restrict name
|
||||
|
||||
if any(u["id"] == user_id for u in group.users):
|
||||
pass
|
||||
else:
|
||||
group.users.append({"name": name, "id": user_id})
|
||||
|
||||
await db.update(group)
|
||||
|
||||
return JSONResponse(asplain(group))
|
||||
|
||||
|
||||
def create_app():
|
||||
|
|
@ -176,7 +261,6 @@ def create_app():
|
|||
Mount(
|
||||
"/api/v1",
|
||||
routes=[
|
||||
Route("/ratings", ratings), # XXX legacy, remove.
|
||||
Route("/movies", get_movies),
|
||||
Route("/movies", add_movie, methods=["POST"]),
|
||||
Route("/users", add_user, methods=["POST"]),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue