attach favorites to logged in user

This commit is contained in:
cwilvx
2024-05-25 15:12:44 +03:00
parent 300c8eb30b
commit 0b8a5e92f5
14 changed files with 128 additions and 38 deletions
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+9 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -1
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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))
+22
View File
@@ -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
View File
@@ -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 = ""
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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")