Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
Crossoufire committed Jun 12, 2024
2 parents afac712 + 27eaf8f commit a858818
Show file tree
Hide file tree
Showing 49 changed files with 2,798 additions and 2,642 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## CHANGELOG v1.4.1
---
### Under the Hood
- Improved UI performance and backend of `/list`
- Updated the `package.json` dependencies

### Features
- Added the option for the users to compare their stats with another user in `/stats` (alpha)

### UI Modifications
- Removed the misc sidebar and added cards carousel to the main stats in `/stats`
- Changed TMDB to IGDB for games in `/details`
- Changed Top Watched to Top Read for Books and Top Played for Games in `/stats`
-
### Fixes
- Fixed error in user last updates in `/profile`


## CHANGELOG v1.4.0
---
### Under the Hood
Expand Down
5 changes: 0 additions & 5 deletions backend/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from backend.config import Config


# Globally accessible Flask modules
mail = Mail()
db = SQLAlchemy()
migrate = Migrate()
Expand Down Expand Up @@ -110,8 +109,6 @@ def _create_first_db_data():


def create_app(config_class: Type[Config] = Config) -> Flask:
""" Create and initialize the app """

app = Flask(__name__, static_url_path="/api/static")
app.config.from_object(config_class)
app.url_map.strict_slashes = False
Expand All @@ -133,8 +130,6 @@ def create_app(config_class: Type[Config] = Config) -> Flask:
from backend.api.utils.scheduled_tasks import add_cli_commands
add_cli_commands()

# _create_first_db_data()

return app


Expand Down
37 changes: 24 additions & 13 deletions backend/api/data_managers/medialist_query_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Tuple, Dict, List

from flask import abort, request
from sqlalchemy import asc, or_, ColumnElement
from werkzeug.exceptions import HTTPException

from backend.api import db
from backend.api.models.user_models import User
from backend.api.routes.handlers import current_user
Expand Down Expand Up @@ -162,7 +164,7 @@ def _search_query(self):
self.common_filter,
self.labels_filter,
self.status_filter,
)
)

paginate_results = (
base_query.distinct(self.media_label.label).group_by(self.media.id)
Expand All @@ -175,12 +177,23 @@ def _search_query(self):
self.results = [media.to_dict() for media in paginate_results.items]

def _items_query(self):
paginated_results = (
outerjoin_to_add = []

if self.genres and self.genres[0] != "All":
outerjoin_to_add.append([self.media_genre, self.media_genre.media_id == self.media_list.media_id])
if self.labels and self.labels[0] != "All":
outerjoin_to_add.append([self.media_label, self.media_label.media_id == self.media_list.media_id])

base_query = (
db.session.query(self.media_list)
.outerjoin(self.media, self.media.id == self.media_list.media_id)
.outerjoin(self.media_genre, self.media_genre.media_id == self.media.id)
.outerjoin(self.media_label, self.media_label.media_id == self.media.id)
.filter(
)

for table, condition in outerjoin_to_add:
base_query = base_query.outerjoin(table, condition)

base_query = (
base_query.filter(
self.media_list.user_id == self.user.id,
self.favorite_filter,
self.genres_filter,
Expand All @@ -189,15 +202,14 @@ def _items_query(self):
self.labels_filter,
self.status_filter,
self.comment_filter,
)
.distinct(self.media_label.label).group_by(self.media.id)
.order_by(self.sorting_filter, asc(self.media.name))
)
.group_by(self.media_list.media_id).order_by(self.sorting_filter, asc(self.media.name))
.paginate(page=int(self.page), per_page=self.PER_PAGE, error_out=True)
)

self.total = paginated_results.total
self.pages = paginated_results.pages
self.results = [media.to_dict() for media in paginated_results.items]
self.total = base_query.total
self.pages = base_query.pages
self.results = [media.to_dict() for media in base_query.items]

def return_results(self) -> Tuple[Dict, Dict, List, Dict]:
if self.search:
Expand All @@ -215,14 +227,13 @@ def return_results(self) -> Tuple[Dict, Dict, List, Dict]:

filters = dict(
page=self.page,
lang=self.lang,
sort=self.sorting,
search=self.search,
status=self.status,
genres=self.genres,
labels=self.labels,
lang=self.lang,
comment=self.comment,
sorting=self.sorting,
common=self.common,
favorite=self.favorite,
)
Expand Down
3 changes: 1 addition & 2 deletions backend/api/data_managers/stats_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ class BaseStats:

def __init__(self, user: User):
self.user = user
self.data = {"values": {}, "lists": {}}
self.data = {"values": {}, "lists": {}, "is_feeling": self.user.add_feeling}

self.media_models = ModelsFetcher.get_dict_models(self.GROUP, "all")
self._initialize_media_models()

self.data["is_feeling"] = self.user.add_feeling
self.rating = self.media_list.feeling if self.user.add_feeling else self.media_list.score
self.common_join = [self.media_list, self.media_list.media_id == self.media.id]
self.common_filter = []
Expand Down
4 changes: 0 additions & 4 deletions backend/api/models/books_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ def remove_non_list_media(cls):

@staticmethod
def form_only() -> List[str]:
""" Return the allowed fields for the edit book form """
return ["name", "release_date", "pages", "language", "publishers", "synopsis"]


Expand Down Expand Up @@ -250,8 +249,6 @@ class BooksGenre(db.Model):

@classmethod
def replace_genres(cls, genres: List[Dict], media_id: int):
""" Replace the old genres by the new ones """

# Remove actual genres
cls.query.filter_by(media_id=media_id).delete()

Expand All @@ -263,7 +260,6 @@ def replace_genres(cls, genres: List[Dict], media_id: int):

@staticmethod
def get_available_genres() -> List:
""" Return the available genres for the books """
return ["Action & Adventure", "Biography", "Chick lit", "Children", "Classic", "Crime", "Drama",
"Dystopian", "Essay", "Fantastic", "Fantasy", "History", "Humor", "Horror", "Literary Novel",
"Memoirs", "Mystery", "Paranormal", "Philosophy", "Poetry", "Romance", "Science", "Science-Fiction",
Expand Down
19 changes: 10 additions & 9 deletions backend/api/models/user_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ def get_list_levels(self) -> List[Dict]:
return level_per_ml

def get_last_updates(self, limit_: int) -> List[Dict]:
""" Get the last media updates of the current user """
""" Get last current user's media updates """
return [update.to_dict() for update in self.last_updates.filter_by(user_id=self.id).limit(limit_).all()]

def get_follows_updates(self, limit_: int) -> List[Dict]:
Expand Down Expand Up @@ -510,8 +510,6 @@ def verify_jwt_token(token: str) -> User | None:


class UserLastUpdate(db.Model):
""" UserLastUpdate SQL model """

THRESHOLD = 600

id = db.Column(db.Integer, primary_key=True)
Expand All @@ -536,8 +534,6 @@ class UserLastUpdate(db.Model):
new_redo = db.Column(db.Integer)

def to_dict(self) -> Dict:
""" Transform a <UserLastUpdate> object into a dict """

update_dict = {}

# Page update
Expand All @@ -564,10 +560,15 @@ def to_dict(self) -> Dict:

# Season and episode update
else:
update_dict["update"] = [
f"S{self.old_season:02d}.E{self.old_episode:02d}",
f"S{self.new_season:02d}.E{self.new_episode:02d}",
]
try:
update_dict["update"] = [
f"S{self.old_season:02d}.E{self.old_episode:02d}",
f"S{self.new_season:02d}.E{self.new_episode:02d}",
]
except:
update_dict["update"] = ["Watching"]
current_app.logger.error(f"[ERROR] - An error occurred updating the user last updates for: "
f"({self.media_id}, {self.media_name}, {self.media_type})")

# Update date and add media name
update_dict["date"] = self.date.replace(tzinfo=timezone.utc).isoformat()
Expand Down
4 changes: 2 additions & 2 deletions backend/api/models/utils_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,13 @@ class MediaLabelMixin:

@classmethod
def get_user_labels(cls, user_id: int) -> List[str]:
q_all = db.session.query(cls.label.distinct()).filter_by(user_id=user_id).all()
q_all = db.session.query(cls.label.distinct()).filter_by(user_id=user_id).order_by(cls.label).all()
return [label[0] for label in q_all]

@classmethod
def get_user_media_labels(cls, user_id: int, media_id: int) -> Dict:
all_labels = set(cls.get_user_labels(user_id))
q_in = db.session.query(cls.label).filter_by(user_id=user_id, media_id=media_id).all()
q_in = db.session.query(cls.label).filter_by(user_id=user_id, media_id=media_id).order_by(cls.label).all()
already_in = {label[0] for label in q_in}
available = all_labels - already_in
return dict(already_in=list(already_in), available=list(available))
Expand Down
10 changes: 4 additions & 6 deletions backend/api/routes/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,18 @@ def media_details(media_type: MediaType, media_id: int):
@validate_media_type
def get_details_form(media_type: MediaType, media_id: int):
if current_user.role == RoleType.USER:
return abort(403, "You are not authorized")
return abort(403, "You are not authorized. Please contact an admin.")

media_model, genre_model = ModelsFetcher.get_lists_models(media_type, [ModelTypes.MEDIA, ModelTypes.GENRE])

media = media_model.query.filter_by(id=media_id).first()
if not media:
return abort(404, "The media does not exists")

# Accepted form fields
forms_fields = media_model.form_only()

data = {
"fields": [(key, val) for (key, val) in media.to_dict().items() if key in forms_fields],
"genres": genre_model.get_available_genres() if media_type == MediaType.BOOKS else None,
"fields": [(key, val) for (key, val) in media.to_dict().items() if key in media_model.form_only()],
"all_genres": genre_model.get_available_genres() if media_type == MediaType.BOOKS else None,
"genres": [genre.genre for genre in media.genres],
}

return jsonify(data=data), 200
Expand Down
4 changes: 0 additions & 4 deletions backend/api/routes/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@


def _send_async_email(app: Flask, to: str, username: str, subject: str, template: str, callback: str, token: str):
""" Send an email using a new thread to not block the main thread """

with app.app_context():
path = Path(current_app.root_path, f"static/emails/{template}.html")
with open(path) as fp:
Expand All @@ -26,8 +24,6 @@ def _send_async_email(app: Flask, to: str, username: str, subject: str, template


def send_email(to: str, username: str, subject: str, template: str, callback: str, token: str):
""" Create thread to send asynchronously the email """

# noinspection PyProtectedMember,PyUnresolvedReferences
app = current_app._get_current_object()
thread = Thread(target=_send_async_email, args=(app, to, username, subject, template, callback, token))
Expand Down
16 changes: 13 additions & 3 deletions backend/api/routes/lists.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from flask import jsonify, Blueprint
from backend.api import db, cache
from backend.api.models.user_models import User
from backend.api.routes.handlers import token_auth, current_user
from backend.api.data_managers.medialist_query_manager import MediaListQuery
from backend.api.utils.decorators import validate_media_type
from backend.api.utils.enums import MediaType
from backend.api.utils.enums import MediaType, RoleType
from backend.api.data_managers.stats_manager import BaseStats


Expand Down Expand Up @@ -33,7 +34,7 @@ def media_list(media_type: MediaType, username: str):


@lists_bp.route("/stats/<media_type>/<username>", methods=["GET"])
@cache.cached(timeout=3600)
# @cache.cached(timeout=3600)
@token_auth.login_required
@validate_media_type
def stats_page(media_type: MediaType, username: str):
Expand All @@ -42,4 +43,13 @@ def stats_page(media_type: MediaType, username: str):
stats_class = BaseStats.get_stats_class(media_type)
stats = stats_class(user).create_stats()

return jsonify(data=stats), 200
data = dict(
is_current=(user.id == current_user.id),
stats=stats,
users=[{
"label": user.username,
"value": user.username,
} for user in User.query.filter(User.active == True, User.role != RoleType.ADMIN).all()]
)

return jsonify(data=data), 200
2 changes: 0 additions & 2 deletions backend/api/routes/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ def revoke_token():

@tokens.route("/tokens/reset_password_token", methods=["POST"])
def reset_password_token():
""" Generate a password reset token and send the mail to the user """

try:
data = request.get_json()
except:
Expand Down
2 changes: 1 addition & 1 deletion backend/api/utils/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def int_to_money(value: int):
value /= 1000
exp += 1

return f"{round(value, 0)} {suffixes[exp]}$"
return f"{int(value)} {suffixes[exp]}$"


def display_time(minutes: int) -> str:
Expand Down
2 changes: 0 additions & 2 deletions backend/tests/base_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import unittest
from datetime import datetime
from typing import Type, Dict

from flask_bcrypt import generate_password_hash

from backend.api import create_app, db
from backend.api.utils.enums import RoleType
from backend.config import Config
Expand Down
1 change: 0 additions & 1 deletion backend/tests/test_users.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os
from unittest import mock

from backend.api.utils.functions import ModelsFetcher
from backend.tests.base_test import BaseTest

Expand Down
Loading

0 comments on commit a858818

Please sign in to comment.