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
- Move future logs to appropriate user id
# TODO
- 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_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)
+10 -2
View File
@@ -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}
+2 -2
View File
@@ -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
+19 -13
View File
@@ -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]
+1
View File
@@ -2,6 +2,7 @@ class Migration:
"""
Base migration class.
"""
enabled: bool = True
@staticmethod
def migrate():
+7 -7
View File
@@ -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"
+13 -14
View File
@@ -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()
+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"
}