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