save track logs to logged in user

+ rewrite migration collection
+ prevent logging invalid track logs
+ add jsoni
This commit is contained in:
cwilvx
2024-05-18 17:16:07 +03:00
parent f8f07c2116
commit 30768dd5d6
10 changed files with 160 additions and 42 deletions
+10 -3
View File
@@ -1,4 +1,11 @@
- Move user track logs to user zero # TODO
- Move future logs to appropriate user id
- Migrations: - Migrations:
- Move userdata to new hashing algorithm 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
+3 -1
View File
@@ -68,7 +68,9 @@ def create_api():
app.config["JWT_TOKEN_LOCATION"] = ["cookies"] app.config["JWT_TOKEN_LOCATION"] = ["cookies"]
app.config["JWT_COOKIE_CSRF_PROTECT"] = False app.config["JWT_COOKIE_CSRF_PROTECT"] = False
app.config["JWT_SESSION_COOKIE"] = 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
CORS(app, origins="*", supports_credentials=True) CORS(app, origins="*", supports_credentials=True)
+10 -2
View File
@@ -1,3 +1,4 @@
from flask_jwt_extended import current_user
from flask_openapi3 import Tag from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint from flask_openapi3 import APIBlueprint
from pydantic import Field from pydantic import Field
@@ -31,8 +32,15 @@ def log_track(body: LogTrackBody):
duration = body.duration duration = body.duration
source = body.source source = body.source
if not timestamp or duration < 5:
return {"msg": "Invalid entry."}, 400
last_row = db.insert_track( 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}
+2 -2
View File
@@ -3,7 +3,7 @@ from app.db.sqlite.utils import SQLiteManager
class SQLiteTrackLogger: class SQLiteTrackLogger:
@classmethod @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 Inserts a track into the database
""" """
@@ -18,7 +18,7 @@ class SQLiteTrackLogger:
) VALUES(?,?,?,?,?) ) VALUES(?,?,?,?,?)
""" """
cur.execute(sql, (trackhash, duration, timestamp, source, 0)) cur.execute(sql, (trackhash, duration, timestamp, source, userid))
lastrowid = cur.lastrowid lastrowid = cur.lastrowid
return lastrowid return lastrowid
+19 -13
View File
@@ -4,24 +4,28 @@ Migrations module.
Reads and applies the latest database migrations. Reads and applies the latest database migrations.
""" """
import inspect
from types import ModuleType
from app.db.sqlite.migrations import MigrationManager from app.db.sqlite.migrations import MigrationManager
from app.logger import log from app.logger import log
from app.migrations import v1_3_0, v1_4_9 from app.migrations import v1_3_0, v1_4_9
from app.migrations.base import Migration from app.migrations.base import Migration
migrations: list[list[Migration]] = [
[ def get_all_migrations(module: ModuleType) -> list[Migration]:
# v1.3.0 """
v1_3_0.RemoveSmallThumbnailFolder, Extracts all migration classes from a module.
v1_3_0.RemovePlaylistArtistHashes, """
v1_3_0.AddSettingsToPlaylistTable, predicate = (
v1_3_0.AddLastUpdatedToTrackTable, lambda obj: inspect.isclass(obj)
v1_3_0.MovePlaylistsAndFavoritesTo10BitHashes, and issubclass(obj, Migration)
v1_3_0.RemoveAllTracks, and obj.enabled
v1_3_0.UpdateAppSettingsTable, and obj.__module__ == module.__name__
], )
[v1_4_9.AddTimestampToFavoritesTable, v1_4_9.DeleteOriginalThumbnails],
] # 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(): def apply_migrations():
@@ -34,6 +38,8 @@ def apply_migrations():
migrations past that index are applied and the new length migrations past that index are applied and the new length
is stored as the new migration index. 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() index = MigrationManager.get_index()
all_migrations = [migration for sublist in migrations for migration in sublist] all_migrations = [migration for sublist in migrations for migration in sublist]
+1
View File
@@ -2,6 +2,7 @@ class Migration:
""" """
Base migration class. Base migration class.
""" """
enabled: bool = True
@staticmethod @staticmethod
def migrate(): def migrate():
+7 -7
View File
@@ -23,7 +23,7 @@ from app.utils.hashing import create_hash
# 6: trackhashes # 6: trackhashes
class RemoveSmallThumbnailFolder(Migration): class m1_RemoveSmallThumbnailFolder(Migration):
""" """
Removes the small thumbnail folder. Removes the small thumbnail folder.
@@ -45,7 +45,7 @@ class RemoveSmallThumbnailFolder(Migration):
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
class RemovePlaylistArtistHashes(Migration): class m2_RemovePlaylistArtistHashes(Migration):
""" """
removes the artisthashes column from the playlists table. removes the artisthashes column from the playlists table.
""" """
@@ -64,7 +64,7 @@ class RemovePlaylistArtistHashes(Migration):
cur.close() cur.close()
class AddSettingsToPlaylistTable(Migration): class m3_AddSettingsToPlaylistTable(Migration):
""" """
adds the settings column and removes the banner_pos and has_gif columns adds the settings column and removes the banner_pos and has_gif columns
to the playlists table. to the playlists table.
@@ -141,7 +141,7 @@ class AddSettingsToPlaylistTable(Migration):
cur.close() cur.close()
class AddLastUpdatedToTrackTable(Migration): class m4_AddLastUpdatedToTrackTable(Migration):
""" """
adds the last modified column to the tracks table. adds the last modified column to the tracks table.
""" """
@@ -161,7 +161,7 @@ class AddLastUpdatedToTrackTable(Migration):
cur.close() cur.close()
class MovePlaylistsAndFavoritesTo10BitHashes(Migration): class m5_MovePlaylistsAndFavoritesTo10BitHashes(Migration):
""" """
moves the playlists and favorites to 10 bit hashes. moves the playlists and favorites to 10 bit hashes.
""" """
@@ -268,7 +268,7 @@ class MovePlaylistsAndFavoritesTo10BitHashes(Migration):
cur.close() cur.close()
class RemoveAllTracks(Migration): class m6_RemoveAllTracks(Migration):
""" """
removes all tracks from the tracks table. removes all tracks from the tracks table.
""" """
@@ -282,7 +282,7 @@ class RemoveAllTracks(Migration):
cur.close() cur.close()
class UpdateAppSettingsTable(Migration): class m7_UpdateAppSettingsTable(Migration):
@staticmethod @staticmethod
def migrate(): def migrate():
drop_table_sql = "DROP TABLE settings" drop_table_sql = "DROP TABLE settings"
+13 -14
View File
@@ -5,7 +5,7 @@ from app.migrations.base import Migration
from app.settings import Paths from app.settings import Paths
class AddTimestampToFavoritesTable(Migration): class _1AddTimestampToFavoritesTable(Migration):
""" """
Adds a timestamp column to the favorites table. Adds a timestamp column to the favorites table.
""" """
@@ -30,7 +30,7 @@ class AddTimestampToFavoritesTable(Migration):
cur.close() cur.close()
class MoveHashesToSha1(Migration): class _4MoveHashesToSha1(Migration):
""" """
Moves the 10 bit item hashes from sha256 to sha1 which is Moves the 10 bit item hashes from sha256 to sha1 which is
faster and more lenient on less powerful devices. 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. Thanks to [@tcsenpai](https:github.com/tcsenpai) for the contribution.
""" """
enabled: bool = False
pass pass
# INFO: Apparentlly, every single table is affected by this migration. # INFO: Apparentlly, every single table is affected by this migration.
# NOTE: Use generators to avoid memory issues. # NOTE: Use generators to avoid memory issues.
class DeleteOriginalThumbnails(Migration): class _2DeleteOriginalThumbnails(Migration):
""" """
Original thumbnails are too large and are not needed. Original thumbnails are too large and are not needed.
""" """
@@ -59,18 +61,15 @@ class DeleteOriginalThumbnails(Migration):
if os.path.exists(og_imgpath): if os.path.exists(og_imgpath):
shutil.rmtree(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 @staticmethod
def migrate(): def migrate():
imgpath = Paths.get_thumbs_path() # INFO: Move the scrobble table to the user id 1
og_imgpath = os.path.join(imgpath, "original") with SQLiteManager(userdata_db=True) as cur:
cur.execute("UPDATE track_logger SET userid = 1 WHERE userid = 0")
if os.path.exists(og_imgpath): cur.close()
shutil.rmtree(og_imgpath)
+91
View File
@@ -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"
+4
View File
@@ -0,0 +1,4 @@
{
"age": 45,
"name": "Johniee"
}