mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
attach favorites to logged in user
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
1. Playlists
|
||||
2. Favorites
|
||||
- Package jsoni and publish on PyPi
|
||||
- Rewrite stores to use dictionaries instead of list pools
|
||||
|
||||
# DONE
|
||||
- Add recently played playlist
|
||||
|
||||
@@ -4,6 +4,7 @@ Contains all the album routes.
|
||||
|
||||
import random
|
||||
|
||||
from flask_jwt_extended import current_user
|
||||
from pydantic import Field
|
||||
from flask_openapi3 import Tag
|
||||
from flask_openapi3 import APIBlueprint
|
||||
|
||||
@@ -6,6 +6,7 @@ import math
|
||||
import random
|
||||
from datetime import datetime
|
||||
|
||||
from flask_jwt_extended import current_user
|
||||
from flask_openapi3 import APIBlueprint, Tag
|
||||
from pydantic import Field
|
||||
from app.api.apischemas import AlbumLimitSchema, ArtistHashSchema, ArtistLimitSchema, TrackLimitSchema
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import List, TypeVar
|
||||
|
||||
from flask_jwt_extended import current_user
|
||||
from flask_openapi3 import Tag
|
||||
from flask_openapi3 import APIBlueprint
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -10,7 +11,7 @@ from app.settings import Defaults
|
||||
from app.utils.bisection import use_bisection
|
||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||
from app.serializers.track import serialize_track, serialize_tracks
|
||||
from app.serializers.artist import serialize_for_card as serialize_artist
|
||||
from app.serializers.artist import serialize_for_card as serialize_artist, serialize_for_cards
|
||||
from app.serializers.album import serialize_for_card, serialize_for_card_many
|
||||
|
||||
from app.store.albums import AlbumStore
|
||||
@@ -98,7 +99,9 @@ def get_favorite_tracks(query: GenericLimitSchema):
|
||||
Get favorite tracks
|
||||
"""
|
||||
limit = query.limit
|
||||
tracks = favdb.get_fav_tracks()
|
||||
userid = current_user['id']
|
||||
|
||||
tracks = favdb.get_fav_tracks(userid)
|
||||
trackhashes = [t[1] for t in tracks]
|
||||
trackhashes.reverse()
|
||||
src_tracks = sorted(TrackStore.tracks, key=lambda x: x.trackhash)
|
||||
@@ -118,6 +121,7 @@ def get_favorite_artists(query: GenericLimitSchema):
|
||||
Get favorite artists
|
||||
"""
|
||||
limit = query.limit
|
||||
|
||||
artists = favdb.get_fav_artists()
|
||||
artisthashes = [a[1] for a in artists]
|
||||
artisthashes.reverse()
|
||||
@@ -266,9 +270,9 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
||||
|
||||
return {
|
||||
"recents": recents[:album_limit],
|
||||
"tracks": tracks[:track_limit],
|
||||
"albums": albums[:album_limit],
|
||||
"artists": artists[:artist_limit],
|
||||
"tracks": serialize_tracks(tracks[:track_limit]),
|
||||
"albums": serialize_for_card_many(albums[:album_limit]),
|
||||
"artists": serialize_for_cards(artists[:artist_limit]),
|
||||
"count": count,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from flask_jwt_extended import current_user
|
||||
from flask_openapi3 import Tag
|
||||
from flask_openapi3 import APIBlueprint
|
||||
|
||||
|
||||
+37
-19
@@ -1,4 +1,6 @@
|
||||
from datetime import datetime
|
||||
|
||||
from flask_jwt_extended import current_user
|
||||
from app.models import FavType
|
||||
from .utils import SQLiteManager
|
||||
|
||||
@@ -11,12 +13,14 @@ class SQLiteFavoriteMethods:
|
||||
"""
|
||||
Checks if an item is favorited.
|
||||
"""
|
||||
sql = """SELECT * FROM favorites WHERE hash = ? AND type = ?"""
|
||||
userid = current_user["id"]
|
||||
|
||||
sql = """SELECT * FROM favorites WHERE hash = ? AND type = ? AND userid = ?"""
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (itemhash, fav_type))
|
||||
items = cur.fetchall()
|
||||
cur.execute(sql, (itemhash, fav_type, userid))
|
||||
item = cur.fetchone()
|
||||
cur.close()
|
||||
return len(items) > 0
|
||||
return item is not None
|
||||
|
||||
@classmethod
|
||||
def insert_one_favorite(cls, fav_type: str, fav_hash: str):
|
||||
@@ -27,10 +31,11 @@ class SQLiteFavoriteMethods:
|
||||
if cls.check_is_favorite(fav_hash, fav_type):
|
||||
return
|
||||
|
||||
sql = """INSERT INTO favorites(type, hash, timestamp) VALUES(?,?,?)"""
|
||||
current_timestamp = datetime.now().timestamp()
|
||||
sql = """INSERT INTO favorites(type, hash, timestamp, userid) VALUES(?,?,?,?)"""
|
||||
current_timestamp = int(datetime.now().timestamp())
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (fav_type, fav_hash, current_timestamp))
|
||||
userid = current_user["id"]
|
||||
cur.execute(sql, (fav_type, fav_hash, current_timestamp, userid))
|
||||
cur.close()
|
||||
|
||||
@classmethod
|
||||
@@ -38,55 +43,67 @@ class SQLiteFavoriteMethods:
|
||||
"""
|
||||
Returns a list of all favorites.
|
||||
"""
|
||||
sql = """SELECT * FROM favorites"""
|
||||
sql = """SELECT * FROM favorites WHERE userid = ?"""
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql)
|
||||
userid = current_user["id"]
|
||||
cur.execute(sql, (userid,))
|
||||
favs = cur.fetchall()
|
||||
cur.close()
|
||||
return [fav for fav in favs if fav[1] != ""]
|
||||
|
||||
@classmethod
|
||||
def get_favorites(cls, fav_type: str) -> list[tuple]:
|
||||
def get_favorites(cls, fav_type: str, userid: int = None) -> list[tuple]:
|
||||
"""
|
||||
Returns a list of favorite tracks.
|
||||
|
||||
If userid is None, all favorites are returned.
|
||||
"""
|
||||
sql = """SELECT * FROM favorites WHERE type = ?"""
|
||||
params = (fav_type,)
|
||||
|
||||
if not userid:
|
||||
sql += " AND userid = ?"
|
||||
params = (fav_type, userid)
|
||||
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (fav_type,))
|
||||
cur.execute(sql, params)
|
||||
all_favs = cur.fetchall()
|
||||
cur.close()
|
||||
return all_favs
|
||||
|
||||
@classmethod
|
||||
def get_fav_tracks(cls) -> list[tuple]:
|
||||
def get_fav_tracks(cls, userid: int = None) -> list[tuple]:
|
||||
"""
|
||||
Returns a list of favorite tracks.
|
||||
"""
|
||||
return cls.get_favorites(FavType.track)
|
||||
return cls.get_favorites(FavType.track, userid)
|
||||
|
||||
@classmethod
|
||||
def get_fav_albums(cls) -> list[tuple]:
|
||||
"""
|
||||
Returns a list of favorite albums.
|
||||
"""
|
||||
return cls.get_favorites(FavType.album)
|
||||
userid = current_user["id"]
|
||||
return cls.get_favorites(FavType.album, userid)
|
||||
|
||||
@classmethod
|
||||
def get_fav_artists(cls) -> list[tuple]:
|
||||
"""
|
||||
Returns a list of favorite artists.
|
||||
"""
|
||||
return cls.get_favorites(FavType.artist)
|
||||
userid = current_user["id"]
|
||||
return cls.get_favorites(FavType.artist, userid)
|
||||
|
||||
@classmethod
|
||||
def delete_favorite(cls, fav_type: str, fav_hash: str):
|
||||
"""
|
||||
Deletes a favorite from the database.
|
||||
"""
|
||||
sql = """DELETE FROM favorites WHERE hash = ? AND type = ?"""
|
||||
sql = """DELETE FROM favorites WHERE hash = ? AND type = ? AND userid = ?"""
|
||||
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (fav_hash, fav_type))
|
||||
userid = current_user["id"]
|
||||
cur.execute(sql, (fav_hash, fav_type, userid))
|
||||
cur.close()
|
||||
|
||||
@classmethod
|
||||
@@ -94,10 +111,11 @@ class SQLiteFavoriteMethods:
|
||||
"""
|
||||
Returns the number of favorite tracks.
|
||||
"""
|
||||
sql = """SELECT COUNT(*) FROM favorites WHERE type = ?"""
|
||||
sql = """SELECT COUNT(*) FROM favorites WHERE type = ? AND userid = ?"""
|
||||
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (FavType.track,))
|
||||
userid = current_user["id"]
|
||||
cur.execute(sql, (FavType.track, userid))
|
||||
count = cur.fetchone()[0]
|
||||
cur.close()
|
||||
return count
|
||||
|
||||
@@ -16,7 +16,9 @@ CREATE TABLE IF NOT EXISTS favorites (
|
||||
id integer PRIMARY KEY,
|
||||
hash text not null,
|
||||
type text not null,
|
||||
timestamp integer not null default 0
|
||||
timestamp integer not null default 0,
|
||||
userid integer not null,
|
||||
foreign key (userid) references users(id) on delete cascade
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
This library contains all the functions related to the search functionality.
|
||||
"""
|
||||
|
||||
from typing import Any, Generator, List, TypeVar
|
||||
|
||||
from rapidfuzz import process, utils
|
||||
|
||||
@@ -58,7 +58,8 @@ def apply_migrations():
|
||||
try:
|
||||
migration.migrate()
|
||||
log.info("Applied migration: %s", migration.__name__)
|
||||
except:
|
||||
except Exception as e:
|
||||
log.error("Failed to run migration: %s", migration.__name__)
|
||||
log.error(e)
|
||||
|
||||
MigrationManager.set_index(len(all_migrations))
|
||||
|
||||
@@ -73,3 +73,25 @@ class _3MoveScrobbleToUserId1(Migration):
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute("UPDATE track_logger SET userid = 1 WHERE userid = 0")
|
||||
cur.close()
|
||||
|
||||
|
||||
class _4AddUserIdToFavoritesTable(Migration):
|
||||
"""
|
||||
Adds a userid column to the favorites table.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def migrate():
|
||||
# check if userid column exists
|
||||
exists_sql = "select count(*) from pragma_table_info('favorites') where name = 'userid'"
|
||||
sql = "ALTER TABLE favorites ADD userid INTEGER NOT NULL DEFAULT 1 REFERENCES users(id) ON DELETE CASCADE"
|
||||
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
data = cur.execute(exists_sql)
|
||||
data = data.fetchone()
|
||||
|
||||
if data[0] == 1:
|
||||
return# INFO: column already exists
|
||||
|
||||
cur.executescript(sql)
|
||||
|
||||
+17
-3
@@ -1,7 +1,10 @@
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from flask_jwt_extended import current_user
|
||||
|
||||
|
||||
from app.settings import SessionVarKeys, get_flag
|
||||
from app.utils.hashing import create_hash
|
||||
from app.utils.parsers import (
|
||||
@@ -40,11 +43,22 @@ class Track:
|
||||
|
||||
image: str = ""
|
||||
artist_hashes: str = ""
|
||||
is_favorite: bool = False
|
||||
|
||||
fav_userids: list = field(default_factory=list)
|
||||
"""
|
||||
A string of user ids separated by commas.
|
||||
"""
|
||||
# is_favorite: bool = False
|
||||
|
||||
@property
|
||||
def is_favorite(self):
|
||||
return current_user['id'] in self.fav_userids
|
||||
|
||||
# temporary attributes
|
||||
_pos: int = 0 # for sorting tracks by disc and track number
|
||||
_ati: str = "" # (album track identifier) for removing duplicates when merging album versions
|
||||
_ati: str = (
|
||||
"" # (album track identifier) for removing duplicates when merging album versions
|
||||
)
|
||||
|
||||
og_title: str = ""
|
||||
og_album: str = ""
|
||||
|
||||
@@ -5,6 +5,9 @@ from app.models.track import Track
|
||||
|
||||
def serialize_track(track: Track, to_remove: set = {}, remove_disc=True) -> dict:
|
||||
album_dict = asdict(track)
|
||||
# is_favorite @property is not included in asdict
|
||||
album_dict["is_favorite"] = track.is_favorite
|
||||
|
||||
props = {
|
||||
"date",
|
||||
"genre",
|
||||
@@ -16,6 +19,7 @@ def serialize_track(track: Track, to_remove: set = {}, remove_disc=True) -> dict
|
||||
"track",
|
||||
"artist_hashes",
|
||||
"created_date",
|
||||
"fav_userids",
|
||||
}.union(to_remove)
|
||||
|
||||
if not remove_disc:
|
||||
|
||||
+26
-6
@@ -1,5 +1,6 @@
|
||||
# from tqdm import tqdm
|
||||
|
||||
from flask_jwt_extended import current_user
|
||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||
from app.db.sqlite.tracks import SQLiteTrackMethods as trackdb
|
||||
from app.models import Track
|
||||
@@ -25,15 +26,32 @@ class TrackStore:
|
||||
|
||||
cls.tracks = CustomList(trackdb.get_all_tracks())
|
||||
|
||||
fav_hashes = favdb.get_fav_tracks()
|
||||
fav_hashes = " ".join([t[1] for t in fav_hashes])
|
||||
favs = favdb.get_fav_tracks()
|
||||
|
||||
records = dict()
|
||||
|
||||
for fav in favs:
|
||||
if fav[1] not in records:
|
||||
# if trackhash not in dict, add it
|
||||
# and set the value to a set containing the userid
|
||||
records[fav[1]] = {fav[4]}
|
||||
|
||||
# if trackhash is in dict, add the userid to the set
|
||||
records[fav[1]].add(fav[4])
|
||||
|
||||
for track in cls.tracks:
|
||||
if instance_key != TRACKS_LOAD_KEY:
|
||||
return
|
||||
|
||||
if track.trackhash in fav_hashes:
|
||||
track.is_favorite = True
|
||||
try:
|
||||
track.fav_userids = list(records[track.trackhash])
|
||||
except KeyError:
|
||||
track.fav_userids = []
|
||||
|
||||
# if track.trackhash in fav_hashes:
|
||||
# fav = [t for t in favs if t["hash"] == track.trackhash][0]
|
||||
# print(fav)
|
||||
# track.favorite_data = [i["userid"] for i in fav]
|
||||
|
||||
print("Done!")
|
||||
|
||||
@@ -99,7 +117,8 @@ class TrackStore:
|
||||
|
||||
for track in cls.tracks:
|
||||
if track.trackhash == trackhash:
|
||||
track.is_favorite = True
|
||||
if current_user["id"] not in track.fav_userids:
|
||||
track.fav_userids.append(current_user["id"])
|
||||
|
||||
@classmethod
|
||||
def remove_track_from_fav(cls, trackhash: str):
|
||||
@@ -109,7 +128,8 @@ class TrackStore:
|
||||
|
||||
for track in cls.tracks:
|
||||
if track.trackhash == trackhash:
|
||||
track.is_favorite = False
|
||||
if current_user["id"] in track.fav_userids:
|
||||
track.fav_userids.remove(current_user["id"])
|
||||
|
||||
@classmethod
|
||||
def append_track_artists(
|
||||
|
||||
+3
-3
@@ -78,9 +78,9 @@ class MyConfig(Jsoni):
|
||||
name: str = "John"
|
||||
# _configpath: str = "notconfig.json"
|
||||
|
||||
# @property
|
||||
# def _configpath(self):
|
||||
# return "notconfig.json"
|
||||
@property
|
||||
def _configpath(self):
|
||||
return "notconfig.json"
|
||||
|
||||
|
||||
config = MyConfig("notconfig.json")
|
||||
|
||||
Reference in New Issue
Block a user