mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
save track logs to logged in user
+ rewrite migration collection + prevent logging invalid track logs + add jsoni
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -2,6 +2,7 @@ class Migration:
|
||||
"""
|
||||
Base migration class.
|
||||
"""
|
||||
enabled: bool = True
|
||||
|
||||
@staticmethod
|
||||
def migrate():
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"age": 45,
|
||||
"name": "Johniee"
|
||||
}
|
||||
Reference in New Issue
Block a user