From 2bf6bc6e773691bc094bd83ffb8c4fde419b402f Mon Sep 17 00:00:00 2001 From: ducklet Date: Mon, 8 Apr 2024 22:09:18 +0200 Subject: [PATCH] wip --- unwind/models.py | 42 +++++++++++++++++++++++++++++++ unwind/web.py | 64 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/unwind/models.py b/unwind/models.py index 6ea13e9..aae161b 100644 --- a/unwind/models.py +++ b/unwind/models.py @@ -457,6 +457,48 @@ class Rating: ratings = Rating.__table__ +# TODO +# - distinguish between ratings & watches +# - they are completely separate +# - I can rate something at any time, without having watched it, e.g. in a discussion with a friend I change my opinion on a movie +# - I can watch something without having fully formed an opinion yet, i.e. I don't want to rate it yet + + +@mapper_registry.mapped +@dataclass +class Watch: + __table__: ClassVar[Table] = Table( + "watches", + metadata, + Column("id", String, primary_key=True), # ULID + Column("movie_id", ForeignKey("movies.id"), nullable=False), # ULID + Column("user_id", ForeignKey("users.id"), nullable=False), # ULID + Column("started", String, nullable=False), # datetime + Column("finished", String), # datetime + Column("geoloc", String), # geo coords + Column("score", Integer), # + Column("favorite", Integer), # bool + ) + + id: ULID = field(default_factory=ULID) + + movie_id: ULID = None + movie: Relation[Movie] = None + + user_id: ULID = None + user: Relation[User] = None + + started: datetime | None = None + finished: datetime | None = None + + geoloc: str | None = None + score: int | None = None # range: [0,100] + favorite: bool | None = None + + +watches = Rating.__table__ + + class GroupUser(TypedDict): id: str name: str diff --git a/unwind/web.py b/unwind/web.py index b4ba575..294e676 100644 --- a/unwind/web.py +++ b/unwind/web.py @@ -49,12 +49,12 @@ class BearerAuthBackend(AuthenticationBackend): self.admin_tokens = {v: k for k, v in credentials.items()} async def authenticate(self, conn: HTTPConnection): - if "Authorization" not in conn.headers: + if "authorization" not in conn.headers: return # XXX should we remove the auth header after reading, for security reasons? - auth = conn.headers["Authorization"] + auth = conn.headers["authorization"] try: scheme, credentials = auth.split() except ValueError as err: @@ -62,23 +62,24 @@ class BearerAuthBackend(AuthenticationBackend): roles = [] - if scheme.lower() == "bearer": - is_admin = credentials in self.admin_tokens - if not is_admin: + match scheme.lower(): + case "bearer": + is_admin = credentials in self.admin_tokens + if not is_admin: + return + name = self.admin_tokens[credentials] + user = SimpleUser(name) + roles.append("admin") + + case "basic": + try: + name, secret = b64decode(credentials).decode().split(":") + except Exception as err: + raise AuthenticationError("Invalid auth credentials") from err + user = AuthedUser(name, secret) + + case _: return - name = self.admin_tokens[credentials] - user = SimpleUser(name) - roles.append("admin") - - elif scheme.lower() == "basic": - try: - name, secret = b64decode(credentials).decode().split(":") - except Exception as err: - raise AuthenticationError("Invalid auth credentials") from err - user = AuthedUser(name, secret) - - else: - return return AuthCredentials(["authenticated", *roles]), user @@ -366,6 +367,33 @@ async def progress_for_load_imdb_movies(request): return JSONResponse(resp) +@route("/users/{user_id}/watches", methods=["POST"]) +@requires(["authenticated"]) +async def users_watching_start(request): + # { + # id + # movie_id + # location (gps) + # started + # finished + # score + # fav + # } + pass + + +@route("/users/{user_id}/watches/{id}", methods=["PUT"]) +@requires(["authenticated"]) +async def users_watching_done(request): + # { + # ... + # finished + # score + # fav + # } + pass + + _import_lock = asyncio.Lock()