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
|
# 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
@@ -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)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
@@ -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]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ class Migration:
|
|||||||
"""
|
"""
|
||||||
Base migration class.
|
Base migration class.
|
||||||
"""
|
"""
|
||||||
|
enabled: bool = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def migrate():
|
def migrate():
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
|
||||||
|
|
||||||
|
|||||||
@@ -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