From b32d0a5743d516a13c4575542b2c29cc6f143e05 Mon Sep 17 00:00:00 2001 From: cwilvx Date: Sun, 9 Jun 2024 16:14:09 +0300 Subject: [PATCH] add a pairing endpoint + append / to folder paths + filter recently played by logged in user id + fix typo in login response + update track logger migration to add foreign key --- TODO.md | 10 ++++----- app/api/auth.py | 36 +++++++++++++++++++++++++++++-- app/api/logger/__init__.py | 2 -- app/db/sqlite/logger/tracks.py | 13 ++++++----- app/db/sqlite/queries.py | 3 ++- app/lib/folderslib.py | 2 +- app/migrations/v1_4_9/__init__.py | 20 +++++++++++++++-- manage.py | 9 +++++++- 8 files changed, 76 insertions(+), 19 deletions(-) diff --git a/TODO.md b/TODO.md index 7237b471..972e53c0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,16 +1,16 @@ # TODO - Migrations: 1. Move userdata to new hashing algorithm -- Store (and read) from the correct user account: - 1. Playlists - 2. Favorites + - Package jsoni and publish on PyPi - Rewrite stores to use dictionaries instead of list pools - last updated date on tracks added via watchdog is broken -- hide "remove from playlist" option on system playlists # DONE - Support auth headers - Add recently played playlist - Move user track logs to user zero -- Move future logs to appropriate user id \ No newline at end of file +- Move future logs to appropriate user id +- Store (and read) from the correct user account: + 1. Playlists + 2. Favorites \ No newline at end of file diff --git a/app/api/auth.py b/app/api/auth.py index 0d201cd0..07f53efb 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -48,7 +48,7 @@ def create_new_token(user: dict): return { "msg": f"Logged in as {user['username']}", - "acccesstoken": access_token, + "accesstoken": access_token, "refreshtoken": create_refresh_token(identity=user), "maxage": max_age, } @@ -76,7 +76,7 @@ def login(body: LoginBody): return {"msg": "Hehe! invalid password"}, 401 res = create_new_token(user.todict()) - token = res["acccesstoken"] + token = res["accesstoken"] age = res["maxage"] res = jsonify(res) set_access_cookies(res, token, max_age=age) @@ -84,6 +84,38 @@ def login(body: LoginBody): return res +pair_token = dict() + + +class PairDeviceQuery(BaseModel): + code: str = Field("", description="The code") + + +@api.get("/pair") +@jwt_required(optional=True) +def pair_device(query: PairDeviceQuery): + """ + Pair the Swing Music mobile app with this server + + Send a code to get an access token. Send an authenticated request without the code to generate a new token. + """ + if current_user: + token = create_new_token(get_jwt_identity()) + key = token["accesstoken"][-6:] + + global pair_token + pair_token = { + key: token, + } + + return {"code": key} + + if query.code: + return pair_token.get(query.code, {"msg": "Invalid code"}) + + return {"msg": "No code provided"}, 400 + + @api.post("/refresh") @jwt_required(refresh=True) def refresh(): diff --git a/app/api/logger/__init__.py b/app/api/logger/__init__.py index 67b46e2b..40fd4db7 100644 --- a/app/api/logger/__init__.py +++ b/app/api/logger/__init__.py @@ -1,4 +1,3 @@ -from flask_jwt_extended import current_user from flask_openapi3 import Tag from flask_openapi3 import APIBlueprint from pydantic import Field @@ -40,7 +39,6 @@ def log_track(body: LogTrackBody): timestamp=timestamp, duration=duration, source=source, - userid=current_user["id"], ) return {"total entries": last_row} diff --git a/app/db/sqlite/logger/tracks.py b/app/db/sqlite/logger/tracks.py index 832cbefb..7f43e8b2 100644 --- a/app/db/sqlite/logger/tracks.py +++ b/app/db/sqlite/logger/tracks.py @@ -1,10 +1,11 @@ +from flask_jwt_extended import current_user from app.db.sqlite.utils import SQLiteManager from app.models.logger import TrackLog as TrackLog class SQLiteTrackLogger: @classmethod - def insert_track(cls, trackhash: str, duration: int, source: str, timestamp: int, userid: int): + def insert_track(cls, trackhash: str, duration: int, source: str, timestamp: int): """ Inserts a track play record into the database """ @@ -19,7 +20,9 @@ class SQLiteTrackLogger: ) VALUES(?,?,?,?,?) """ - cur.execute(sql, (trackhash, duration, timestamp, source, userid)) + cur.execute( + sql, (trackhash, duration, timestamp, source, current_user["id"]) + ) lastrowid = cur.lastrowid return lastrowid @@ -31,7 +34,7 @@ class SQLiteTrackLogger: """ with SQLiteManager(userdata_db=True) as cur: - sql = """SELECT * FROM track_logger ORDER BY timestamp DESC""" + sql = f"""SELECT * FROM track_logger WHERE userid = {current_user['id']} ORDER BY timestamp DESC""" cur.execute(sql) rows = cur.fetchall() @@ -45,9 +48,9 @@ class SQLiteTrackLogger: """ with SQLiteManager(userdata_db=True) as cur: - sql = """SELECT * FROM track_logger ORDER BY timestamp DESC LIMIT ?,?""" + sql = f"""SELECT * FROM track_logger WHERE userid = {current_user['id']} ORDER BY timestamp DESC LIMIT ?,?""" cur.execute(sql, (start, limit)) rows = cur.fetchall() - return [TrackLog(*row) for row in rows] \ No newline at end of file + return [TrackLog(*row) for row in rows] diff --git a/app/db/sqlite/queries.py b/app/db/sqlite/queries.py index 6ab9056f..1e52b272 100644 --- a/app/db/sqlite/queries.py +++ b/app/db/sqlite/queries.py @@ -55,7 +55,8 @@ CREATE TABLE IF NOT EXISTS track_logger ( duration integer NOT NULL, timestamp integer NOT NULL, source text, - userid integer NOT NULL DEFAULT 0 + userid integer NOT NULL DEFAULT 1, + constraint fk_users foreign key (userid) references users(id) on delete cascade ); CREATE TABLE IF NOT EXISTS users ( diff --git a/app/lib/folderslib.py b/app/lib/folderslib.py index baf00733..112a72d7 100644 --- a/app/lib/folderslib.py +++ b/app/lib/folderslib.py @@ -18,7 +18,7 @@ def create_folder(path: str, trackcount=0, foldercount=0) -> Folder: return Folder( name=folder.name, - path=win_replace_slash(str(folder)), + path=win_replace_slash(str(folder)) + "/", is_sym=folder.is_symlink(), trackcount=trackcount, foldercount=foldercount, diff --git a/app/migrations/v1_4_9/__init__.py b/app/migrations/v1_4_9/__init__.py index a6ecf357..b5729c4f 100644 --- a/app/migrations/v1_4_9/__init__.py +++ b/app/migrations/v1_4_9/__init__.py @@ -69,9 +69,25 @@ class _3MoveScrobbleToUserId1(Migration): @staticmethod def migrate(): + sql = """ + UPDATE track_logger SET userid = 1 WHERE userid = 0; + ALTER TABLE track_logger RENAME TO _track_logger; + CREATE TABLE IF NOT EXISTS track_logger ( + id integer PRIMARY KEY, + trackhash text NOT NULL, + duration integer NOT NULL, + timestamp integer NOT NULL, + source text, + userid integer NOT NULL DEFAULT 1, + constraint fk_users foreign key (userid) references users(id) on delete cascade + ); + + INSERT INTO track_logger SELECT * FROM _track_logger; + DROP TABLE _track_logger; + """ # INFO: Move the scrobble table to the user id 1 with SQLiteManager(userdata_db=True) as cur: - cur.execute("UPDATE track_logger SET userid = 1 WHERE userid = 0") + cur.executescript(sql) cur.close() @@ -108,7 +124,7 @@ class _4AddUserIdToFavoritesTable(Migration): data = data.fetchone() if data[0] == 1: - return # INFO: column already exists + return # INFO: column already exists cur.executescript(sql) diff --git a/manage.py b/manage.py index fee176e7..71e7f3e0 100644 --- a/manage.py +++ b/manage.py @@ -84,7 +84,14 @@ app = create_api() app.static_folder = get_home_res_path("client") # INFO: Routes that don't need authentication -whitelisted_routes = {"/auth/login", "/auth/users", "/auth/logout", "/auth/refresh", "/docs"} +whitelisted_routes = { + "/auth/login", + "/auth/users", + "/auth/pair", + "/auth/logout", + "/auth/refresh", + "/docs", +} blacklist_extensions = {".webp"}.union(getClientFilesExtensions())