From 30768dd5d614585785c5154a9b8936f0b32999ce Mon Sep 17 00:00:00 2001 From: cwilvx Date: Sat, 18 May 2024 17:16:07 +0300 Subject: [PATCH] save track logs to logged in user + rewrite migration collection + prevent logging invalid track logs + add jsoni --- TODO.md | 13 ++++- app/api/__init__.py | 4 +- app/api/logger/__init__.py | 12 +++- app/db/sqlite/logger/tracks.py | 4 +- app/migrations/__init__.py | 32 ++++++----- app/migrations/base.py | 1 + app/migrations/v1_3_0/__init__.py | 14 ++--- app/migrations/v1_4_9/__init__.py | 27 +++++---- jsoni/index.py | 91 +++++++++++++++++++++++++++++++ notconfig.json | 4 ++ 10 files changed, 160 insertions(+), 42 deletions(-) create mode 100644 jsoni/index.py create mode 100644 notconfig.json diff --git a/TODO.md b/TODO.md index 36d03066..0258f1a6 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,11 @@ -- Move user track logs to user zero - - Move future logs to appropriate user id +# TODO - Migrations: - - Move userdata to new hashing algorithm \ No newline at end of file + 1. Move userdata to new hashing algorithm +- Store on the correct user account: + 1. Playlists + 2. Favorites +- Package jsoni and publish on PyPi + +# DONE +- Move user track logs to user zero +- Move future logs to appropriate user id \ No newline at end of file diff --git a/app/api/__init__.py b/app/api/__init__.py index f0ac3cc0..bc865fca 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -68,7 +68,9 @@ def create_api(): app.config["JWT_TOKEN_LOCATION"] = ["cookies"] app.config["JWT_COOKIE_CSRF_PROTECT"] = False app.config["JWT_SESSION_COOKIE"] = False - app.config["JWT_ACCESS_TOKEN_EXPIRES"] = int(datetime.timedelta(days=30).total_seconds()) + + jwt_expiry = int(datetime.timedelta(days=30).total_seconds()) + app.config["JWT_ACCESS_TOKEN_EXPIRES"] = jwt_expiry # CORS CORS(app, origins="*", supports_credentials=True) diff --git a/app/api/logger/__init__.py b/app/api/logger/__init__.py index 546451e6..67b46e2b 100644 --- a/app/api/logger/__init__.py +++ b/app/api/logger/__init__.py @@ -1,3 +1,4 @@ +from flask_jwt_extended import current_user from flask_openapi3 import Tag from flask_openapi3 import APIBlueprint from pydantic import Field @@ -31,8 +32,15 @@ def log_track(body: LogTrackBody): duration = body.duration source = body.source + if not timestamp or duration < 5: + return {"msg": "Invalid entry."}, 400 + last_row = db.insert_track( - trackhash=trackhash, timestamp=timestamp, duration=duration, source=source + trackhash=trackhash, + timestamp=timestamp, + duration=duration, + source=source, + userid=current_user["id"], ) - return {"last_row": last_row} + return {"total entries": last_row} diff --git a/app/db/sqlite/logger/tracks.py b/app/db/sqlite/logger/tracks.py index c2590300..36db63db 100644 --- a/app/db/sqlite/logger/tracks.py +++ b/app/db/sqlite/logger/tracks.py @@ -3,7 +3,7 @@ from app.db.sqlite.utils import SQLiteManager class SQLiteTrackLogger: @classmethod - def insert_track(cls, trackhash: str, duration: int, source: str, timestamp: int): + def insert_track(cls, trackhash: str, duration: int, source: str, timestamp: int, userid: int): """ Inserts a track into the database """ @@ -18,7 +18,7 @@ class SQLiteTrackLogger: ) VALUES(?,?,?,?,?) """ - cur.execute(sql, (trackhash, duration, timestamp, source, 0)) + cur.execute(sql, (trackhash, duration, timestamp, source, userid)) lastrowid = cur.lastrowid return lastrowid diff --git a/app/migrations/__init__.py b/app/migrations/__init__.py index e6d5c431..2e9f87ae 100644 --- a/app/migrations/__init__.py +++ b/app/migrations/__init__.py @@ -4,24 +4,28 @@ Migrations module. Reads and applies the latest database migrations. """ +import inspect +from types import ModuleType from app.db.sqlite.migrations import MigrationManager from app.logger import log from app.migrations import v1_3_0, v1_4_9 from app.migrations.base import Migration -migrations: list[list[Migration]] = [ - [ - # v1.3.0 - v1_3_0.RemoveSmallThumbnailFolder, - v1_3_0.RemovePlaylistArtistHashes, - v1_3_0.AddSettingsToPlaylistTable, - v1_3_0.AddLastUpdatedToTrackTable, - v1_3_0.MovePlaylistsAndFavoritesTo10BitHashes, - v1_3_0.RemoveAllTracks, - v1_3_0.UpdateAppSettingsTable, - ], - [v1_4_9.AddTimestampToFavoritesTable, v1_4_9.DeleteOriginalThumbnails], -] + +def get_all_migrations(module: ModuleType) -> list[Migration]: + """ + Extracts all migration classes from a module. + """ + predicate = ( + lambda obj: inspect.isclass(obj) + and issubclass(obj, Migration) + and obj.enabled + and obj.__module__ == module.__name__ + ) + + # INFO: I couldn't find how to sort the classes in order of appearance + # so I just renamed them to be sortable by name + return [obj for name, obj in inspect.getmembers(module, predicate)] def apply_migrations(): @@ -34,6 +38,8 @@ def apply_migrations(): migrations past that index are applied and the new length is stored as the new migration index. """ + modules = [v1_3_0, v1_4_9] + migrations = [get_all_migrations(m) for m in modules] index = MigrationManager.get_index() all_migrations = [migration for sublist in migrations for migration in sublist] diff --git a/app/migrations/base.py b/app/migrations/base.py index 7002021c..cd30da1a 100644 --- a/app/migrations/base.py +++ b/app/migrations/base.py @@ -2,6 +2,7 @@ class Migration: """ Base migration class. """ + enabled: bool = True @staticmethod def migrate(): diff --git a/app/migrations/v1_3_0/__init__.py b/app/migrations/v1_3_0/__init__.py index b1df7b27..9b20b6b0 100644 --- a/app/migrations/v1_3_0/__init__.py +++ b/app/migrations/v1_3_0/__init__.py @@ -23,7 +23,7 @@ from app.utils.hashing import create_hash # 6: trackhashes -class RemoveSmallThumbnailFolder(Migration): +class m1_RemoveSmallThumbnailFolder(Migration): """ Removes the small thumbnail folder. @@ -45,7 +45,7 @@ class RemoveSmallThumbnailFolder(Migration): os.makedirs(path, exist_ok=True) -class RemovePlaylistArtistHashes(Migration): +class m2_RemovePlaylistArtistHashes(Migration): """ removes the artisthashes column from the playlists table. """ @@ -64,7 +64,7 @@ class RemovePlaylistArtistHashes(Migration): cur.close() -class AddSettingsToPlaylistTable(Migration): +class m3_AddSettingsToPlaylistTable(Migration): """ adds the settings column and removes the banner_pos and has_gif columns to the playlists table. @@ -141,7 +141,7 @@ class AddSettingsToPlaylistTable(Migration): cur.close() -class AddLastUpdatedToTrackTable(Migration): +class m4_AddLastUpdatedToTrackTable(Migration): """ adds the last modified column to the tracks table. """ @@ -161,7 +161,7 @@ class AddLastUpdatedToTrackTable(Migration): cur.close() -class MovePlaylistsAndFavoritesTo10BitHashes(Migration): +class m5_MovePlaylistsAndFavoritesTo10BitHashes(Migration): """ moves the playlists and favorites to 10 bit hashes. """ @@ -268,7 +268,7 @@ class MovePlaylistsAndFavoritesTo10BitHashes(Migration): cur.close() -class RemoveAllTracks(Migration): +class m6_RemoveAllTracks(Migration): """ removes all tracks from the tracks table. """ @@ -282,7 +282,7 @@ class RemoveAllTracks(Migration): cur.close() -class UpdateAppSettingsTable(Migration): +class m7_UpdateAppSettingsTable(Migration): @staticmethod def migrate(): drop_table_sql = "DROP TABLE settings" diff --git a/app/migrations/v1_4_9/__init__.py b/app/migrations/v1_4_9/__init__.py index 6f0de717..712be3c3 100644 --- a/app/migrations/v1_4_9/__init__.py +++ b/app/migrations/v1_4_9/__init__.py @@ -5,7 +5,7 @@ from app.migrations.base import Migration from app.settings import Paths -class AddTimestampToFavoritesTable(Migration): +class _1AddTimestampToFavoritesTable(Migration): """ Adds a timestamp column to the favorites table. """ @@ -30,7 +30,7 @@ class AddTimestampToFavoritesTable(Migration): cur.close() -class MoveHashesToSha1(Migration): +class _4MoveHashesToSha1(Migration): """ Moves the 10 bit item hashes from sha256 to sha1 which is faster and more lenient on less powerful devices. @@ -38,13 +38,15 @@ class MoveHashesToSha1(Migration): Thanks to [@tcsenpai](https:github.com/tcsenpai) for the contribution. """ + enabled: bool = False + pass # INFO: Apparentlly, every single table is affected by this migration. # NOTE: Use generators to avoid memory issues. -class DeleteOriginalThumbnails(Migration): +class _2DeleteOriginalThumbnails(Migration): """ Original thumbnails are too large and are not needed. """ @@ -59,18 +61,15 @@ class DeleteOriginalThumbnails(Migration): if os.path.exists(og_imgpath): shutil.rmtree(og_imgpath) -class DeleteOriginalThumbnailsa(Migration): - """ - Original thumbnails are too large and are not needed. - """ - # TODO: Implement this migration +class _3MoveScrobbleToUserId1(Migration): + """ + Updates all track logs from user id = 0 to user id = 1 + """ @staticmethod def migrate(): - imgpath = Paths.get_thumbs_path() - og_imgpath = os.path.join(imgpath, "original") - - if os.path.exists(og_imgpath): - shutil.rmtree(og_imgpath) - + # 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.close() diff --git a/jsoni/index.py b/jsoni/index.py new file mode 100644 index 00000000..ec504aa1 --- /dev/null +++ b/jsoni/index.py @@ -0,0 +1,91 @@ +import os +import json + +from typing import Any +from dataclasses import asdict, dataclass + + +@dataclass +class Jsoni: + _configpath: str = "" + _init_complete: bool = False + + # @property + # def _configpath(self): + # """ + # The path to the config file + # """ + # return None + + @property + def _config_as_dict(self): + all_keys = asdict(self) + # print("all_keys: ", all_keys) + # remove internal attributes (starting with __) + return {k: v for k, v in all_keys.items() if not k.startswith("_")} + + def create_file(self): + # if not exists, create the config file + if not os.path.exists(self._configpath): + print("creating file") + self.write_to_file(self._config_as_dict) + + def write_to_file(self, settings: dict[str, Any]): + print("writing to file") + print("settings: ", settings) + with open(self._configpath, "w") as f: + json.dump(settings, f, indent=4) + + def __setattr__(self, name: str, value: Any): + if not self._init_complete: + print("setting local attr", "name: ", name, ", value: ", value) + super().__setattr__(name, value) + return + + # if is internal attribute, set to instance + # but don't write to file + super().__setattr__(name, value) + if name.startswith("_"): + print("setting local internal attr", "name: ", name, ", value: ", value) + return + + print("writing attr", "name: ", name, ", value: ", value) + self.write_to_file(self._config_as_dict) + + def load_config(self): + with open(self._configpath, "r") as f: + settings: dict[str, Any] = json.load(f) + + for key, value in settings.items(): + setattr(self, key, value) + + def __post_init__(self): + if not self._configpath: + raise AttributeError( + f"{self.__class__.__name__}: self._configpath is not set" + ) + + print("self: ", self) + self.create_file() + self.load_config() + self._init_complete = True + print("init complete!!!!") + + +@dataclass +class MyConfig(Jsoni): + age: int = 30 + name: str = "John" + # _configpath: str = "notconfig.json" + + # @property + # def _configpath(self): + # return "notconfig.json" + + +config = MyConfig("notconfig.json") +print("config.name: ", config.name) +config.age = 45 +print("config.name: ", config.name) +# config.create_file() +# config.name = "Jane" diff --git a/notconfig.json b/notconfig.json new file mode 100644 index 00000000..8167fc1a --- /dev/null +++ b/notconfig.json @@ -0,0 +1,4 @@ +{ + "age": 45, + "name": "Johniee" +} \ No newline at end of file