From 89b2d3371c996445ed253fa53d7be5521b55100b Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Fri, 13 Dec 2024 22:47:24 +0100 Subject: [PATCH 01/42] Starting work, models, schemas and a few crud functions --- app/modules/cmm/__init__.py | 0 app/modules/cmm/cruds_cmm.py | 108 ++++++++++++++++++++++++ app/modules/cmm/endpoints_cmm.py | 138 +++++++++++++++++++++++++++++++ app/modules/cmm/models_cmm.py | 61 ++++++++++++++ app/modules/cmm/schemas_cmm.py | 52 ++++++++++++ app/modules/cmm/types_cmm.py | 8 ++ 6 files changed, 367 insertions(+) create mode 100644 app/modules/cmm/__init__.py create mode 100644 app/modules/cmm/cruds_cmm.py create mode 100644 app/modules/cmm/endpoints_cmm.py create mode 100644 app/modules/cmm/models_cmm.py create mode 100644 app/modules/cmm/schemas_cmm.py create mode 100644 app/modules/cmm/types_cmm.py diff --git a/app/modules/cmm/__init__.py b/app/modules/cmm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py new file mode 100644 index 0000000000..6ac965120e --- /dev/null +++ b/app/modules/cmm/cruds_cmm.py @@ -0,0 +1,108 @@ +from collections.abc import Sequence +from datetime import UTC, date, datetime, timedelta +from uuid import UUID + +from sqlalchemy import delete, func, select, update +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import noload, selectinload + +from app.modules.cmm import models_cmm, types_cmm + +n_memes = 10 +n_weeks = 7 + + +async def get_newest_memes(db: AsyncSession, n_page: int) -> Sequence[models_cmm.Meme]: + result = await db.execute( + select(models_cmm.Meme) + .order_by(models_cmm.Meme.creation_time) + .fetch(n_memes) + .offset((n_page - 1) * n_memes), + ) + return result.scalars().all() + + +async def get_best_memes(db: AsyncSession, n_page: int) -> Sequence[models_cmm.Meme]: + result = await db.execute( + select(models_cmm.Meme) + .order_by(models_cmm.Meme.vote_score) + .fetch(n_memes) + .offset((n_page - 1) * n_memes), + ) + return result.scalars().all() + + +async def get_trending_memes( + db: AsyncSession, n_page: int +) -> Sequence[models_cmm.Meme]: + result = await db.execute( + select(models_cmm.Meme) + .order_by(models_cmm.Meme.vote_score) + .where( + (models_cmm.Meme.creation_time - datetime.now(tz=UTC)) + < timedelta(days=n_weeks) + ) + .fetch(n_memes) + .offset((n_page - 1) * n_memes), + ) + return result.scalars().all() + + +async def get_memes_from_user( + db: AsyncSession, + user_id: str, +) -> Sequence[models_cmm.Meme]: + result = await db.execute( + select(models_cmm.Meme) + .where(models_cmm.Meme.user_id == user_id) + .order_by(models_cmm.Meme.creation_time), + ) + return result.scalars().all() + + +async def get_reported_memes(db: AsyncSession) -> Sequence[models_cmm.Meme]: + result = await db.execute( + select(models_cmm.Meme).where( + models_cmm.Meme.status == types_cmm.MemeStatus.reported, + ), + ) + return result.scalars().all() + + +async def create_meme(db: AsyncSession, meme: models_cmm.Meme): + db.add(meme) + + +async def delete_meme(db: AsyncSession, meme_id: UUID): + await db.execute( + delete(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), + ) + + +async def create_report(db: AsyncSession, report: models_cmm.Report): + db.add(report) + + +async def delete_report(db: AsyncSession, report_id: UUID): + await db.execute( + delete(models_cmm.Report).where(models_cmm.Meme.id == report_id), + ) + + +async def create_vote(db: AsyncSession, vote: models_cmm.Vote): + db.add(vote) + + +async def update_vote(db: AsyncSession, vote_id: UUID, new_positive: bool): + await db.execute( + update(models_cmm.Vote) + .where(models_cmm.Vote.id == vote_id) + .values({models_cmm.Vote.positive: new_positive}), + ) + + +async def delete_vote(db: AsyncSession, vote_id: UUID): + await db.execute( + delete(models_cmm.Vote).where(models_cmm.Vote.id == vote_id), + ) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py new file mode 100644 index 0000000000..074116a6e8 --- /dev/null +++ b/app/modules/cmm/endpoints_cmm.py @@ -0,0 +1,138 @@ +import uuid +from datetime import UTC, datetime + +from fastapi import Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core import models_core +from app.core.groups.groups_type import AccountType, GroupType +from app.dependencies import get_db, is_user_a_member, is_user_in +from app.modules.flappybird import ( + cruds_flappybird, + models_flappybird, + schemas_flappybird, +) +from app.types.module import Module + +module = Module( + root="flappybird", + tag="Flappy Bird", + default_allowed_account_types=[AccountType.student], +) + + +@module.router.get( + "/flappybird/scores", + response_model=list[schemas_flappybird.FlappyBirdScoreInDB], + status_code=200, +) +async def get_flappybird_score(db: AsyncSession = Depends(get_db)): + """Return the leaderboard""" + leaderboard = await cruds_flappybird.get_flappybird_score_leaderboard(db=db) + return leaderboard + + +@module.router.get( + "/flappybird/scores/me", + status_code=200, + response_model=schemas_flappybird.FlappyBirdScoreCompleteFeedBack, +) +async def get_current_user_flappybird_personal_best( + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_a_member), +): + user_personal_best_table = ( + await cruds_flappybird.get_flappybird_personal_best_by_user_id( + db=db, + user_id=user.id, + ) + ) + + if user_personal_best_table is None: + raise HTTPException( + status_code=404, + detail="Not found", + ) + + position = await cruds_flappybird.get_flappybird_score_position( + db=db, + score_value=user_personal_best_table.value, + ) + if position is None: + raise HTTPException( + status_code=404, + detail="Not found", + ) + user_personal_best = schemas_flappybird.FlappyBirdScoreCompleteFeedBack( + value=user_personal_best_table.value, + user=user_personal_best_table.user, + creation_time=user_personal_best_table.creation_time, + position=position, + ) + + return user_personal_best + + +@module.router.post( + "/flappybird/scores", + response_model=schemas_flappybird.FlappyBirdScoreBase, + status_code=201, +) +async def create_flappybird_score( + flappybird_score: schemas_flappybird.FlappyBirdScoreBase, + user: models_core.CoreUser = Depends(is_user_a_member), + db: AsyncSession = Depends(get_db), +): + # Currently, flappybird_score is a schema instance + # To add it to the database, we need to create a model + + # We need to generate a new UUID for the score + score_id = uuid.uuid4() + # And get the current date and time + creation_time = datetime.now(UTC) + + db_flappybird_score = models_flappybird.FlappyBirdScore( + id=score_id, + user_id=user.id, + value=flappybird_score.value, + creation_time=creation_time, + ) + db_flappybird_best_score = models_flappybird.FlappyBirdBestScore( + id=score_id, + user_id=user.id, + value=flappybird_score.value, + creation_time=creation_time, + ) + personal_best = await cruds_flappybird.get_flappybird_personal_best_by_user_id( + user_id=user.id, + db=db, + ) + if personal_best is None: + await cruds_flappybird.create_flappybird_best_score( + flappybird_best_score=db_flappybird_best_score, + db=db, + ) + else: + if personal_best.value < flappybird_score.value: + await cruds_flappybird.update_flappybird_best_score( + user_id=user.id, + best_score=flappybird_score.value, + db=db, + ) + await cruds_flappybird.create_flappybird_score( + flappybird_score=db_flappybird_score, + db=db, + ) + return db_flappybird_score + + +@module.router.delete( + "/flappybird/scores/{targeted_user_id}", + status_code=204, +) +async def remove_flappybird_score( + targeted_user_id: str, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_in(GroupType.admin)), +): + await cruds_flappybird.delete_flappybird_best_score(db=db, user_id=targeted_user_id) diff --git a/app/modules/cmm/models_cmm.py b/app/modules/cmm/models_cmm.py new file mode 100644 index 0000000000..d01e2b6c70 --- /dev/null +++ b/app/modules/cmm/models_cmm.py @@ -0,0 +1,61 @@ +from datetime import datetime +import uuid +from typing import TYPE_CHECKING + +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from app.core.models_core import CoreUser + +if TYPE_CHECKING: + from app.modules.cmm.models_cmm import Meme + +from app.modules.cmm.types_cmm import MemeStatus +from app.types.sqlalchemy import Base, PrimaryKey + + +class Report(Base): + __tablename__ = "cmm_report" + + id: Mapped[PrimaryKey] + user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) + user: Mapped[CoreUser] = relationship("CoreUser", init=False) + creation_time: Mapped[datetime] + meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("cmm_meme.id")) + meme: Mapped[Meme] = relationship("Meme", init=False) + description: Mapped[str | None] = mapped_column(default=None) + + +class Vote(Base): + ___tablename___ = "cmm_vote" + + id: Mapped[PrimaryKey] + user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) + user: Mapped[CoreUser] = relationship("CoreUser", init=False) + creation_time: Mapped[datetime] + meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("cmm_meme.id")) + meme: Mapped[Meme] = relationship("Meme", init=False) + positive: bool + + +class Meme(Base): + __tablename__ = "cmm_meme" + + id: Mapped[PrimaryKey] + user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) + user: Mapped[CoreUser] = relationship("CoreUser", init=False) + creation_time: Mapped[datetime] + vote_score: Mapped[int] + votes: Mapped[list[Vote]] = relationship( + "Votes", + secondary="cmm_vote", + lazy="selectin", + default_factory=list, + ) + status: Mapped[MemeStatus] + reports: Mapped[list[Report]] = relationship( + "Report", + secondary="cmm_report", + lazy="selectin", + default_factory=list, + ) diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py new file mode 100644 index 0000000000..67af59e3a4 --- /dev/null +++ b/app/modules/cmm/schemas_cmm.py @@ -0,0 +1,52 @@ +import uuid +from datetime import datetime + +from fastapi.datastructures import UploadFile +from pydantic import BaseModel + +from app.core.schemas_core import CoreUserSimple +from app.modules.cmm.types_cmm import MemeStatus + + +class ReportBase(BaseModel): + # pour la création + meme_id: str + description: str | None + + +class Report(ReportBase): + # pour lire les report + user: CoreUserSimple + creation_time: datetime + + +class ReportComplete(Report): + id: uuid.UUID + + +class VoteBase(BaseModel): + meme_id: str + positive: bool + + +class Vote(VoteBase): + user: CoreUserSimple + creation_time: datetime + + +class VoteComplete(Vote): + id: uuid.UUID + + +class MemeBase(BaseModel): + image: UploadFile + + +class Meme(MemeBase): + # dans les get + user: CoreUserSimple + creation_time: datetime + vote_score: int + votes: list[Vote] + status: MemeStatus + reports: list[Report] diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py new file mode 100644 index 0000000000..670e6737d8 --- /dev/null +++ b/app/modules/cmm/types_cmm.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class MemeStatus(str, Enum): + neutral = "neutral" + reported = "reported" + banned = "banned" + allowed = "allowed" From 0d093ac7146c5f7269422b6157819dfb69a0be36 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Fri, 13 Dec 2024 23:51:20 +0100 Subject: [PATCH 02/42] Get memes endpoint --- app/modules/cmm/cruds_cmm.py | 29 ++++-- app/modules/cmm/endpoints_cmm.py | 153 +++++++++---------------------- app/modules/cmm/types_cmm.py | 8 ++ 3 files changed, 73 insertions(+), 117 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 6ac965120e..561d0ba6cf 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -13,20 +13,36 @@ n_weeks = 7 -async def get_newest_memes(db: AsyncSession, n_page: int) -> Sequence[models_cmm.Meme]: +async def get_memes_by_date( + db: AsyncSession, + n_page: int, + descending: bool, +) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) - .order_by(models_cmm.Meme.creation_time) + .order_by( + models_cmm.Meme.creation_time.desc() + if descending + else models_cmm.Meme.creation_time, + ) .fetch(n_memes) .offset((n_page - 1) * n_memes), ) return result.scalars().all() -async def get_best_memes(db: AsyncSession, n_page: int) -> Sequence[models_cmm.Meme]: +async def get_memes_by_votes( + db: AsyncSession, + n_page: int, + descending: bool, +) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) - .order_by(models_cmm.Meme.vote_score) + .order_by( + models_cmm.Meme.vote_score.desc() + if descending + else models_cmm.Meme.vote_score, + ) .fetch(n_memes) .offset((n_page - 1) * n_memes), ) @@ -34,14 +50,15 @@ async def get_best_memes(db: AsyncSession, n_page: int) -> Sequence[models_cmm.M async def get_trending_memes( - db: AsyncSession, n_page: int + db: AsyncSession, + n_page: int, ) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) .order_by(models_cmm.Meme.vote_score) .where( (models_cmm.Meme.creation_time - datetime.now(tz=UTC)) - < timedelta(days=n_weeks) + < timedelta(days=n_weeks), ) .fetch(n_memes) .offset((n_page - 1) * n_memes), diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 074116a6e8..7c602eabf8 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,138 +1,69 @@ import uuid from datetime import UTC, datetime -from fastapi import Depends, HTTPException +from fastapi import Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.core import models_core from app.core.groups.groups_type import AccountType, GroupType from app.dependencies import get_db, is_user_a_member, is_user_in -from app.modules.flappybird import ( - cruds_flappybird, - models_flappybird, - schemas_flappybird, -) +from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm from app.types.module import Module module = Module( - root="flappybird", - tag="Flappy Bird", + root="cmm", + tag="Centrale Mega Meme", default_allowed_account_types=[AccountType.student], ) @module.router.get( "/flappybird/scores", - response_model=list[schemas_flappybird.FlappyBirdScoreInDB], + response_model=list[schemas_cmm.Meme], status_code=200, ) -async def get_flappybird_score(db: AsyncSession = Depends(get_db)): - """Return the leaderboard""" - leaderboard = await cruds_flappybird.get_flappybird_score_leaderboard(db=db) - return leaderboard - - -@module.router.get( - "/flappybird/scores/me", - status_code=200, - response_model=schemas_flappybird.FlappyBirdScoreCompleteFeedBack, -) -async def get_current_user_flappybird_personal_best( +async def get_memes( + sort_by: str | None, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_a_member), + n_page: int = 1, ): - user_personal_best_table = ( - await cruds_flappybird.get_flappybird_personal_best_by_user_id( - db=db, - user_id=user.id, - ) - ) - - if user_personal_best_table is None: + if n_page < 1: raise HTTPException( - status_code=404, - detail="Not found", + status_code=204, + detail="Invalid page number", ) - position = await cruds_flappybird.get_flappybird_score_position( - db=db, - score_value=user_personal_best_table.value, - ) - if position is None: - raise HTTPException( - status_code=404, - detail="Not found", - ) - user_personal_best = schemas_flappybird.FlappyBirdScoreCompleteFeedBack( - value=user_personal_best_table.value, - user=user_personal_best_table.user, - creation_time=user_personal_best_table.creation_time, - position=position, - ) - - return user_personal_best - - -@module.router.post( - "/flappybird/scores", - response_model=schemas_flappybird.FlappyBirdScoreBase, - status_code=201, -) -async def create_flappybird_score( - flappybird_score: schemas_flappybird.FlappyBirdScoreBase, - user: models_core.CoreUser = Depends(is_user_a_member), - db: AsyncSession = Depends(get_db), -): - # Currently, flappybird_score is a schema instance - # To add it to the database, we need to create a model - - # We need to generate a new UUID for the score - score_id = uuid.uuid4() - # And get the current date and time - creation_time = datetime.now(UTC) - - db_flappybird_score = models_flappybird.FlappyBirdScore( - id=score_id, - user_id=user.id, - value=flappybird_score.value, - creation_time=creation_time, - ) - db_flappybird_best_score = models_flappybird.FlappyBirdBestScore( - id=score_id, - user_id=user.id, - value=flappybird_score.value, - creation_time=creation_time, - ) - personal_best = await cruds_flappybird.get_flappybird_personal_best_by_user_id( - user_id=user.id, - db=db, - ) - if personal_best is None: - await cruds_flappybird.create_flappybird_best_score( - flappybird_best_score=db_flappybird_best_score, - db=db, - ) - else: - if personal_best.value < flappybird_score.value: - await cruds_flappybird.update_flappybird_best_score( - user_id=user.id, - best_score=flappybird_score.value, + match sort_by: + case types_cmm.MemeSort.best: + meme_page = await cruds_cmm.get_memes_by_votes( db=db, + descending=True, + n_page=n_page, ) - await cruds_flappybird.create_flappybird_score( - flappybird_score=db_flappybird_score, - db=db, - ) - return db_flappybird_score - + case types_cmm.MemeSort.worst: + meme_page = await cruds_cmm.get_memes_by_votes( + db=db, + descending=False, + n_page=n_page, + ) + case types_cmm.MemeSort.trending: + meme_page = await cruds_cmm.get_trending_memes( + db=db, + n_page=n_page, + ) + case types_cmm.MemeSort.newest: + meme_page = await cruds_cmm.get_memes_by_date( + db=db, + descending=True, + n_page=n_page, + ) + case types_cmm.MemeSort.oldest: + meme_page = await cruds_cmm.get_memes_by_date( + db=db, + descending=False, + n_page=n_page, + ) + case _: + raise HTTPException(status_code=204, detail="Invalid sort method") -@module.router.delete( - "/flappybird/scores/{targeted_user_id}", - status_code=204, -) -async def remove_flappybird_score( - targeted_user_id: str, - db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.admin)), -): - await cruds_flappybird.delete_flappybird_best_score(db=db, user_id=targeted_user_id) + return meme_page diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index 670e6737d8..5c0d98ee52 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -6,3 +6,11 @@ class MemeStatus(str, Enum): reported = "reported" banned = "banned" allowed = "allowed" + + +class MemeSort(str, Enum): + best = "best" + worst = "worst" + trending = "trending" + newest = "newest" + oldest = "oldest" From 4b016ae92ddcc1a0719bd6d8052718428bd967ac Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Mon, 16 Dec 2024 13:52:52 +0100 Subject: [PATCH 03/42] POST endpoint --- app/modules/cmm/cruds_cmm.py | 23 ++---------- app/modules/cmm/endpoints_cmm.py | 62 ++++++++++++++++++++++++++++++-- app/modules/cmm/models_cmm.py | 18 ---------- app/modules/cmm/schemas_cmm.py | 17 --------- app/modules/cmm/types_cmm.py | 2 -- 5 files changed, 62 insertions(+), 60 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 561d0ba6cf..790407e99f 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -78,35 +78,16 @@ async def get_memes_from_user( return result.scalars().all() -async def get_reported_memes(db: AsyncSession) -> Sequence[models_cmm.Meme]: - result = await db.execute( - select(models_cmm.Meme).where( - models_cmm.Meme.status == types_cmm.MemeStatus.reported, - ), - ) - return result.scalars().all() - - -async def create_meme(db: AsyncSession, meme: models_cmm.Meme): +def add_meme_in_DB(db: AsyncSession, meme: models_cmm.Meme): db.add(meme) -async def delete_meme(db: AsyncSession, meme_id: UUID): +async def delete_meme_in_DB(db: AsyncSession, meme_id: UUID): await db.execute( delete(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), ) -async def create_report(db: AsyncSession, report: models_cmm.Report): - db.add(report) - - -async def delete_report(db: AsyncSession, report_id: UUID): - await db.execute( - delete(models_cmm.Report).where(models_cmm.Meme.id == report_id), - ) - - async def create_vote(db: AsyncSession, vote: models_cmm.Vote): db.add(vote) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 7c602eabf8..44725fa75c 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -2,13 +2,24 @@ from datetime import UTC, datetime from fastapi import Depends, HTTPException, status +from fastapi.datastructures import UploadFile +from fastapi import File from sqlalchemy.ext.asyncio import AsyncSession +from starlette.routing import request_response from app.core import models_core from app.core.groups.groups_type import AccountType, GroupType -from app.dependencies import get_db, is_user_a_member, is_user_in +from app.dependencies import ( + get_db, + get_request_id, + is_user, + is_user_a_member, + is_user_in, +) from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm +from app.types.content_type import ContentType from app.types.module import Module +from app.utils.tools import save_file_as_data module = Module( root="cmm", @@ -18,7 +29,7 @@ @module.router.get( - "/flappybird/scores", + "/memes/{n_page}", response_model=list[schemas_cmm.Meme], status_code=200, ) @@ -27,6 +38,9 @@ async def get_memes( db: AsyncSession = Depends(get_db), n_page: int = 1, ): + """ + Get a page of memes according to the asked sort + """ if n_page < 1: raise HTTPException( status_code=204, @@ -67,3 +81,47 @@ async def get_memes( raise HTTPException(status_code=204, detail="Invalid sort method") return meme_page + + +@module.router.post( + "/memes", + response_model=schemas_cmm.Meme, + status_code=201, +) +async def add_meme( + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), + image: UploadFile = File(...), + request_id: str = Depends(get_request_id), +): + """ + Add a new meme + """ + try: + meme_id = uuid.uuid4() + meme = models_cmm.Meme( + id=meme_id, + user_id=user.id, + creation_time=datetime.now(UTC), + vote_score=0, + votes=[], + status=types_cmm.MemeStatus.neutral, + ) + cruds_cmm.add_meme_in_DB(db=db, meme=meme) + await db.commit() + await save_file_as_data( + upload_file=image, + directory="profile-pictures", + filename=str(meme_id), + request_id=request_id, + max_file_size=4 * 1024 * 1024, + accepted_content_types=[ + ContentType.jpg, + ContentType.png, + ContentType.webp, + ], + ) + + except Exception: + await db.rollback() + raise diff --git a/app/modules/cmm/models_cmm.py b/app/modules/cmm/models_cmm.py index d01e2b6c70..2df9dc81c9 100644 --- a/app/modules/cmm/models_cmm.py +++ b/app/modules/cmm/models_cmm.py @@ -14,18 +14,6 @@ from app.types.sqlalchemy import Base, PrimaryKey -class Report(Base): - __tablename__ = "cmm_report" - - id: Mapped[PrimaryKey] - user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) - user: Mapped[CoreUser] = relationship("CoreUser", init=False) - creation_time: Mapped[datetime] - meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("cmm_meme.id")) - meme: Mapped[Meme] = relationship("Meme", init=False) - description: Mapped[str | None] = mapped_column(default=None) - - class Vote(Base): ___tablename___ = "cmm_vote" @@ -53,9 +41,3 @@ class Meme(Base): default_factory=list, ) status: Mapped[MemeStatus] - reports: Mapped[list[Report]] = relationship( - "Report", - secondary="cmm_report", - lazy="selectin", - default_factory=list, - ) diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 67af59e3a4..636c9a231c 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -8,22 +8,6 @@ from app.modules.cmm.types_cmm import MemeStatus -class ReportBase(BaseModel): - # pour la création - meme_id: str - description: str | None - - -class Report(ReportBase): - # pour lire les report - user: CoreUserSimple - creation_time: datetime - - -class ReportComplete(Report): - id: uuid.UUID - - class VoteBase(BaseModel): meme_id: str positive: bool @@ -49,4 +33,3 @@ class Meme(MemeBase): vote_score: int votes: list[Vote] status: MemeStatus - reports: list[Report] diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index 5c0d98ee52..539dba9ff0 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -3,9 +3,7 @@ class MemeStatus(str, Enum): neutral = "neutral" - reported = "reported" banned = "banned" - allowed = "allowed" class MemeSort(str, Enum): From 6697cd9534d88765d2f4d67839eb8192ccdd19a3 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Mon, 16 Dec 2024 18:10:45 +0100 Subject: [PATCH 04/42] All basic meme endpoints --- app/modules/cmm/cruds_cmm.py | 15 ++++- app/modules/cmm/endpoints_cmm.py | 100 ++++++++++++++++++++++++++++--- app/modules/cmm/schemas_cmm.py | 2 +- 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 790407e99f..3a6ccf3925 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -1,6 +1,7 @@ from collections.abc import Sequence from datetime import UTC, date, datetime, timedelta from uuid import UUID +import uuid from sqlalchemy import delete, func, select, update from sqlalchemy.exc import IntegrityError @@ -78,11 +79,21 @@ async def get_memes_from_user( return result.scalars().all() -def add_meme_in_DB(db: AsyncSession, meme: models_cmm.Meme): +async def get_meme_by_id( + db: AsyncSession, + meme_id: uuid.UUID, +) -> models_cmm.Meme | None: + result = await db.execute( + select(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), + ) + return result.unique().scalars().first() + + +def add_meme(db: AsyncSession, meme: models_cmm.Meme): db.add(meme) -async def delete_meme_in_DB(db: AsyncSession, meme_id: UUID): +async def delete_meme_by_id(db: AsyncSession, meme_id: UUID): await db.execute( delete(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), ) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 44725fa75c..a0b8401d5d 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,11 +1,10 @@ import uuid from datetime import UTC, datetime -from fastapi import Depends, HTTPException, status +from fastapi import Depends, File, HTTPException from fastapi.datastructures import UploadFile -from fastapi import File +from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from starlette.routing import request_response from app.core import models_core from app.core.groups.groups_type import AccountType, GroupType @@ -13,13 +12,16 @@ get_db, get_request_id, is_user, - is_user_a_member, - is_user_in, ) from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm from app.types.content_type import ContentType from app.types.module import Module -from app.utils.tools import save_file_as_data +from app.utils.tools import ( + delete_file_from_data, + get_file_from_data, + is_user_member_of_an_allowed_group, + save_file_as_data, +) module = Module( root="cmm", @@ -29,7 +31,7 @@ @module.router.get( - "/memes/{n_page}", + "/memes", response_model=list[schemas_cmm.Meme], status_code=200, ) @@ -83,6 +85,86 @@ async def get_memes( return meme_page +@module.router.get( + "/memes/{meme_id}", + status_code=200, + response_model=schemas_cmm.Meme, +) +async def get_meme_by_id( + meme_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Get a meme caracteristics using its id + """ + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) + if meme is None: + raise HTTPException(status_code=204, detail="The meme does not exist") + + return meme + + +@module.router.get( + "/memes/{meme_id}/img", + status_code=200, + response_model=FileResponse, +) +async def get_meme_image_by_id( + meme_id: uuid.UUID, + db: AsyncSession = Depends(get_db), +): + """ + Get a meme image using its id + """ + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) + if meme is None: + raise HTTPException(status_code=404, detail="The meme does not exist") + + # TODO: Change default asset + return get_file_from_data( + default_asset="assets/pdf/default_ph.pdf", + directory="memes", + filename=str(meme_id), + ) + + +@module.router.delete( + "memes/{meme_id}", + status_code=204, +) +async def delete_meme_by_id( + meme_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Remove a meme from db + Must be admin or author of meme + """ + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) + if not meme: + raise HTTPException( + status_code=404, + detail="Invalid meme_id", + ) + if not ( + meme.user_id == user.id + or is_user_member_of_an_allowed_group(user, [GroupType.admin]) + ): + raise HTTPException( + status_code=403, + detail="You cannot remove a meme from another user", + ) + try: + await cruds_cmm.delete_meme_by_id(db=db, meme_id=meme_id) + await delete_file_from_data(directory="meme", filename=str(meme_id)) + await db.commit() + except Exception: + await db.rollback() + raise + + @module.router.post( "/memes", response_model=schemas_cmm.Meme, @@ -107,11 +189,11 @@ async def add_meme( votes=[], status=types_cmm.MemeStatus.neutral, ) - cruds_cmm.add_meme_in_DB(db=db, meme=meme) + cruds_cmm.add_meme(db=db, meme=meme) await db.commit() await save_file_as_data( upload_file=image, - directory="profile-pictures", + directory="memes", filename=str(meme_id), request_id=request_id, max_file_size=4 * 1024 * 1024, diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 636c9a231c..d43fde4bed 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -23,7 +23,7 @@ class VoteComplete(Vote): class MemeBase(BaseModel): - image: UploadFile + pass class Meme(MemeBase): From baf2b0fb83758c17db2443c2e06360b3594f7be8 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Wed, 18 Dec 2024 11:21:36 +0100 Subject: [PATCH 05/42] Commit for peer review --- app/modules/cmm/cruds_cmm.py | 14 ++++++++++++++ app/modules/cmm/endpoints_cmm.py | 9 +++++++-- app/modules/cmm/schemas_cmm.py | 15 +++++++-------- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 3a6ccf3925..8cff292bdb 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -99,6 +99,20 @@ async def delete_meme_by_id(db: AsyncSession, meme_id: UUID): ) +async def get_vote( + db: AsyncSession, + meme_id: UUID, + user_id: str, +) -> models_cmm.Vote | None: + result = await db.execute( + select(models_cmm.Vote).where( + models_cmm.Vote.meme_id == meme_id, + models_cmm.Vote.user_id == user_id, + ), + ) + return result.unique().scalars().first() + + async def create_vote(db: AsyncSession, vote: models_cmm.Vote): db.add(vote) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index a0b8401d5d..8ccd0cbfcc 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -88,7 +88,7 @@ async def get_memes( @module.router.get( "/memes/{meme_id}", status_code=200, - response_model=schemas_cmm.Meme, + response_model=schemas_cmm.FullMeme, ) async def get_meme_by_id( meme_id: uuid.UUID, @@ -102,7 +102,12 @@ async def get_meme_by_id( if meme is None: raise HTTPException(status_code=204, detail="The meme does not exist") - return meme + my_vote = await cruds_cmm.get_vote(db=db, meme_id=meme.id, user_id=user.id) + full_meme = schemas_cmm.FullMeme( + my_vote=my_vote.positive, + **meme.model_dump(), + ) + return full_meme @module.router.get( diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index d43fde4bed..bd0e3b2dd3 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -1,8 +1,7 @@ import uuid from datetime import datetime -from fastapi.datastructures import UploadFile -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from app.core.schemas_core import CoreUserSimple from app.modules.cmm.types_cmm import MemeStatus @@ -22,14 +21,14 @@ class VoteComplete(Vote): id: uuid.UUID -class MemeBase(BaseModel): - pass - - -class Meme(MemeBase): - # dans les get +class Meme(BaseModel): + model_config = ConfigDict(from_attributes=True) user: CoreUserSimple creation_time: datetime vote_score: int votes: list[Vote] status: MemeStatus + + +class FullMeme(Meme): + my_vote: bool From 789c94e8d1fa5206e3fc1eb2ee82f78ac3fb76e6 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Fri, 20 Dec 2024 23:07:25 +0100 Subject: [PATCH 06/42] All endpoints except ban, need fix for get_memes --- app/modules/cmm/cruds_cmm.py | 84 ++++++++++++++---- app/modules/cmm/endpoints_cmm.py | 145 ++++++++++++++++++++++++++++++- app/modules/cmm/models_cmm.py | 5 +- app/modules/cmm/schemas_cmm.py | 4 +- app/modules/cmm/types_cmm.py | 19 ++++ 5 files changed, 233 insertions(+), 24 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 8cff292bdb..e4c2b04fa8 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -1,14 +1,12 @@ -from collections.abc import Sequence -from datetime import UTC, date, datetime, timedelta -from uuid import UUID import uuid +from datetime import UTC, datetime, timedelta +from uuid import UUID -from sqlalchemy import delete, func, select, update -from sqlalchemy.exc import IntegrityError +from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import noload, selectinload +from sqlalchemy.orm import selectinload -from app.modules.cmm import models_cmm, types_cmm +from app.modules.cmm import models_cmm n_memes = 10 n_weeks = 7 @@ -18,7 +16,8 @@ async def get_memes_by_date( db: AsyncSession, n_page: int, descending: bool, -) -> Sequence[models_cmm.Meme]: + user_id: str, +): result = await db.execute( select(models_cmm.Meme) .order_by( @@ -29,14 +28,20 @@ async def get_memes_by_date( .fetch(n_memes) .offset((n_page - 1) * n_memes), ) - return result.scalars().all() + votes_map = await get_my_votes_from_memes( + db=db, + user_id=user_id, + meme_result=result, + ) + return {"memes": result.scalars().all(), "votes_map": votes_map} async def get_memes_by_votes( db: AsyncSession, n_page: int, descending: bool, -) -> Sequence[models_cmm.Meme]: + user_id: str, +): result = await db.execute( select(models_cmm.Meme) .order_by( @@ -47,13 +52,19 @@ async def get_memes_by_votes( .fetch(n_memes) .offset((n_page - 1) * n_memes), ) - return result.scalars().all() + votes_map = await get_my_votes_from_memes( + db=db, + user_id=user_id, + meme_result=result, + ) + return {"memes": result.scalars().all(), "votes_map": votes_map} async def get_trending_memes( db: AsyncSession, n_page: int, -) -> Sequence[models_cmm.Meme]: + user_id: str, +): result = await db.execute( select(models_cmm.Meme) .order_by(models_cmm.Meme.vote_score) @@ -64,19 +75,29 @@ async def get_trending_memes( .fetch(n_memes) .offset((n_page - 1) * n_memes), ) - return result.scalars().all() + votes_map = await get_my_votes_from_memes( + db=db, + user_id=user_id, + meme_result=result, + ) + return {"memes": result.scalars().all(), "votes_map": votes_map} async def get_memes_from_user( db: AsyncSession, user_id: str, -) -> Sequence[models_cmm.Meme]: +): result = await db.execute( select(models_cmm.Meme) .where(models_cmm.Meme.user_id == user_id) .order_by(models_cmm.Meme.creation_time), ) - return result.scalars().all() + votes_map = await get_my_votes_from_memes( + db=db, + user_id=user_id, + meme_result=result, + ) + return {"memes": result.scalars().all(), "votes_map": votes_map} async def get_meme_by_id( @@ -84,11 +105,30 @@ async def get_meme_by_id( meme_id: uuid.UUID, ) -> models_cmm.Meme | None: result = await db.execute( - select(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), + select(models_cmm.Meme) + .options(selectinload(models_cmm.Meme.votes)) + .where(models_cmm.Meme.id == meme_id), ) return result.unique().scalars().first() +async def get_my_votes_from_memes( + db: AsyncSession, + user_id: str, + meme_result, +) -> dict[UUID, models_cmm.Vote]: + result = await db.execute( + select(models_cmm.Vote) + .join(meme_result, meme_result.id == models_cmm.Vote.meme_id) + .where( + models_cmm.Vote.user_id == user_id, + ), + ) + votes = result.scalars().all() + votes_map = {vote.meme_id: vote for vote in votes} + return votes_map + + def add_meme(db: AsyncSession, meme: models_cmm.Meme): db.add(meme) @@ -113,7 +153,17 @@ async def get_vote( return result.unique().scalars().first() -async def create_vote(db: AsyncSession, vote: models_cmm.Vote): +async def get_vote_by_id( + db: AsyncSession, + vote_id: UUID, +) -> models_cmm.Vote | None: + result = await db.execute( + select(models_cmm.Vote).where(models_cmm.Vote.id == vote_id), + ) + return result.unique().scalars().first() + + +def add_vote(db: AsyncSession, vote: models_cmm.Vote): db.add(vote) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 8ccd0cbfcc..371681060c 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -4,6 +4,7 @@ from fastapi import Depends, File, HTTPException from fastapi.datastructures import UploadFile from fastapi.responses import FileResponse +from pydantic import HttpUrl from sqlalchemy.ext.asyncio import AsyncSession from app.core import models_core @@ -39,6 +40,7 @@ async def get_memes( sort_by: str | None, db: AsyncSession = Depends(get_db), n_page: int = 1, + user: models_core.CoreUser = Depends(is_user()), ): """ Get a page of memes according to the asked sort @@ -55,34 +57,47 @@ async def get_memes( db=db, descending=True, n_page=n_page, + user_id=user.id, ) case types_cmm.MemeSort.worst: meme_page = await cruds_cmm.get_memes_by_votes( db=db, descending=False, n_page=n_page, + user_id=user.id, ) case types_cmm.MemeSort.trending: meme_page = await cruds_cmm.get_trending_memes( db=db, n_page=n_page, + user_id=user.id, ) case types_cmm.MemeSort.newest: meme_page = await cruds_cmm.get_memes_by_date( db=db, descending=True, n_page=n_page, + user_id=user.id, ) case types_cmm.MemeSort.oldest: meme_page = await cruds_cmm.get_memes_by_date( db=db, descending=False, n_page=n_page, + user_id=user.id, ) case _: raise HTTPException(status_code=204, detail="Invalid sort method") - return meme_page + # TODO: Doesnt work if vote doesnt exist for meme, missing key + full_meme_page = [ + schemas_cmm.FullMeme( + **meme.model_dump(), + my_vote=types_cmm.compute_vote_value(meme_page["votes_map"][meme.id]), + ) + for meme in meme_page["memes"] + ] + return full_meme_page @module.router.get( @@ -104,7 +119,7 @@ async def get_meme_by_id( my_vote = await cruds_cmm.get_vote(db=db, meme_id=meme.id, user_id=user.id) full_meme = schemas_cmm.FullMeme( - my_vote=my_vote.positive, + my_vote=types_cmm.compute_vote_value(my_vote), **meme.model_dump(), ) return full_meme @@ -212,3 +227,129 @@ async def add_meme( except Exception: await db.rollback() raise + else: + return meme + + +@module.router.get( + "/memes/{meme_id}/vote", + status_code=200, + response_model=schemas_cmm.Vote, +) +async def get_vote( + meme_id: uuid.UUID, + user_id: str, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Get a meme caracteristics using its id + """ + vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user_id) + if vote is None: + raise HTTPException( + status_code=204, detail="The meme has no vote from this user" + ) + + return vote + + +@module.router.get( + "/memes/votes/{vote_id}", + status_code=200, + response_model=schemas_cmm.Vote, +) +async def get_vote_by_id( + vote_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Get a meme caracteristics using its id + """ + vote = await cruds_cmm.get_vote_by_id(db=db, vote_id=vote_id) + if vote is None: + raise HTTPException(status_code=204, detail="The vote does not exist") + + return vote + + +@module.router.post( + "/memes/{meme_id}/vote", + response_model=schemas_cmm.Vote, + status_code=201, +) +async def add_vote( + meme_id: uuid.UUID, + positive: bool, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Add a new vote for the user to a meme from its id + """ + try: + vote_id = uuid.uuid4() + vote = models_cmm.Vote( + id=vote_id, + meme_id=meme_id, + user_id=user.id, + positive=positive, + ) + cruds_cmm.add_vote(db=db, vote=vote) + await db.commit() + except Exception: + await db.rollback() + raise + else: + return vote + + +@module.router.delete( + "/memes/{meme_id}/vote", + status_code=201, +) +async def delete_vote( + meme_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Remove the vote from the user if it exists + """ + vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) + if vote is None: + raise HTTPException(status_code=404, detail="The vote does not exist") + try: + await cruds_cmm.delete_vote(db=db, vote_id=vote.id) + await db.commit() + except Exception: + await db.rollback() + raise + + +@module.router.patch( + "/memes/{meme_id}/vote", + status_code=201, +) +async def update_vote( + meme_id: uuid.UUID, + positive: bool, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Update a vote from the user if it exists even if vote is already at the right positivity + """ + vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) + if vote is None: + raise HTTPException(status_code=404, detail="The vote does not exist") + try: + await cruds_cmm.update_vote(db=db, vote_id=vote.id, new_positive=positive) + await db.commit() + except Exception: + await db.rollback() + raise + else: + vote.positive = positive + return vote diff --git a/app/modules/cmm/models_cmm.py b/app/modules/cmm/models_cmm.py index 2df9dc81c9..ae6b630e48 100644 --- a/app/modules/cmm/models_cmm.py +++ b/app/modules/cmm/models_cmm.py @@ -1,5 +1,5 @@ -from datetime import datetime import uuid +from datetime import datetime from typing import TYPE_CHECKING from sqlalchemy import ForeignKey @@ -20,7 +20,6 @@ class Vote(Base): id: Mapped[PrimaryKey] user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) user: Mapped[CoreUser] = relationship("CoreUser", init=False) - creation_time: Mapped[datetime] meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("cmm_meme.id")) meme: Mapped[Meme] = relationship("Meme", init=False) positive: bool @@ -35,7 +34,7 @@ class Meme(Base): creation_time: Mapped[datetime] vote_score: Mapped[int] votes: Mapped[list[Vote]] = relationship( - "Votes", + "Vote", secondary="cmm_vote", lazy="selectin", default_factory=list, diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index bd0e3b2dd3..595bcec373 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, ConfigDict from app.core.schemas_core import CoreUserSimple -from app.modules.cmm.types_cmm import MemeStatus +from app.modules.cmm.types_cmm import MemeStatus, VoteValue class VoteBase(BaseModel): @@ -31,4 +31,4 @@ class Meme(BaseModel): class FullMeme(Meme): - my_vote: bool + my_vote: VoteValue diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index 539dba9ff0..9fb24c3c20 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -1,5 +1,7 @@ from enum import Enum +from app.modules.cmm.models_cmm import Vote + class MemeStatus(str, Enum): neutral = "neutral" @@ -12,3 +14,20 @@ class MemeSort(str, Enum): trending = "trending" newest = "newest" oldest = "oldest" + + +class VoteValue(str, Enum): + down = "down" + up = "up" + neutral = "neutral" + + +def compute_vote_value(vote: Vote | None) -> VoteValue: + if vote is None: + my_vote_value = VoteValue.neutral + elif vote.positive: + my_vote_value = VoteValue.up + else: + my_vote_value = VoteValue.down + + return my_vote_value From 85e06078b13b675069d16bfb9652f433a2c7fc9f Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 21 Dec 2024 18:04:54 +0100 Subject: [PATCH 07/42] All working endpoints, tests to do next --- app/modules/cmm/cruds_cmm.py | 80 ++++++++++++++++++- app/modules/cmm/endpoints_cmm.py | 129 ++++++++++++++++++++++++++++--- app/modules/cmm/models_cmm.py | 12 +++ app/modules/cmm/schemas_cmm.py | 7 ++ 4 files changed, 216 insertions(+), 12 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index e4c2b04fa8..3dd5538740 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -1,4 +1,5 @@ import uuid +from collections.abc import Sequence from datetime import UTC, datetime, timedelta from uuid import UUID @@ -6,7 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from app.modules.cmm import models_cmm +from app.modules.cmm import models_cmm, types_cmm n_memes = 10 n_weeks = 7 @@ -100,6 +101,18 @@ async def get_memes_from_user( return {"memes": result.scalars().all(), "votes_map": votes_map} +async def update_ban_status_of_memes_from_user( + db: AsyncSession, + user_id: str, + new_ban_status: types_cmm.MemeStatus, +): + await db.execute( + update(models_cmm.Meme) + .where(models_cmm.Meme.user_id == user_id) + .values({models_cmm.Meme.status: new_ban_status}), + ) + + async def get_meme_by_id( db: AsyncSession, meme_id: uuid.UUID, @@ -133,6 +146,18 @@ def add_meme(db: AsyncSession, meme: models_cmm.Meme): db.add(meme) +async def update_meme_ban_status( + db: AsyncSession, + ban_status: types_cmm.MemeStatus, + meme_id: UUID, +): + await db.execute( + update(models_cmm.Meme) + .where(models_cmm.Meme.id == meme_id) + .values({models_cmm.Meme.status: ban_status}), + ) + + async def delete_meme_by_id(db: AsyncSession, meme_id: UUID): await db.execute( delete(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), @@ -179,3 +204,56 @@ async def delete_vote(db: AsyncSession, vote_id: UUID): await db.execute( delete(models_cmm.Vote).where(models_cmm.Vote.id == vote_id), ) + + +async def get_ban_by_id( + db: AsyncSession, + ban_id: UUID, +) -> models_cmm.Ban | None: + result = await db.execute( + select(models_cmm.Ban).where(models_cmm.Ban.id == ban_id), + ) + return result.unique().scalars().first() + + +async def get_user_current_ban( + db: AsyncSession, + user_id: str, +) -> models_cmm.Ban | None: + result = await db.execute( + select(models_cmm.Ban).where( + models_cmm.Ban.user_id == user_id, + models_cmm.Ban.end_time.is_(None), + ), + ) + return result.unique().scalars().first() + + +async def get_user_ban_history( + db: AsyncSession, + user_id: str, +) -> Sequence[models_cmm.Ban]: + result = await db.execute( + select(models_cmm.Ban) + .where(models_cmm.Ban.user_id == user_id) + .order_by(models_cmm.Ban.creation_time), + ) + return result.scalars().all() + + +def add_user_ban(db: AsyncSession, ban: models_cmm.Ban): + db.add(ban) + + +async def update_end_of_ban(db: AsyncSession, end_time: datetime, ban_id: UUID): + await db.execute( + update(models_cmm.Ban) + .where(models_cmm.Ban.id == ban_id) + .values({models_cmm.Ban.end_time: end_time}), + ) + + +async def delete_ban(db: AsyncSession, ban_id: UUID): + await db.execute( + delete(models_cmm.Ban).where(models_cmm.Ban.id == ban_id), + ) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 371681060c..3fb68ab63f 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -4,7 +4,6 @@ from fastapi import Depends, File, HTTPException from fastapi.datastructures import UploadFile from fastapi.responses import FileResponse -from pydantic import HttpUrl from sqlalchemy.ext.asyncio import AsyncSession from app.core import models_core @@ -160,7 +159,7 @@ async def delete_meme_by_id( ): """ Remove a meme from db - Must be admin or author of meme + Must author of meme if meme is not banned or admin """ meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) if not meme: @@ -168,14 +167,18 @@ async def delete_meme_by_id( status_code=404, detail="Invalid meme_id", ) - if not ( - meme.user_id == user.id - or is_user_member_of_an_allowed_group(user, [GroupType.admin]) - ): - raise HTTPException( - status_code=403, - detail="You cannot remove a meme from another user", - ) + if not is_user_member_of_an_allowed_group(user, [GroupType.admin]): + if meme.user_id != user.id: + raise HTTPException( + status_code=403, + detail="You cannot remove a meme from another user", + ) + elif meme.status != types_cmm.MemeStatus.neutral: + raise HTTPException( + status_code=403, + detail="You cannot remove your meme if it is banned ", + ) + try: await cruds_cmm.delete_meme_by_id(db=db, meme_id=meme_id) await delete_file_from_data(directory="meme", filename=str(meme_id)) @@ -248,7 +251,8 @@ async def get_vote( vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user_id) if vote is None: raise HTTPException( - status_code=204, detail="The meme has no vote from this user" + status_code=204, + detail="The meme has no vote from this user", ) return vote @@ -353,3 +357,106 @@ async def update_vote( else: vote.positive = positive return vote + + +@module.router.post( + "/users/{user_id}/ban", + status_code=201, + response_model=models_cmm.Ban, +) +async def ban_user( + user_id: str, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Ban a user and hide all of his memes + Must be admin + """ + if not is_user_member_of_an_allowed_group(user, [GroupType.admin]): + raise HTTPException( + status_code=401, + detail="Cannot ban another user", + ) + + current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user_id) + if current_ban is not None: + raise HTTPException(status_code=404, detail="User is already banned") + + ban = models_cmm.Ban( + id=uuid.uuid4(), + user_id=user_id, + admin_id=user.id, + end_time=None, + creation_time=datetime.now(UTC), + ) + try: + cruds_cmm.add_user_ban(db=db, ban=ban) + await cruds_cmm.update_ban_status_of_memes_from_user( + db=db, + user_id=user_id, + new_ban_status=types_cmm.MemeStatus.banned, + ) + await db.commit() + except Exception: + await db.rollback() + raise + else: + return ban + + +@module.router.post( + "/users/{user_id}/unban", + status_code=201, +) +async def unban_user( + user_id: str, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Unban a user and unhide all of his memes + Must be admin + """ + if not is_user_member_of_an_allowed_group(user, [GroupType.admin]): + raise HTTPException( + status_code=401, + detail="Cannot unban another user", + ) + + current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user_id) + if current_ban is None: + raise HTTPException(status_code=404, detail="User is not already banned") + + try: + await cruds_cmm.update_end_of_ban( + db=db, + ban_id=current_ban.id, + end_time=datetime.now(UTC), + ) + await cruds_cmm.update_ban_status_of_memes_from_user( + db=db, + user_id=user_id, + new_ban_status=types_cmm.MemeStatus.neutral, + ) + await db.commit() + except Exception: + await db.rollback() + raise + + +@module.router.get( + "/users/{user_id}/ban_history", + status_code=200, + response_model=list[schemas_cmm.Ban], +) +async def get_user_ban_history( + user_id: str, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +): + """ + Get the ban history of an user + """ + ban_history = await cruds_cmm.get_user_ban_history(db=db, user_id=user_id) + return ban_history diff --git a/app/modules/cmm/models_cmm.py b/app/modules/cmm/models_cmm.py index ae6b630e48..3f4615c849 100644 --- a/app/modules/cmm/models_cmm.py +++ b/app/modules/cmm/models_cmm.py @@ -40,3 +40,15 @@ class Meme(Base): default_factory=list, ) status: Mapped[MemeStatus] + + +class Ban(Base): + __tablename__ = "cmm_ban" + + id: Mapped[PrimaryKey] + user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) + user: Mapped[CoreUser] = relationship("CoreUser", init=False) + creation_time: Mapped[datetime] + end_time: Mapped[datetime | None] + admin_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) + admin: Mapped[CoreUser] = relationship("CoreUser", init=False) diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 595bcec373..e0cff353eb 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -32,3 +32,10 @@ class Meme(BaseModel): class FullMeme(Meme): my_vote: VoteValue + + +class Ban(BaseModel): + creation_time: datetime + end_time: datetime | None + user: CoreUserSimple + admin: CoreUserSimple From 498ce26c034d6abe81f840c458702c670b9c807e Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Mon, 23 Dec 2024 16:57:59 +0100 Subject: [PATCH 08/42] Inbetween commit --- app/modules/cmm/cruds_cmm.py | 34 ++++++++++++------ app/modules/cmm/endpoints_cmm.py | 59 +++++++++++++++++--------------- app/modules/cmm/models_cmm.py | 26 +++++++------- app/modules/cmm/types_cmm.py | 21 +++--------- tests/test_cmm.py | 16 +++++++++ 5 files changed, 89 insertions(+), 67 deletions(-) create mode 100644 tests/test_cmm.py diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 3dd5538740..af6e585e8d 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -7,20 +7,29 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from app.modules.cmm import models_cmm, types_cmm +from app.modules.cmm import models_cmm, schemas_cmm, types_cmm n_memes = 10 n_weeks = 7 +async def compute_schemas(meme_page, votes_map): + full_meme_page = [schemas_cmm.FullMeme(**meme.model_dump()) for meme in meme_page] + for i, meme in enumerate(meme_page): + if meme.id in votes_map: + full_meme_page[i].my_vote = meme.positive + return full_meme_page + + async def get_memes_by_date( db: AsyncSession, n_page: int, descending: bool, user_id: str, -): +) -> Sequence[schemas_cmm.FullMeme]: result = await db.execute( select(models_cmm.Meme) + .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) .order_by( models_cmm.Meme.creation_time.desc() if descending @@ -34,7 +43,7 @@ async def get_memes_by_date( user_id=user_id, meme_result=result, ) - return {"memes": result.scalars().all(), "votes_map": votes_map} + return await compute_schemas(result.scalars().all(), votes_map) async def get_memes_by_votes( @@ -42,9 +51,10 @@ async def get_memes_by_votes( n_page: int, descending: bool, user_id: str, -): +) -> Sequence[schemas_cmm.FullMeme]: result = await db.execute( select(models_cmm.Meme) + .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) .order_by( models_cmm.Meme.vote_score.desc() if descending @@ -58,20 +68,21 @@ async def get_memes_by_votes( user_id=user_id, meme_result=result, ) - return {"memes": result.scalars().all(), "votes_map": votes_map} + return await compute_schemas(result.scalars().all(), votes_map) async def get_trending_memes( db: AsyncSession, n_page: int, user_id: str, -): +) -> Sequence[schemas_cmm.FullMeme]: result = await db.execute( select(models_cmm.Meme) .order_by(models_cmm.Meme.vote_score) .where( (models_cmm.Meme.creation_time - datetime.now(tz=UTC)) < timedelta(days=n_weeks), + models_cmm.Meme.status == types_cmm.MemeStatus.neutral, ) .fetch(n_memes) .offset((n_page - 1) * n_memes), @@ -81,16 +92,19 @@ async def get_trending_memes( user_id=user_id, meme_result=result, ) - return {"memes": result.scalars().all(), "votes_map": votes_map} + return await compute_schemas(result.scalars().all(), votes_map) async def get_memes_from_user( db: AsyncSession, user_id: str, -): +) -> Sequence[schemas_cmm.FullMeme]: result = await db.execute( select(models_cmm.Meme) - .where(models_cmm.Meme.user_id == user_id) + .where( + models_cmm.Meme.user_id == user_id, + models_cmm.Meme.status == types_cmm.MemeStatus.neutral, + ) .order_by(models_cmm.Meme.creation_time), ) votes_map = await get_my_votes_from_memes( @@ -98,7 +112,7 @@ async def get_memes_from_user( user_id=user_id, meme_result=result, ) - return {"memes": result.scalars().all(), "votes_map": votes_map} + return await compute_schemas(result.scalars().all(), votes_map) async def update_ban_status_of_memes_from_user( diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 3fb68ab63f..b472cf53ba 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -30,10 +30,20 @@ ) +async def verify_ban_status( + db, + user, +): + user_current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user.id) + if user_current_ban is not None: + raise HTTPException(status_code=403, detail="You are currently banned") + + @module.router.get( - "/memes", + "/cmm/memes", response_model=list[schemas_cmm.Meme], status_code=200, + dependencies=[Depends(verify_ban_status)], ) async def get_memes( sort_by: str | None, @@ -52,34 +62,34 @@ async def get_memes( match sort_by: case types_cmm.MemeSort.best: - meme_page = await cruds_cmm.get_memes_by_votes( + full_meme_page = await cruds_cmm.get_memes_by_votes( db=db, descending=True, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.worst: - meme_page = await cruds_cmm.get_memes_by_votes( + full_meme_page = await cruds_cmm.get_memes_by_votes( db=db, descending=False, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.trending: - meme_page = await cruds_cmm.get_trending_memes( + full_meme_page = await cruds_cmm.get_trending_memes( db=db, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.newest: - meme_page = await cruds_cmm.get_memes_by_date( + full_meme_page = await cruds_cmm.get_memes_by_date( db=db, descending=True, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.oldest: - meme_page = await cruds_cmm.get_memes_by_date( + full_meme_page = await cruds_cmm.get_memes_by_date( db=db, descending=False, n_page=n_page, @@ -88,19 +98,11 @@ async def get_memes( case _: raise HTTPException(status_code=204, detail="Invalid sort method") - # TODO: Doesnt work if vote doesnt exist for meme, missing key - full_meme_page = [ - schemas_cmm.FullMeme( - **meme.model_dump(), - my_vote=types_cmm.compute_vote_value(meme_page["votes_map"][meme.id]), - ) - for meme in meme_page["memes"] - ] return full_meme_page @module.router.get( - "/memes/{meme_id}", + "/cmm/memes/{meme_id}", status_code=200, response_model=schemas_cmm.FullMeme, ) @@ -118,20 +120,21 @@ async def get_meme_by_id( my_vote = await cruds_cmm.get_vote(db=db, meme_id=meme.id, user_id=user.id) full_meme = schemas_cmm.FullMeme( - my_vote=types_cmm.compute_vote_value(my_vote), **meme.model_dump(), + my_vote=types_cmm.VoteValue(None if my_vote is None else my_vote.positive), ) return full_meme @module.router.get( - "/memes/{meme_id}/img", + "/cmm/memes/{meme_id}/img", status_code=200, - response_model=FileResponse, + response_class=FileResponse, ) async def get_meme_image_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), ): """ Get a meme image using its id @@ -149,7 +152,7 @@ async def get_meme_image_by_id( @module.router.delete( - "memes/{meme_id}", + "/cmm/memes/{meme_id}", status_code=204, ) async def delete_meme_by_id( @@ -189,7 +192,7 @@ async def delete_meme_by_id( @module.router.post( - "/memes", + "/cmm/memes", response_model=schemas_cmm.Meme, status_code=201, ) @@ -235,7 +238,7 @@ async def add_meme( @module.router.get( - "/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote", status_code=200, response_model=schemas_cmm.Vote, ) @@ -259,7 +262,7 @@ async def get_vote( @module.router.get( - "/memes/votes/{vote_id}", + "/cmm/memes/votes/{vote_id}", status_code=200, response_model=schemas_cmm.Vote, ) @@ -279,7 +282,7 @@ async def get_vote_by_id( @module.router.post( - "/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote", response_model=schemas_cmm.Vote, status_code=201, ) @@ -310,7 +313,7 @@ async def add_vote( @module.router.delete( - "/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote", status_code=201, ) async def delete_vote( @@ -333,7 +336,7 @@ async def delete_vote( @module.router.patch( - "/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote", status_code=201, ) async def update_vote( @@ -360,7 +363,7 @@ async def update_vote( @module.router.post( - "/users/{user_id}/ban", + "/cmm/users/{user_id}/ban", status_code=201, response_model=models_cmm.Ban, ) @@ -406,7 +409,7 @@ async def ban_user( @module.router.post( - "/users/{user_id}/unban", + "/cmm/users/{user_id}/unban", status_code=201, ) async def unban_user( @@ -446,7 +449,7 @@ async def unban_user( @module.router.get( - "/users/{user_id}/ban_history", + "/cmm/users/{user_id}/ban_history", status_code=200, response_model=list[schemas_cmm.Ban], ) diff --git a/app/modules/cmm/models_cmm.py b/app/modules/cmm/models_cmm.py index 3f4615c849..f90a86db85 100644 --- a/app/modules/cmm/models_cmm.py +++ b/app/modules/cmm/models_cmm.py @@ -1,27 +1,22 @@ import uuid from datetime import datetime -from typing import TYPE_CHECKING from sqlalchemy import ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.models_core import CoreUser - -if TYPE_CHECKING: - from app.modules.cmm.models_cmm import Meme - from app.modules.cmm.types_cmm import MemeStatus from app.types.sqlalchemy import Base, PrimaryKey class Vote(Base): - ___tablename___ = "cmm_vote" + __tablename__ = "cmm_vote" id: Mapped[PrimaryKey] user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) user: Mapped[CoreUser] = relationship("CoreUser", init=False) meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("cmm_meme.id")) - meme: Mapped[Meme] = relationship("Meme", init=False) + meme: Mapped["Meme"] = relationship("Meme", init=False) positive: bool @@ -29,17 +24,16 @@ class Meme(Base): __tablename__ = "cmm_meme" id: Mapped[PrimaryKey] + status: Mapped[MemeStatus] user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) user: Mapped[CoreUser] = relationship("CoreUser", init=False) creation_time: Mapped[datetime] vote_score: Mapped[int] - votes: Mapped[list[Vote]] = relationship( + votes: Mapped[list["Vote"]] = relationship( "Vote", - secondary="cmm_vote", lazy="selectin", default_factory=list, ) - status: Mapped[MemeStatus] class Ban(Base): @@ -47,8 +41,16 @@ class Ban(Base): id: Mapped[PrimaryKey] user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) - user: Mapped[CoreUser] = relationship("CoreUser", init=False) + user: Mapped[CoreUser] = relationship( + "CoreUser", + init=False, + foreign_keys=[user_id], + ) creation_time: Mapped[datetime] end_time: Mapped[datetime | None] admin_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) - admin: Mapped[CoreUser] = relationship("CoreUser", init=False) + admin: Mapped[CoreUser] = relationship( + "CoreUser", + init=False, + foreign_keys=[admin_id], + ) diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index 9fb24c3c20..014f377f15 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -1,7 +1,5 @@ from enum import Enum -from app.modules.cmm.models_cmm import Vote - class MemeStatus(str, Enum): neutral = "neutral" @@ -16,18 +14,7 @@ class MemeSort(str, Enum): oldest = "oldest" -class VoteValue(str, Enum): - down = "down" - up = "up" - neutral = "neutral" - - -def compute_vote_value(vote: Vote | None) -> VoteValue: - if vote is None: - my_vote_value = VoteValue.neutral - elif vote.positive: - my_vote_value = VoteValue.up - else: - my_vote_value = VoteValue.down - - return my_vote_value +class VoteValue(Enum): + down = False + up = True + neutral = None diff --git a/tests/test_cmm.py b/tests/test_cmm.py new file mode 100644 index 0000000000..9d89e32aec --- /dev/null +++ b/tests/test_cmm.py @@ -0,0 +1,16 @@ +""" +* [ ] All crud operations on meme + * [ ] Ajout de n memes avec les valeurs bien choisies pour avoir les sort par plusieurs users + * [ ] Delete de 1 meme + * [ ] read d'une page de meme dans tous les sens +* [ ] All crud operations on vote + * [ ] Vote sur 1 meme par plusieurs user + * [ ] Changement du vote + * [ ] Delete du vote + * [ ] Read d'une page meme avec mes votes +* [ ] All crud operations on ban + * [ ] Ban d'un user et impact sur ses memes + * [ ] Unban d'un user et impact sur ses memes +* [ ] Testing edge cases with ban + * [ ] Call d'une méthode par un user ban +""" From abcf8a86acf054bebe648d442e22b4d1e62e101a Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:01:48 +0100 Subject: [PATCH 09/42] Fix cruds --- app/modules/cmm/cruds_cmm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index af6e585e8d..19fb3424cc 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -13,7 +13,7 @@ n_weeks = 7 -async def compute_schemas(meme_page, votes_map): +async def compute_schemas(meme_page, votes_map) -> Sequence[schemas_cmm.FullMeme]: full_meme_page = [schemas_cmm.FullMeme(**meme.model_dump()) for meme in meme_page] for i, meme in enumerate(meme_page): if meme.id in votes_map: From 567efe3be5cd62778c07b7f13e1b09ab39da957f Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Tue, 24 Dec 2024 00:49:49 +0100 Subject: [PATCH 10/42] First passing test --- app/modules/cmm/cruds_cmm.py | 57 ++++++++++------- app/modules/cmm/endpoints_cmm.py | 39 ++++++------ app/modules/cmm/models_cmm.py | 9 ++- app/modules/cmm/schemas_cmm.py | 6 +- tests/test_cmm.py | 102 +++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 43 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 19fb3424cc..263585ec62 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -11,10 +11,20 @@ n_memes = 10 n_weeks = 7 +# TODO: Update meme vote_score with votes -async def compute_schemas(meme_page, votes_map) -> Sequence[schemas_cmm.FullMeme]: - full_meme_page = [schemas_cmm.FullMeme(**meme.model_dump()) for meme in meme_page] +async def compute_schemas(meme_page, votes_map) -> list[schemas_cmm.ShownMeme]: + full_meme_page = [ + schemas_cmm.ShownMeme( + user=meme.user, + creation_time=meme.creation_time, + vote_score=meme.vote_score, + status=meme.status, + my_vote=types_cmm.VoteValue.neutral, + ) + for meme in meme_page + ] for i, meme in enumerate(meme_page): if meme.id in votes_map: full_meme_page[i].my_vote = meme.positive @@ -26,7 +36,7 @@ async def get_memes_by_date( n_page: int, descending: bool, user_id: str, -) -> Sequence[schemas_cmm.FullMeme]: +) -> Sequence[schemas_cmm.ShownMeme]: result = await db.execute( select(models_cmm.Meme) .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) @@ -35,15 +45,16 @@ async def get_memes_by_date( if descending else models_cmm.Meme.creation_time, ) - .fetch(n_memes) + .limit(n_memes) .offset((n_page - 1) * n_memes), ) + meme_page = result.scalars().all() votes_map = await get_my_votes_from_memes( db=db, user_id=user_id, - meme_result=result, + meme_page=meme_page, ) - return await compute_schemas(result.scalars().all(), votes_map) + return await compute_schemas(meme_page, votes_map) async def get_memes_by_votes( @@ -51,31 +62,33 @@ async def get_memes_by_votes( n_page: int, descending: bool, user_id: str, -) -> Sequence[schemas_cmm.FullMeme]: +) -> Sequence[schemas_cmm.ShownMeme]: result = await db.execute( select(models_cmm.Meme) .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) + .options(selectinload("*")) .order_by( models_cmm.Meme.vote_score.desc() if descending else models_cmm.Meme.vote_score, ) - .fetch(n_memes) + .limit(n_memes) .offset((n_page - 1) * n_memes), ) + meme_page = result.scalars().all() votes_map = await get_my_votes_from_memes( db=db, user_id=user_id, - meme_result=result, + meme_page=meme_page, ) - return await compute_schemas(result.scalars().all(), votes_map) + return await compute_schemas(meme_page, votes_map) async def get_trending_memes( db: AsyncSession, n_page: int, user_id: str, -) -> Sequence[schemas_cmm.FullMeme]: +) -> Sequence[schemas_cmm.ShownMeme]: result = await db.execute( select(models_cmm.Meme) .order_by(models_cmm.Meme.vote_score) @@ -84,21 +97,22 @@ async def get_trending_memes( < timedelta(days=n_weeks), models_cmm.Meme.status == types_cmm.MemeStatus.neutral, ) - .fetch(n_memes) + .limit(n_memes) .offset((n_page - 1) * n_memes), ) + meme_page = result.scalars().all() votes_map = await get_my_votes_from_memes( db=db, user_id=user_id, - meme_result=result, + meme_page=meme_page, ) - return await compute_schemas(result.scalars().all(), votes_map) + return await compute_schemas(meme_page, votes_map) async def get_memes_from_user( db: AsyncSession, user_id: str, -) -> Sequence[schemas_cmm.FullMeme]: +) -> Sequence[schemas_cmm.ShownMeme]: result = await db.execute( select(models_cmm.Meme) .where( @@ -107,12 +121,13 @@ async def get_memes_from_user( ) .order_by(models_cmm.Meme.creation_time), ) + meme_page = result.scalars().all() votes_map = await get_my_votes_from_memes( db=db, user_id=user_id, - meme_result=result, + meme_page=meme_page, ) - return await compute_schemas(result.scalars().all(), votes_map) + return await compute_schemas(meme_page, votes_map) async def update_ban_status_of_memes_from_user( @@ -142,13 +157,13 @@ async def get_meme_by_id( async def get_my_votes_from_memes( db: AsyncSession, user_id: str, - meme_result, + meme_page, ) -> dict[UUID, models_cmm.Vote]: + meme_ids = [meme.id for meme in meme_page] result = await db.execute( - select(models_cmm.Vote) - .join(meme_result, meme_result.id == models_cmm.Vote.meme_id) - .where( + select(models_cmm.Vote).where( models_cmm.Vote.user_id == user_id, + models_cmm.Vote.meme_id.in_(meme_ids), ), ) votes = result.scalars().all() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index b472cf53ba..627bb65505 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -40,20 +40,21 @@ async def verify_ban_status( @module.router.get( - "/cmm/memes", - response_model=list[schemas_cmm.Meme], + "/cmm/memes/", + response_model=list[schemas_cmm.ShownMeme], status_code=200, - dependencies=[Depends(verify_ban_status)], ) async def get_memes( - sort_by: str | None, - db: AsyncSession = Depends(get_db), + sort_by: str = "best", n_page: int = 1, + db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_user()), ): """ Get a page of memes according to the asked sort """ + print(sort_by) + print(n_page) if n_page < 1: raise HTTPException( status_code=204, @@ -102,9 +103,9 @@ async def get_memes( @module.router.get( - "/cmm/memes/{meme_id}", + "/cmm/memes/{meme_id}/", status_code=200, - response_model=schemas_cmm.FullMeme, + response_model=schemas_cmm.ShownMeme, ) async def get_meme_by_id( meme_id: uuid.UUID, @@ -119,7 +120,7 @@ async def get_meme_by_id( raise HTTPException(status_code=204, detail="The meme does not exist") my_vote = await cruds_cmm.get_vote(db=db, meme_id=meme.id, user_id=user.id) - full_meme = schemas_cmm.FullMeme( + full_meme = schemas_cmm.ShownMeme( **meme.model_dump(), my_vote=types_cmm.VoteValue(None if my_vote is None else my_vote.positive), ) @@ -127,7 +128,7 @@ async def get_meme_by_id( @module.router.get( - "/cmm/memes/{meme_id}/img", + "/cmm/memes/{meme_id}/img/", status_code=200, response_class=FileResponse, ) @@ -152,7 +153,7 @@ async def get_meme_image_by_id( @module.router.delete( - "/cmm/memes/{meme_id}", + "/cmm/memes/{meme_id}/", status_code=204, ) async def delete_meme_by_id( @@ -192,7 +193,7 @@ async def delete_meme_by_id( @module.router.post( - "/cmm/memes", + "/cmm/memes/", response_model=schemas_cmm.Meme, status_code=201, ) @@ -238,7 +239,7 @@ async def add_meme( @module.router.get( - "/cmm/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote/", status_code=200, response_model=schemas_cmm.Vote, ) @@ -262,7 +263,7 @@ async def get_vote( @module.router.get( - "/cmm/memes/votes/{vote_id}", + "/cmm/memes/votes/{vote_id}/", status_code=200, response_model=schemas_cmm.Vote, ) @@ -282,7 +283,7 @@ async def get_vote_by_id( @module.router.post( - "/cmm/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote/", response_model=schemas_cmm.Vote, status_code=201, ) @@ -313,7 +314,7 @@ async def add_vote( @module.router.delete( - "/cmm/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote/", status_code=201, ) async def delete_vote( @@ -336,7 +337,7 @@ async def delete_vote( @module.router.patch( - "/cmm/memes/{meme_id}/vote", + "/cmm/memes/{meme_id}/vote/", status_code=201, ) async def update_vote( @@ -363,7 +364,7 @@ async def update_vote( @module.router.post( - "/cmm/users/{user_id}/ban", + "/cmm/users/{user_id}/ban/", status_code=201, response_model=models_cmm.Ban, ) @@ -409,7 +410,7 @@ async def ban_user( @module.router.post( - "/cmm/users/{user_id}/unban", + "/cmm/users/{user_id}/unban/", status_code=201, ) async def unban_user( @@ -449,7 +450,7 @@ async def unban_user( @module.router.get( - "/cmm/users/{user_id}/ban_history", + "/cmm/users/{user_id}/ban_history/", status_code=200, response_model=list[schemas_cmm.Ban], ) diff --git a/app/modules/cmm/models_cmm.py b/app/modules/cmm/models_cmm.py index f90a86db85..47f5bf629f 100644 --- a/app/modules/cmm/models_cmm.py +++ b/app/modules/cmm/models_cmm.py @@ -16,7 +16,11 @@ class Vote(Base): user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) user: Mapped[CoreUser] = relationship("CoreUser", init=False) meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("cmm_meme.id")) - meme: Mapped["Meme"] = relationship("Meme", init=False) + meme: Mapped["Meme"] = relationship( + "Meme", + init=False, + back_populates="votes", + ) positive: bool @@ -31,8 +35,9 @@ class Meme(Base): vote_score: Mapped[int] votes: Mapped[list["Vote"]] = relationship( "Vote", - lazy="selectin", default_factory=list, + lazy="selectin", + back_populates="meme", ) diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index e0cff353eb..583cdc54d9 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -30,8 +30,12 @@ class Meme(BaseModel): status: MemeStatus -class FullMeme(Meme): +class ShownMeme(BaseModel): + user: CoreUserSimple + creation_time: datetime my_vote: VoteValue + vote_score: int + status: MemeStatus class Ban(BaseModel): diff --git a/tests/test_cmm.py b/tests/test_cmm.py index 9d89e32aec..39ea8cfa1d 100644 --- a/tests/test_cmm.py +++ b/tests/test_cmm.py @@ -14,3 +14,105 @@ * [ ] Testing edge cases with ban * [ ] Call d'une méthode par un user ban """ + +import datetime +import uuid +from pathlib import Path + +import pytest_asyncio +from fastapi.testclient import TestClient + +from app.core import models_core +from app.core.groups.groups_type import GroupType +from app.modules.cmm import models_cmm +from app.modules.cmm.types_cmm import MemeStatus +from tests.commons import ( + add_object_to_db, + create_api_access_token, + create_user_with_groups, +) + +cmm_user_1: models_core.CoreUser +cmm_user_2: models_core.CoreUser +cmm_user_to_ban: models_core.CoreUser +cmm_admin: models_core.CoreUser + +memes_1: list[models_cmm.Meme] +memes_2: list[models_cmm.Meme] +memes_to_ban: list[models_cmm.Meme] +token_user_1: str +token_user_2: str +token_user_to_ban: str +token_admin: str + + +@pytest_asyncio.fixture(scope="module", autouse=True) +async def init_objects() -> None: + global cmm_user_1 + cmm_user_1 = await create_user_with_groups([]) + global token_cmm + token_cmm = create_api_access_token(cmm_user_1) + + global cmm_user_2 + cmm_user_2 = await create_user_with_groups([]) + + global cmm_user_to_ban + cmm_user_to_ban = await create_user_with_groups([]) + global token_user_to_ban + token_user_to_ban = create_api_access_token(cmm_user_1) + + global cmm_admin + cmm_admin = await create_user_with_groups([GroupType.admin]) + global token_admin + token_admin = create_api_access_token(cmm_user_1) + + global memes_1 + memes_1 = [ + models_cmm.Meme( + id=uuid.uuid4(), + status=MemeStatus.neutral, + user_id=cmm_user_1.id, + creation_time=datetime.datetime(24, i, 23, tzinfo=datetime.UTC), + vote_score=i, + ) + for i in range(1, 13) + ] + for meme in memes_1: + await add_object_to_db(meme) + global memes_2 + memes_2 = [ + models_cmm.Meme( + id=uuid.uuid4(), + status=MemeStatus.neutral, + user_id=cmm_user_1.id, + creation_time=datetime.datetime(24, i, 23, tzinfo=datetime.UTC), + vote_score=i, + ) + for i in range(1, 13) + ] + for meme in memes_2: + await add_object_to_db(meme) + global memes_to_ban + memes_to_ban = [ + models_cmm.Meme( + id=uuid.uuid4(), + status=MemeStatus.neutral, + user_id=cmm_user_to_ban.id, + creation_time=datetime.datetime(24, i, 23, tzinfo=datetime.UTC), + vote_score=i, + ) + for i in range(1, 13) + ] + for meme in memes_to_ban: + await add_object_to_db(meme) + + +def test_get_meme_page(client: TestClient) -> None: + response = client.get( + "/cmm/memes/", + headers={"Authorization": f"Bearer {token_cmm}"}, + ) + print(response) + print(response.status_code) + # print(response.json()) + assert 1 == 1 From 756b4ba898f63ec92a7db530ad3b70262f1ccf0e Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Tue, 24 Dec 2024 12:12:13 +0100 Subject: [PATCH 11/42] Semiworking single SQL full meme request --- app/modules/cmm/cruds_cmm.py | 64 +++++++++++++++++------------------ app/modules/cmm/models_cmm.py | 3 +- tests/test_cmm.py | 36 ++++++++++++++++---- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 263585ec62..3a08e6d3d7 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -5,7 +5,8 @@ from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload +from sqlalchemy.orm import joinedload, lazyload, selectinload +from sqlalchemy.sql.coercions import SelectStatementImpl from app.modules.cmm import models_cmm, schemas_cmm, types_cmm @@ -14,20 +15,24 @@ # TODO: Update meme vote_score with votes -async def compute_schemas(meme_page, votes_map) -> list[schemas_cmm.ShownMeme]: - full_meme_page = [ - schemas_cmm.ShownMeme( +async def compute_schemas(meme_page) -> list[schemas_cmm.ShownMeme]: + full_meme_page = [] + print(len(meme_page)) + for meme in meme_page: + if len(meme.votes) == 0: + my_vote = types_cmm.VoteValue.neutral + else: + print(meme.creation_time, meme.votes[0].positive) + my_vote = meme.votes[0].positive + shown_meme = schemas_cmm.ShownMeme( user=meme.user, creation_time=meme.creation_time, vote_score=meme.vote_score, status=meme.status, - my_vote=types_cmm.VoteValue.neutral, + my_vote=my_vote, ) - for meme in meme_page - ] - for i, meme in enumerate(meme_page): - if meme.id in votes_map: - full_meme_page[i].my_vote = meme.positive + full_meme_page.append(shown_meme) + return full_meme_page @@ -37,8 +42,15 @@ async def get_memes_by_date( descending: bool, user_id: str, ) -> Sequence[schemas_cmm.ShownMeme]: + print("get memes by date") result = await db.execute( select(models_cmm.Meme) + .options( + selectinload( + models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), + ).load_only(models_cmm.Vote.positive), + selectinload(models_cmm.Meme.user), + ) .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) .order_by( models_cmm.Meme.creation_time.desc() @@ -49,12 +61,8 @@ async def get_memes_by_date( .offset((n_page - 1) * n_memes), ) meme_page = result.scalars().all() - votes_map = await get_my_votes_from_memes( - db=db, - user_id=user_id, - meme_page=meme_page, - ) - return await compute_schemas(meme_page, votes_map) + print("avant return") + return await compute_schemas(meme_page) async def get_memes_by_votes( @@ -76,12 +84,7 @@ async def get_memes_by_votes( .offset((n_page - 1) * n_memes), ) meme_page = result.scalars().all() - votes_map = await get_my_votes_from_memes( - db=db, - user_id=user_id, - meme_page=meme_page, - ) - return await compute_schemas(meme_page, votes_map) + return await compute_schemas(meme_page) async def get_trending_memes( @@ -106,7 +109,7 @@ async def get_trending_memes( user_id=user_id, meme_page=meme_page, ) - return await compute_schemas(meme_page, votes_map) + return await compute_schemas(meme_page) async def get_memes_from_user( @@ -122,12 +125,7 @@ async def get_memes_from_user( .order_by(models_cmm.Meme.creation_time), ) meme_page = result.scalars().all() - votes_map = await get_my_votes_from_memes( - db=db, - user_id=user_id, - meme_page=meme_page, - ) - return await compute_schemas(meme_page, votes_map) + return await compute_schemas(meme_page) async def update_ban_status_of_memes_from_user( @@ -148,7 +146,7 @@ async def get_meme_by_id( ) -> models_cmm.Meme | None: result = await db.execute( select(models_cmm.Meme) - .options(selectinload(models_cmm.Meme.votes)) + .options(selectinload("*")) .where(models_cmm.Meme.id == meme_id), ) return result.unique().scalars().first() @@ -158,8 +156,9 @@ async def get_my_votes_from_memes( db: AsyncSession, user_id: str, meme_page, -) -> dict[UUID, models_cmm.Vote]: +) -> dict[UUID, bool]: meme_ids = [meme.id for meme in meme_page] + print(meme_ids) result = await db.execute( select(models_cmm.Vote).where( models_cmm.Vote.user_id == user_id, @@ -167,7 +166,8 @@ async def get_my_votes_from_memes( ), ) votes = result.scalars().all() - votes_map = {vote.meme_id: vote for vote in votes} + votes_map = {vote.meme_id: vote.positive for vote in votes} + print("votes_map", votes_map) return votes_map diff --git a/app/modules/cmm/models_cmm.py b/app/modules/cmm/models_cmm.py index 47f5bf629f..691428b7ba 100644 --- a/app/modules/cmm/models_cmm.py +++ b/app/modules/cmm/models_cmm.py @@ -20,8 +20,9 @@ class Vote(Base): "Meme", init=False, back_populates="votes", + lazy="selectin", ) - positive: bool + positive: Mapped[bool] class Meme(Base): diff --git a/tests/test_cmm.py b/tests/test_cmm.py index 39ea8cfa1d..6c61494994 100644 --- a/tests/test_cmm.py +++ b/tests/test_cmm.py @@ -38,6 +38,7 @@ cmm_admin: models_core.CoreUser memes_1: list[models_cmm.Meme] +votes_memes_1: list[models_cmm.Vote] memes_2: list[models_cmm.Meme] memes_to_ban: list[models_cmm.Meme] token_user_1: str @@ -50,11 +51,13 @@ async def init_objects() -> None: global cmm_user_1 cmm_user_1 = await create_user_with_groups([]) - global token_cmm - token_cmm = create_api_access_token(cmm_user_1) + global token_cmm_1 + token_cmm_1 = create_api_access_token(cmm_user_1) global cmm_user_2 cmm_user_2 = await create_user_with_groups([]) + global token_cmm_2 + token_cmm_2 = create_api_access_token(cmm_user_2) global cmm_user_to_ban cmm_user_to_ban = await create_user_with_groups([]) @@ -77,8 +80,29 @@ async def init_objects() -> None: ) for i in range(1, 13) ] - for meme in memes_1: + global votes_memes_1 + votes_memes_1 = [] + for i, meme in enumerate(memes_1): await add_object_to_db(meme) + if i % 2 == 0: + vote = models_cmm.Vote( + id=uuid.uuid4(), + user_id=cmm_user_1.id, + meme_id=meme.id, + positive=True, + ) + votes_memes_1.append(vote) + await add_object_to_db(vote) + if i % 2 == 1: + vote2 = models_cmm.Vote( + id=uuid.uuid4(), + user_id=cmm_user_2.id, + meme_id=meme.id, + positive=True, + ) + votes_memes_1.append(vote2) + await add_object_to_db(vote2) + global memes_2 memes_2 = [ models_cmm.Meme( @@ -109,10 +133,10 @@ async def init_objects() -> None: def test_get_meme_page(client: TestClient) -> None: response = client.get( - "/cmm/memes/", - headers={"Authorization": f"Bearer {token_cmm}"}, + "/cmm/memes/?sort_by=oldest", + headers={"Authorization": f"Bearer {token_cmm_2}"}, ) print(response) print(response.status_code) # print(response.json()) - assert 1 == 1 + assert 2 == 1 From 22ca911cbb054b722dad1824513a8e55a3538e3a Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Tue, 24 Dec 2024 12:26:31 +0100 Subject: [PATCH 12/42] Fully working single SQL request for full memes --- app/modules/cmm/cruds_cmm.py | 67 ++++++++++++++------------------ app/modules/cmm/endpoints_cmm.py | 35 ++++++++++++----- tests/test_cmm.py | 5 ++- 3 files changed, 59 insertions(+), 48 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 3a08e6d3d7..7d9f18933d 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -15,34 +15,12 @@ # TODO: Update meme vote_score with votes -async def compute_schemas(meme_page) -> list[schemas_cmm.ShownMeme]: - full_meme_page = [] - print(len(meme_page)) - for meme in meme_page: - if len(meme.votes) == 0: - my_vote = types_cmm.VoteValue.neutral - else: - print(meme.creation_time, meme.votes[0].positive) - my_vote = meme.votes[0].positive - shown_meme = schemas_cmm.ShownMeme( - user=meme.user, - creation_time=meme.creation_time, - vote_score=meme.vote_score, - status=meme.status, - my_vote=my_vote, - ) - full_meme_page.append(shown_meme) - - return full_meme_page - - async def get_memes_by_date( db: AsyncSession, n_page: int, descending: bool, user_id: str, -) -> Sequence[schemas_cmm.ShownMeme]: - print("get memes by date") +) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) .options( @@ -51,6 +29,7 @@ async def get_memes_by_date( ).load_only(models_cmm.Vote.positive), selectinload(models_cmm.Meme.user), ) + .execution_options(populate_existing=True) .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) .order_by( models_cmm.Meme.creation_time.desc() @@ -61,8 +40,7 @@ async def get_memes_by_date( .offset((n_page - 1) * n_memes), ) meme_page = result.scalars().all() - print("avant return") - return await compute_schemas(meme_page) + return meme_page async def get_memes_by_votes( @@ -70,11 +48,17 @@ async def get_memes_by_votes( n_page: int, descending: bool, user_id: str, -) -> Sequence[schemas_cmm.ShownMeme]: +) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) - .options(selectinload("*")) + .options( + selectinload( + models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), + ).load_only(models_cmm.Vote.positive), + selectinload(models_cmm.Meme.user), + ) + .execution_options(populate_existing=True) .order_by( models_cmm.Meme.vote_score.desc() if descending @@ -84,17 +68,24 @@ async def get_memes_by_votes( .offset((n_page - 1) * n_memes), ) meme_page = result.scalars().all() - return await compute_schemas(meme_page) + return meme_page async def get_trending_memes( db: AsyncSession, n_page: int, user_id: str, -) -> Sequence[schemas_cmm.ShownMeme]: +) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) .order_by(models_cmm.Meme.vote_score) + .options( + selectinload( + models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), + ).load_only(models_cmm.Vote.positive), + selectinload(models_cmm.Meme.user), + ) + .execution_options(populate_existing=True) .where( (models_cmm.Meme.creation_time - datetime.now(tz=UTC)) < timedelta(days=n_weeks), @@ -104,20 +95,22 @@ async def get_trending_memes( .offset((n_page - 1) * n_memes), ) meme_page = result.scalars().all() - votes_map = await get_my_votes_from_memes( - db=db, - user_id=user_id, - meme_page=meme_page, - ) - return await compute_schemas(meme_page) + return meme_page async def get_memes_from_user( db: AsyncSession, user_id: str, -) -> Sequence[schemas_cmm.ShownMeme]: +) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) + .options( + selectinload( + models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), + ).load_only(models_cmm.Vote.positive), + selectinload(models_cmm.Meme.user), + ) + .execution_options(populate_existing=True) .where( models_cmm.Meme.user_id == user_id, models_cmm.Meme.status == types_cmm.MemeStatus.neutral, @@ -125,7 +118,7 @@ async def get_memes_from_user( .order_by(models_cmm.Meme.creation_time), ) meme_page = result.scalars().all() - return await compute_schemas(meme_page) + return meme_page async def update_ban_status_of_memes_from_user( diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 627bb65505..c7fdb74949 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,3 +1,4 @@ +from collections.abc import Sequence import uuid from datetime import UTC, datetime @@ -39,6 +40,24 @@ async def verify_ban_status( raise HTTPException(status_code=403, detail="You are currently banned") +async def compute_full_meme_page( + meme_page: Sequence[models_cmm.Meme], +) -> list[schemas_cmm.ShownMeme]: + full_meme_page = [ + schemas_cmm.ShownMeme( + user=meme.user, + creation_time=meme.creation_time, + vote_score=meme.vote_score, + status=meme.status, + my_vote=meme.votes[0].positive + if meme.votes + else types_cmm.VoteValue.neutral, + ) + for meme in meme_page + ] + return full_meme_page + + @module.router.get( "/cmm/memes/", response_model=list[schemas_cmm.ShownMeme], @@ -53,44 +72,42 @@ async def get_memes( """ Get a page of memes according to the asked sort """ - print(sort_by) - print(n_page) if n_page < 1: raise HTTPException( - status_code=204, + status_code=404, detail="Invalid page number", ) match sort_by: case types_cmm.MemeSort.best: - full_meme_page = await cruds_cmm.get_memes_by_votes( + meme_page = await cruds_cmm.get_memes_by_votes( db=db, descending=True, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.worst: - full_meme_page = await cruds_cmm.get_memes_by_votes( + meme_page = await cruds_cmm.get_memes_by_votes( db=db, descending=False, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.trending: - full_meme_page = await cruds_cmm.get_trending_memes( + meme_page = await cruds_cmm.get_trending_memes( db=db, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.newest: - full_meme_page = await cruds_cmm.get_memes_by_date( + meme_page = await cruds_cmm.get_memes_by_date( db=db, descending=True, n_page=n_page, user_id=user.id, ) case types_cmm.MemeSort.oldest: - full_meme_page = await cruds_cmm.get_memes_by_date( + meme_page = await cruds_cmm.get_memes_by_date( db=db, descending=False, n_page=n_page, @@ -99,7 +116,7 @@ async def get_memes( case _: raise HTTPException(status_code=204, detail="Invalid sort method") - return full_meme_page + return await compute_full_meme_page(meme_page) @module.router.get( diff --git a/tests/test_cmm.py b/tests/test_cmm.py index 6c61494994..caea06df19 100644 --- a/tests/test_cmm.py +++ b/tests/test_cmm.py @@ -103,6 +103,7 @@ async def init_objects() -> None: votes_memes_1.append(vote2) await add_object_to_db(vote2) + return global memes_2 memes_2 = [ models_cmm.Meme( @@ -133,10 +134,10 @@ async def init_objects() -> None: def test_get_meme_page(client: TestClient) -> None: response = client.get( - "/cmm/memes/?sort_by=oldest", + "/cmm/memes/?sort_by=oldest&n_page=1", headers={"Authorization": f"Bearer {token_cmm_2}"}, ) print(response) print(response.status_code) - # print(response.json()) + print(response.json()) assert 2 == 1 From 929140547c9b9263f06309f2d27f7119c68ef3d5 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Mon, 6 Jan 2025 12:48:21 +0100 Subject: [PATCH 13/42] Few fixes --- app/modules/cmm/cruds_cmm.py | 12 +++++++++-- app/modules/cmm/endpoints_cmm.py | 34 +++++-------------------------- app/modules/cmm/schemas_cmm.py | 4 ++-- app/modules/cmm/types_cmm.py | 6 ------ assets/images/default_meme.png | Bin 0 -> 16138 bytes 5 files changed, 17 insertions(+), 39 deletions(-) create mode 100644 assets/images/default_meme.png diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 7d9f18933d..11c4a6483f 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -139,8 +139,16 @@ async def get_meme_by_id( ) -> models_cmm.Meme | None: result = await db.execute( select(models_cmm.Meme) - .options(selectinload("*")) - .where(models_cmm.Meme.id == meme_id), + .options( + selectinload( + models_cmm.Meme.votes.and_( + models_cmm.Vote.user_id == models_cmm.Meme.user_id, + ), + ).load_only(models_cmm.Vote.positive), + selectinload(models_cmm.Meme.user), + ) + .execution_options(populate_existing=True) + .where(models_cmm.Meme.id == meme_id) ) return result.unique().scalars().first() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index c7fdb74949..aa332d372d 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,4 +1,3 @@ -from collections.abc import Sequence import uuid from datetime import UTC, datetime @@ -40,24 +39,6 @@ async def verify_ban_status( raise HTTPException(status_code=403, detail="You are currently banned") -async def compute_full_meme_page( - meme_page: Sequence[models_cmm.Meme], -) -> list[schemas_cmm.ShownMeme]: - full_meme_page = [ - schemas_cmm.ShownMeme( - user=meme.user, - creation_time=meme.creation_time, - vote_score=meme.vote_score, - status=meme.status, - my_vote=meme.votes[0].positive - if meme.votes - else types_cmm.VoteValue.neutral, - ) - for meme in meme_page - ] - return full_meme_page - - @module.router.get( "/cmm/memes/", response_model=list[schemas_cmm.ShownMeme], @@ -132,16 +113,11 @@ async def get_meme_by_id( """ Get a meme caracteristics using its id """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) - if meme is None: + shown_meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) + if shown_meme is None: raise HTTPException(status_code=204, detail="The meme does not exist") - my_vote = await cruds_cmm.get_vote(db=db, meme_id=meme.id, user_id=user.id) - full_meme = schemas_cmm.ShownMeme( - **meme.model_dump(), - my_vote=types_cmm.VoteValue(None if my_vote is None else my_vote.positive), - ) - return full_meme + return shown_meme @module.router.get( @@ -163,7 +139,7 @@ async def get_meme_image_by_id( # TODO: Change default asset return get_file_from_data( - default_asset="assets/pdf/default_ph.pdf", + default_asset="assets/images/default_meme.png", directory="memes", filename=str(meme_id), ) @@ -180,7 +156,7 @@ async def delete_meme_by_id( ): """ Remove a meme from db - Must author of meme if meme is not banned or admin + Must be author of meme if meme is not banned or admin """ meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) if not meme: diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 583cdc54d9..0ee35f9a45 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, ConfigDict from app.core.schemas_core import CoreUserSimple -from app.modules.cmm.types_cmm import MemeStatus, VoteValue +from app.modules.cmm.types_cmm import MemeStatus class VoteBase(BaseModel): @@ -33,7 +33,7 @@ class Meme(BaseModel): class ShownMeme(BaseModel): user: CoreUserSimple creation_time: datetime - my_vote: VoteValue + my_vote: bool | None vote_score: int status: MemeStatus diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index 014f377f15..539dba9ff0 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -12,9 +12,3 @@ class MemeSort(str, Enum): trending = "trending" newest = "newest" oldest = "oldest" - - -class VoteValue(Enum): - down = False - up = True - neutral = None diff --git a/assets/images/default_meme.png b/assets/images/default_meme.png new file mode 100644 index 0000000000000000000000000000000000000000..c86797b5a4c1080540fe351d8f9fa37b6df954ca GIT binary patch literal 16138 zcmZX5byQVb7cbxe4$=(*0*6MryEy{VASGQAf)avsclV(iX+&B&MY@qjTDm*l=3c$u zd*l7%ka6~2d+)X8nscsSgeWV%M1T71DI6Rex~zu%QrV?I5-M8 zSt)V#Px^c5$klk=wSlkTRj{gje-&>v2OtRq&*-q~NjfIX8m&bAo|C zsi0!g-Qg5O?V#Y8tfreuS2z7pN9W0bQ&RJx-R7xM2lH>=zHJ}u&$>(-pFF&R%3>>n z@ZhDzu<6x61ZvRN1<=?lqv{8LPuDl5FW^nDbD;-5Q+6GWZ> zS3Uk}7U9iN{Ix|`fDahxaXd5iYqWpH$O6Z6BlH#aU7xRs)_dJweZRfCSgItyAbN;P zZu^|}khF`!8`g30kR#CY(>QE~gy4dAPwO;IwNB6dsC$p-s`p|2`c1mP$6ca@s9RdT z^@qeQ;)Fo4Znolz*8ACkWX|scsl3Aj>4Ll$F$Z>DR|=kQk2Zwv`wngs4)zVSx7O?3 zqgYOl(_8n0&Y2Q#6P5xfrCMJm2e6?hxkx34q~TuFjXf75tAHreBXqo5C9;i&Z(Ndm zAl#t8dr545D!6Bu(;?&W(0^3#v|st{;||NnXZIu?_qR;PZl@J;2;VyZT>&=6>iry>|gKlU&TI(6f1m;bcn5Kv><9=bU_|$buE6B{fbwOimX! zepTQFu zZ6cc(%GWQI#!dCZ9-BLevw|AeW#TLLo?6eAG43=iMq}P4#a?op!+fjV!_CxOy>rS1 z(ZEP39PUF|m>$e$iu9<2^4iQdJ3Vu#_O zg<%eq#H&+Ta?PqEF}HkR=pwmlgvNVV1JYp`1S6QnK_ZL`8o zyIMEx+m3<8%&kI!f;KuORY)MYjW+0>yMI zzZV+olG#lH$+*~2qD1fOBbHeLL(SE3=@hHww=b5w>J+)%)i%={6xmJ29Pl5Lj53H3 z4_;#70*G$iFVbNI%mXG-!sgqPC7Orfz@1Wu*mvT@7`JL}yOl57{YG*B-bC(W;Ca7gHx047n6<7Ly!bY&|K|};vFdO38x@vXJoe-To%i3lo^Hill%g7|Gry_j z;(R+)J?WSUJhX|mnC{Pft79Sv?$_s19!7eHIxl%lb6HK$SNzxkXax9118ib0O_g*A zc9)R9GFvXhV7vKZaoo3xve1g;-*5BL0;Ohix5WrWNe(lRtB^=?WB;8ZZ-_D&L7hA28@A)Lo-;87-UF|zFj8Qf zz9dIQ{&25u>HMtd`=2$!^N|9{*crdTDlSrcz3@^zT(0)SVyWd$;tl!@OV)o^!4vub z+ib}FYCXwmxY*|jvTHkvvAW>y7r$g7m#_R=S$+ffn!3OC)JgsX3q67?_>0n{;6ang ziQSR*6@9RZP0Q8BV-;~qy>b{9o}T{vT=U<;tqj7!j=n>CxI6GlCDi}a9;S|!8*3x% zdbVvQ@Ns+X{~c5YJ_LBuXgCS)5Gruptf3$Ju2YAmzDxdN%4|-zdh$28C{TP}l6y=z z?#)gzHLS&MR1J%qAmDcuDi3cArCaYeY(B5LJD+nCoJ9J&iNtUa$h~eWXKd;g-eKS$ zVTwF8R<|yzN!)w8)*EAfcQ$?Te@~n`$iJek`WeqV*enk#l-u>xqH4Rigz@&j(Nw_T zT-Tklx&g+L(pa+Gj^6<%ul|c7dL*A%OLcOO(->{n-}zz0sTn2zmJH=}@MA|%+PMFX zARq*nF6D6^KC39e*xDy53|jjr%Ljfx06Uc<&A&Etg(cxIYDy)sndEThJ<=U!h_iO*`Z2A=l+I)N5@1%L~xS8eC z1tvu}BS?5_QBr|ftF|1?kyfD}2HKO;{e?N9qb^+YPIO)_-o;P3+BP+lq1$RoECwLY zhdbxSe%7v(E}b~2KKbPB)rk$iQ}9-Jv|Qafs?u5HE|7}2fxNEL4tGcqK`y#zn^vUm z0!AYfn3#HvYh1UF{QOg!Ahy$a0YPd-&!#M0y6KR}&93isP}Pt?ztHVrXTy=>!Qw)i z)2!vas6kL15>km?RZSe(a3rv)Ko}uI{50aB;rc_;PqLQaKvMLLft) z#@(-B3#Ez{k2Mu&wp_PON9t$T@X_Uh$?!%T>FN87S5-HM(iNhf3m+p{bOmFh;^U0i z+qiUUsnE5h6?Z7}idfbJpRL#JEH4Fuk>>y&>I@su)wOkZ6O5G6D^2Xhqnxf0Efe$ATqdbm5M zn~Y?<5ZMs450x=+UijE>a}V6I&Xp}2N1F^ZcRfx&DxLOb*CX|uX3P?0r7e5HiKjLh zW}nHX@h-VLjIcdiZIV0uGzuaf)2ArESP5gDePBmbX%;JTf{1~xfNYhd*TK5&mC9q| z`?`7-#d>|X?+m`$c41cNxR)wHlp$T55rGYk2p%n4B^SE!`*UWk59V(1)8>c!8}o+s z#IU_ppbz4mF!2i>%K2cC)_F7YHnXO?IACM~TyHUw<#VyG3p7g&%+IkhY#Y`INYda{ zY9m?_tmqJ6>6oIoAJjf8rq7?ic+@I>W8UyYPAlV=Id$k;@*wrgQ^eo0xxz2XMm`WsMV$SY6*`TI@M-AM_V`-l; zjZ;uk6 zp(5l`-}N^Q5cE4;isH5BzbLT%Qf+EyB=&U6P=62=Ji$hY(xwXmcX@{x2l}%q*jFsf zKU+{}(#>N%RF(Rvyxr{N*IA?eAr6d=FpejW(GzHi&I|_; zIB(d@;LL?aaFBamW?unOfi9oqya{x)eF)pjBKu7zisKaqqI1uo136-CX#Na`5xk3E z9_S|b*Pa{gud;TzW0C4gu!%2UtwxFNa%*TAA#wt(@0`Co{TF$N#0<7Ag_Fs03z*wx zc0xaiBJ*O=jrG&e?3b@tZD-yEiqROpxxnge`(cW(x$QT6=3pd`HS=sz$`q-+7M^JF zaC4ZmsWam+b{@r6Ku7x6j!3vhx;l!Q{-FaU@Tez2jv-A_1di(lfQa zRxWTbO<>JB(6Jrqcl8&t)h)Y;^_h+;7t4d_lR1I3;EOzb6>k`d(*mB!j*F#dvNz^1 z9$h1)cJ0UuP%&P_e%U;inW{T9m3c9^SnqIj;53>^&ogVm^IKI8; zyz_=pzcQ)$o`jmHTXg4i`{BwP+`8?zvU_id5xjV=q1|`cAp@>}`d+>mZ!--TxM*gB13wZQ zBu@Pd^dxT0gb+Q?QC^b8s6YzWB%GyTR+7}D{S@pM=i6vE8$U>Iy8jvLTrS~*6bl^!&^qHbf#zXlB;)8f6{7m9P*h0b*+AvQ=L&T^a}sJY9q(T)n~ z;j&F{%~ZYJKOW6n&iml3*v7=vA|hX9=PR99Z)p&Q1kXeP&V((~=s|3#^`SH<9#)+C zT)bQ*CEvoIdZBp68UH}1D`3sQF7hPXBSGdzB?=qyhJRw85VznYv;UIRPHLY{-u+T^ z#pxkWgeX?fZPrjC80m%7ovc(xLjpn0ssncbUn|<)XgK%>K}Xtm7dt5_vH?e+N_=^i zQX$~TegS#525&1%GFo|wN_9Exb#F3}Q$v0AVqtLbun^a`@&3YS4sAmTE5FI-MAl34 zh8X^ClG-%4#yRckR1-DUnj}V=^wK4y(HAw{1aI&r^s06EHke{%;=n!3_a%94loBmQ zpRiKsHcC=LH(oty=bpqzAUNO|P=pngsK}nC_xG}2bhztoyowIx>(2f4+m`sVL{F00 zq5(!vMtqB6Y`HUYgJh1(X2l9q8mq@H*F2B(} zX)L1*)|UNNcF;y({r&aM8z`n46((cxI?n45VikEOsifGr4V+0fnJ3FFIWsF#o1Rh^ zb)EBcb3xI{>G1?H+@7-rgAj^#WvPA~?R}u{j0FilXGB3h|5*IJ9~0jFX>3${;`jLB zUiP#41gbHs?*XSfR^p=;RnaF4Jfp&qY;Um%GNo+sjBw%{bHPPwFQt#DtHrLW45l19<2a*ICaK`x2;tj%2!A%m+L|beo1DOw%5(LcP55W|ioq1= zAyhTy1v#2OaNv1=CArf*k2MbYD#N!DP{`qJsz=|Tqg=Qr@LOlJI}V&{A(fpLCL22I z1o<(}Qof6g?Fn)gV_IADRe#nH&dBgCgI*Znxvp=FiFOE;CN5Mh2j`^@LowHYE_D6m zg~qdUfyIZHVpb~9lhatXV-Ch26c`cC)2S*M;QD9y1(XQzq4^ezxFWO+&1y1Bwi&7x zuh*-0>JvJ~&t+o~lE*Y-H@q=lb+lB4rZIs&8xPqL*XqSn2>)Ctlnzdo<4gP6pwZsk((CTs6$D)8aqcoM&(I+n<4>PtuK0VtB8nl zXqNdRJCAxr8EsyOkRAd<*Mm}Bic^6$niP2dMDQQoC zqO%ti!+)GA?517!0x|51=6}pVf$eulh{Wyq4->8zGQ=2H1MP`Z%fi|$Cpj!S{?iH^ zS`vF_G#lnuqQ3oVQl*mHJ!7J65X+}S8iCQXs{BdrHU73HhEygFQhzK)TP!XgqyW}X zCkgFKk%d0IZjNRg@xldjZ|``0)SCTI#0k3{vH=K1;|cA2HDS-7-qOkbn$AW)-h$b< z_C?Y9Y3im7RDWlW>=;7rJ#^LAdhpcIemO(91avkS%9SLBTo&VE9Ts%Fv|+Si$(q@l zp}46hvOg7GD9YQjt&f|YbXQv+>qhsfh8v1MSQKCOQn-8DiIW8k=?%Xi2h~?}2~khp zg-1^G7v|C8ntjZZdUYkQ`7#z00X9WnrbO=`A{ETp5?(JI!Aug27BD6+n?0Q?k}h+k zo$UFuuK4{@OD&I~x|*r3)N>=8&gujWaRhTE7YJ9)$QWz(NqLbIwMrZ&k`YD^C=NQ1 zd=u$1T&8u9;n#}6NjYX$DfOJJMR8zEy;mk|g6Ukx^*%?^`xH_B22W{2<4T(ooJP>_XqQ7$56ZSFi4{#I!IlMV&-K_1UypBrG@mOQdcD(-mc`jWrLFFoI zV)f5eBA`cXalBNUbB?LXMnq9?kE@Z>$|9&meEYw3mG%zBQ!ntAz^ABy(u6arOPA6fnm|BSGlWZ=n;mmeUJ`=j~UK2cUH_X&OXSf!-9RHy6 zE17q#C`A7=uf+uLyxZ^B6Eu0=x8Btfq5>_(_V4WVpUs;8J?eFscj{r|nB0rxNAfQ$ zAW7v%Pssmw$r}mFZhTFy)t-nGW9)%KAb{QeeM;6YHjMXu1~Io)`~vOm*#5(~HGw(t3MaIRL zK6DajSx>fdqkt`Rp|A27L;#B`_M;jgE44T9Ua}PfNNsKDrn0SH<67*i0R`R#)1dAY z*B;`x@pD+#?}YX})xkmstq(01v+d!G)ufFVp*30U0BkA`unT2Fu`*Sx~KTBgN!GQi}e zKewh)PFm6bL`1@)oxd#s7dP|BRuQ~;$9|Lcu5Q7}3r4AtVp3_nUlU36+ey6w?;do&(+FK=iav_1KWA#H*3`Z$5iz$km!9+Fo`*& zh$kN?uYh#&2sNex^fg@&vs;PK{pBjEp~nFGcUY3XtEJCUw3JyV+fu~nY7H`c#*++W z`iTjZ@v2Ng=kgh=lCn}v#A3D2kqw6(sLXMyjL&@ldXzrtXXqE-4{yr4p7q(1Dg!{z zBMG<_(9vHAWJIPDV6z|}kgPNtFTD1>frESA{ip>9;BCtYb=<`)aeXo@YNL!%eLD7Pu4WuaF###pBa)biz}qw$ z=97~Hczq4KM6|Tcr zTfRJd#XpKjsB383xW^+%?WE@c#OU5hSv)!d01BjD0dZ{ec3R)9%*XEa3xFjsn5x4B zc$qeLU<2yY0~lhK4^U_RosIzBdf)_yj)U!g&4@`(WCLu6b7%eI+$fRRd#@q>O&I(W zq@NFN7yC9p*|m$lVO{Q44G#0`MGtspZnYJf+pC9qrse_$qkPG1u1AxS3A2qi(qyg88$m&NPuP851e-!%;xbrM zs))htAKK3TB3+2l<@wh}iS^S{0Qe?3w$BEjm@$gpE1&`TWC85E`(^ZUcf!)sRUjvG zttA_72yPeV+Rx&Ts#;`5TV(r}n(PY#tj*qm)O}lI z+&WZ^D+;ck83&%HzZT3e!*S0p<$6fwd^@|PRcvOm|Le-o4VkCEga{j+>t_wJ3gAqB}uGHkgz0M$VN{!y+Gf3e_fkV+E38J#E0ljslrQV*o9 z-3OdsoxVM_T;q5|!hsecqcTQc$~hg_n?-eJ08;%0o)VBuG@2>1WDBoQ&?Z;XN*<2t z15CITiNIak6HG1m6(EaL&OBIvZ!xipLf9$+77JPU8ynp%&-@O3Ha?mAVLQ(NY~Y#? zqD=t54y1+}9lV6+?w~-n@D`7o>lAbQTOixi_~Ob_`@@h^k`92w>m&E>QMm(%Dd?v9@u^Td#wVjHFuy_ zWvUh$$rfb4X3_fn9f)D;#>?zqhaI6C67frqy!z+wlEHW=YOCGidqKS$bS0vZ!T$Uw z1Dt~ZC!YQkJB&D_6Uc235_^W>#~f|Sh4c^~QZ8tY+1y(g!N3H!csBDFRy0&kQV(EP z)`G&76h1%W7!Fgq>0jYD#5S!oVfVwr({gk%A7#nyB0XaNij|N3^Rb1MbGP)rl7N|1 zr;|9w4eLsFemwcjS;TyC0(2b1B_-TbTAfY62|r}>i`kRx%eB5d?3k}$p_S=~TIoWZ zcGkV-Uqb#Ui`-xoGFn1(kT^XF--j%lP7kW}Kn0_?a|z!chUtcs3Xlv@|EC!~)o&38 zuy(OdDO9y(EQ`#IJ>W2>s`#?qvz;$m;Xb!Sti+)#gjCl04R?Um+W8e#Sw~-p(UW*r zT73OMJ?h;AmT_g{E(D|qsB9R7FJ=@2mb8>dM=mKQu~mYfB;Gy=PQnUA9(N46AC0G= zUg9h0g$y8IeP*EMchPnnL84R2cy~jq8stJr(}5*}CoP93k*`|CPM1*(;L$0Ib;;r~+Z=V;lpShQyTn%wvv@MqLAbP#PCmO1=+EAxI`F zWhfo3eG;e+&Qw})<%2oCv=Z6de8xi-SjTC{q=y)Xy2ohP2*cdc{W?Yg`0tp-_hsWp zPDc_cGaO*zI59y~8mO~fG*e;x8LJSftDHf1T4jG_d#V$fMMADyD_ZRm36EO+h*0E5 zSq;V_M?H^U6o>-z3Lcr%kcMm2_|vG05&N0UI((izwQwHimcheyk{)#~9=c0VOdFAI z&Ic+TtZfb{?i+w?Hu9NgmiK`Cz)^T2*&Pq!6b6(XdoFD11iC#Elh{N_@obcDhxGE+ zuU9^=35H>YMM&+*q&|`>KHH>Z0*<}Xf{EM7AN=rgrVW1?k4i~)f#}Eh_psQ`0Wv=z zP#O_sKOM$B8k=mbUL2ThGK-TF_$D?n&?V&NMa{xb7G&DzFNGw-7^#~80T92r@>cVz zkV}i~J9F{C_C|jszY2|k2%$`vRxULHN2`fIBcLt9A*Z6YpXLglq}WD&Wz47*iuKKq z6s-4H7!^B|=GY;{>{QAfUBoZK2}@8@aA_?TC6j>OIT>}BWwKFa|1L-6I3uQ@-R-eO zfE;0dk*q^I*ZUG1)#=~67)0$q?ZNLNZb8+c+N8@j>av{8SA{W4WFvJ)qa_KG!+SAJ z+JO#*bWKo#B<=6eZ4LI>VMJjxR~)wYXtb$?5AbS`usYbZp^R}~cqHZqb841~J@U9L z0}2v}86T6-WvNtH!cK0w+);mCcg$wmrrsm#8geh@Tz;whA(}2`?!7L4oT@S2EN?g= z5?Tlrfh)@%TU`UZkR?jzzQX{*Fefha;F^_~Me?Oh99Ut9`BgyA9B_t4OgU-E8EpV{ z`inPyL|tHnR4XvLAxYvypot@?8S73G&wkh|Qv1BYGRRH6%}gF{tv1JFGppr8+%C_= zZFzFS!KmFU;cQ&0UM6+ zqA=(9!Zx1+WZFO^_+Kvt*Ps|i|Ti8^Fhq;&9z&DirWjZ)We~x z57dkFgq*LWv>!D}b9>ftfTrd9@Fh!}2JP+csWlDrSq)}UhLN7NG?7f)O3IX)ODrRQ zA8C>R&H~4$*(AD?=7$Tad~Ah}TB4L8Db)5Re}NaO4MNedd;j)j5_{s9RI%`QA5)}k z%Ta<%usITcrisPN>M#}%8(rnYc!hOckP(hCdYqJR0{a&VyQu8?LMh&N#2ZL)er)0$ z^oBh!3hC7qEmB4cjC*s#vzetux(m)BZ$e<^mQUl5j0wwlS_ZL!JKX8pI)Q zNb|ikVeNGcElH||K;K}9%o!9*L7ey&$qR2b;?-p>^%tOpg9})*| z@-sFUF;V7}Qp1u|Lb!;&wZG0sAmp%}W?^2B&UsBA-P=VjG5K`hDMa`~MB(f~V7!UI zuTE5@RtrX!UjrKf1^2p`?IpOuq{lv}cRlTSz1bn0x$=1Aqc z(4Kv+A>g3dwAQChI-0(K||*rag&!=*oVUYS{bcLk`B7 za){$nv>ehfual;m1S3>nV?BON*j?x{xVPUsM#hC^I%80BvG{e#e7=N)zY!d4^ux8B zaC?2_H&(wlZqc1YF8sUn8D*fJ`TP z-<8*mcoMz_8$}wYNRh@d(sYjcY(u*Rb$ssh(}*9vw&26l1`Hjr4q7}xddfsnNkH_Q z-~+mdu9xB@huE|Xnk%Rjx=R5aZ28zwJUvETfL; zMF~R-e-fJVc_po>x^pOMA>Ez99m(>F)Hi^HO*{eKne!!S^E-I5kNV#L$%AbF7*H3a zj(SjrIs>m>Fc20@T=brWUHbCsLP1Dtehn}vcODlfGbro++ zbQrY*X>D9__1XbQO4^|hEalAR8bm=B4Z~F8A@*V2$~Ani zxTL%f(X0*cYCjf+c7<*e;=;}Yc_qztnXS90WA>ZrE1j2RBtv#G;2&WfN zA`t9252)Xi{V|rHe3&3d8Y`tQga51)H5P3@Y%Ht*-;goNHhs0ly2an%w!;>I*@wjz zFRQT(bkYC4Ft3HdX7#pK{mX|kh)Oowe)@;OV>^**>8t0=q1Gp*M+4%NjVPbE8C@tP z`zu*PWv3^&7xV;BA`%Ue*!PqlBDQ28H}nq>ar|=fr*XmYddBnRP~6S6s8kZ8DK=8) z?0m9KH3RgHWag_kwlyn-3Ja4sqmd)3y7rw14C>n~Lv|*TuF`H&jbX3W29EWywdrY! zMs9=cAU;q7b?C-?gG6Q;l_k5oQYcSCE%och`}V?_#4Pj+y`$Hx&fI5t){WuG-p`M; zy9bjm+9SNr*!$e~-{jXygYn`ld+*=rL!}*w#}q6Q=Hw7rOy`X?Bc?ug{9;s*UG^uA z{^H4=udJO;F9gnvf73tc5v;uxJv1hvDg8v^W+lM+j=Fqd=_5VvUQiFbbX+7&ahyq$ zJkvc3HUg^-!75~hfdP#qP==b?f#MQr{B3L^wr^cUME{y(HQcmNzyy;BaYJP4D^MI6 zr@8#D?&uIWItKhI6X}<+ya=S715^`_+u))c(6louQ(p)@7)t1sFFHt6Kk7mM{Sr=! zFYLsGB7*8R#4>%S>xxkCp^R0zM?sjP6}8Gv9c(v8!381u<{Leb^cImkHJ)eHdFibG zcLV*T)?wW%FCX>Z*qUp6T zB|EqFWzYM>s4BddI{X%yW=nJ_tkyo|s zA8fZjKrttGM=z&laWT#R=6nh1vLxrW?9m%nxQ;-zYSS2kFroz~p=5V76M`u%)NCgx zI!GlXaD3_-F^M1ICdl+|w^HFq2eew~3rpEQo&-rayqRaulBMF3^{4m6_7q`Mr$!OE z%{Ud3ex+@+^3hLk1g_!1keu0_T z;2-kXO#hgS=KvLxP%bvNU!Vjti;y<~2poh|XT6rkPYFW7$|e5vD-FU)1l0Q|HFY#n zRI2tYk~2)IztYgwP-XqsO9&MMOeD`8=gtAGX!6YDmt&&6jO{)0Sm7nyp-Dr)dp*vV zfSjH=yQ}6%4VyclT6ThJ>|J&JTLtP`V-5*FT!hcnd%U_1daC8oC3hpNG~QU$ZK(pG zYp-Go=|LuOQY(3H$;WYheVJCOJISG~u$f>T$>J`&juU)BOO4Qrr{-bHlAf61J?ICU z_s2u1ou)?#U)5zzLTC*gShtYTP^iTu^YLIXK_+&)L8+tuH%0UTY z!S3`Z1Iu-M&;WziwL;E74sZz3m=V)bDi#orOnT!@Scd5(a)x0hU$4;SYYZ1%5i85E z|FPjrR={_S!@Q%=GzPuQ{*A4-DgSeQ0g1*Cdn%9~*yaf>E#|w4ra&J5u0h`f^#FY< z#=!6-Xi zpO#vzlTDyy47B}(+N{=Hq%PV`<+shJ9vaQ4yTWCHHy0=%%BMC7iWN}PqtJ^;CVn44w{F5 zLD(`rw~O8LXUH4u16`x8?sj)y+i**<6j)wTh-XnaM{UMz!imRhVpt=0G;h5Mry?E4 zH4nE)Z>LXWQ{f_%=U{tEP095%ZIU{a7EvdDJ0q(oS}dfNggF)0S)nIVL(Jb~!VAX+ zGcHGXLt!8F0zQaeeu)mgV2}_c_JdQ=^N0_APSE&*;i5E}NEq8p1(Q_2Bv#ugp@&m! zj~_vo$FBwn$@Zp~_Dm_Im=_kALepCCY!^qRKCdV6z6*EPNv)M!1}=CSI?5c*^TMK) zuqa#!s^|+`c_)Nl-Ig@4^43t}LIV_%3kyaV>n71!hV=eryV|0>Q(Hr#vCA^Z_u5NT zLdeC46Ug0dvj31XFWZSHgd%Go$Ou6H5R%eCa7|M7!ZS&X<>85f0>Bt1MH^%*r#g;m(=Yzen(7D$`;i#Q zoQ$zv3~{-C)bv?kyio5Q)xIU5|}@;$vwa&3_TyWiAb zPe<_+_@|<3ad=NFCtQdV=8WbOMXm@!$fYUYX`KQazjH>@n_l@TRP)+k`0E>3+ zSlgs+mhc+TT#4QuN-BmKpHyxIdIw;r53Bu|Cq>Xt*lFx5mdZFxlrdn}zkB|D>c@!7 zW_n#);_xI&<&e%FI|mWy8h~{RvoI=Em{P3@0BN+{@uQz$V>t8uYwn^?Zw4sNbpK56 zw)8RpS=Tl4NKV|H?2(dn6;2s=d7J=V2ki$uPsab;8ClAp*}C`dY2{&tN|>^4fEfN! zZu`h@L<~?!NdE7WK=8a`6}AiC%*XO%7j1osCi9c@t!$f)pmQ@@qq&1XGr7H|^_K#q zM^FI;qLY|EGL|=-Q{Q5!Ub>0{V>>#ogwV|n1yB9-xL9Z=v;LosKUC~sv3c-Ojp!Aa z)TZVRxEbd4JO!P0Erx_IbPZZvb^p-5*jixtY0=62SA^pL`NAp(_+RvEe;T85e%w|k zDHbvA0>A%0`^Nz!hW!Ei5^z8Pjuaa}{QePb{i+;b5$BOYEoBfn870Pl&<09aasJ*@ z`O6IJf_l4~cgh>(I|I?t`M&|W&UnkkrUt@1Lnyc7)`%{~Yd}aY5AdkX>Hm9qF)Y5z zWj`C5rxrhGlDKxIUsvc0vx>Echh_jL9b`_IW79?#NdCz6JCAA)X6uL0>x1$wh?Tp7+1yjcg*q zX9GCCbMwA^T`8=h~9N0*-)$ zIMpPQpY~K|_K#08>nV0L#naGXfN!oV0Av$^u3p*kytz3hPUfaTJ%C|F+W_B;?SFfX z9sw^if+IhAZ*##DSXZ_pKz^9Q@32vbqMZ5aPY7y(dYkh#b^*BC!tc<00A>UjJxl?k zLjs{OB0FQbHt*lEP(vK2|4Spduc)h#L{|e?*JgdsgI^bfYD<$51~o?0*?n+ zH1rqam_t?UxFse40@uUsmjcgB*FM64!Job0vn|!OU?+(s79*a`;1Bq`3_Q*f%DiuO zDq1(nhgsUdH^l>AMBMW~b#xGsf?wRxg#fUVjyK1SG_& zQ2Q}J<3ID282G?fDo-Zk{#beXFySHB`z<%NZ_2S?fK+Eq~QLS*-5|wyXQ_VKblU;{UpFLCaR{7u_XRsVGNt` zQNEwooZVQ|_ znZ3|I1T14kcU+cZUA3KQy)MT|OP4Iv1wHN$_ALi##&db;tykAhQFM2#pQI}> z`FYC}G`{EDb$PH>E2tV+w%$Z`|025XczGbbDR%bTcgeXCjB(P&on!IrELV{A31De36unFrLk2IE5h zFiBN%SslH^S$+1^X@>`Lc-|58DzeHaw`B&cEq7R&BU$24c0wt;3$?4v&&PqtlSWvD zn5b(HCnmusBu3qMjlH+rL*-9&jJ$pBDpangLq2Mr9tT9#-1@hPCiZl{o zjn9f?JLWRisgB|20q%y+(>CuTZWLxeFs;R%&A!3Cn|ua+jcfRu8_$Oc`|wIiPl0;P zi};PkGx@>;`l0u$I%2vHuM#gU4JQ2n-}ZLHw1C<_iG>jdFmB_!%sGzyGnas4v9GXr zQ-4SPpDj`-1BzF9y=F5m{!b5~EDWa4EzWQ& Date: Sat, 11 Jan 2025 14:21:23 +0100 Subject: [PATCH 14/42] Fix ? --- app/modules/cmm/cruds_cmm.py | 5 ++--- app/modules/cmm/endpoints_cmm.py | 11 ++++++++++- tests/test_cmm.py | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 11c4a6483f..8ac29c5648 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -5,10 +5,9 @@ from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import joinedload, lazyload, selectinload -from sqlalchemy.sql.coercions import SelectStatementImpl +from sqlalchemy.orm import selectinload -from app.modules.cmm import models_cmm, schemas_cmm, types_cmm +from app.modules.cmm import models_cmm, types_cmm n_memes = 10 n_weeks = 7 diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index aa332d372d..1fb7897c4a 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -97,7 +97,16 @@ async def get_memes( case _: raise HTTPException(status_code=204, detail="Invalid sort method") - return await compute_full_meme_page(meme_page) + return [ + schemas_cmm.ShownMeme( + user=meme.user, + creation_time=meme.creation_time, + vote_score=meme.vote_score, + status=meme.status, + my_vote=meme.votes[0].positive if meme.votes else None, + ) + for meme in meme_page + ] @module.router.get( diff --git a/tests/test_cmm.py b/tests/test_cmm.py index caea06df19..de9c28e583 100644 --- a/tests/test_cmm.py +++ b/tests/test_cmm.py @@ -134,7 +134,7 @@ async def init_objects() -> None: def test_get_meme_page(client: TestClient) -> None: response = client.get( - "/cmm/memes/?sort_by=oldest&n_page=1", + "/cmm/memes/?sort_by=best&n_page=1", headers={"Authorization": f"Bearer {token_cmm_2}"}, ) print(response) From 7ef1e3587f9a0d6a9de0d766f3ed752905486485 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 14:30:40 +0100 Subject: [PATCH 15/42] Fixes --- app/modules/cmm/cruds_cmm.py | 21 +-------------------- app/modules/cmm/endpoints_cmm.py | 3 +-- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 8ac29c5648..bedf24b7fe 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -147,30 +147,11 @@ async def get_meme_by_id( selectinload(models_cmm.Meme.user), ) .execution_options(populate_existing=True) - .where(models_cmm.Meme.id == meme_id) + .where(models_cmm.Meme.id == meme_id), ) return result.unique().scalars().first() -async def get_my_votes_from_memes( - db: AsyncSession, - user_id: str, - meme_page, -) -> dict[UUID, bool]: - meme_ids = [meme.id for meme in meme_page] - print(meme_ids) - result = await db.execute( - select(models_cmm.Vote).where( - models_cmm.Vote.user_id == user_id, - models_cmm.Vote.meme_id.in_(meme_ids), - ), - ) - votes = result.scalars().all() - votes_map = {vote.meme_id: vote.positive for vote in votes} - print("votes_map", votes_map) - return votes_map - - def add_meme(db: AsyncSession, meme: models_cmm.Meme): db.add(meme) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 1fb7897c4a..d235bbaa8d 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -26,7 +26,7 @@ module = Module( root="cmm", tag="Centrale Mega Meme", - default_allowed_account_types=[AccountType.student], + default_allowed_account_types=[AccountType.student, AccountType.staff], ) @@ -146,7 +146,6 @@ async def get_meme_image_by_id( if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") - # TODO: Change default asset return get_file_from_data( default_asset="assets/images/default_meme.png", directory="memes", From 3f2600703279943b982c89d004cc66249bf142fb Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 20:01:27 +0100 Subject: [PATCH 16/42] Added id to shown meme --- app/modules/cmm/endpoints_cmm.py | 1 + app/modules/cmm/schemas_cmm.py | 1 + 2 files changed, 2 insertions(+) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index d235bbaa8d..1ebd3b48a5 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -99,6 +99,7 @@ async def get_memes( return [ schemas_cmm.ShownMeme( + id=str(meme.id), user=meme.user, creation_time=meme.creation_time, vote_score=meme.vote_score, diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 0ee35f9a45..37c2346650 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -31,6 +31,7 @@ class Meme(BaseModel): class ShownMeme(BaseModel): + id: str user: CoreUserSimple creation_time: datetime my_vote: bool | None From a259ad95ff20d83266c85b44592367e5ab5cf44d Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 21:14:29 +0100 Subject: [PATCH 17/42] Fixed vote score --- app/modules/cmm/cruds_cmm.py | 26 ++++++++++++++++++-- app/modules/cmm/endpoints_cmm.py | 41 ++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index bedf24b7fe..e693a967c1 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -11,7 +11,6 @@ n_memes = 10 n_weeks = 7 -# TODO: Update meme vote_score with votes async def get_memes_by_date( @@ -135,13 +134,14 @@ async def update_ban_status_of_memes_from_user( async def get_meme_by_id( db: AsyncSession, meme_id: uuid.UUID, + user_id: str, ) -> models_cmm.Meme | None: result = await db.execute( select(models_cmm.Meme) .options( selectinload( models_cmm.Meme.votes.and_( - models_cmm.Vote.user_id == models_cmm.Meme.user_id, + models_cmm.Vote.user_id == user_id, ), ).load_only(models_cmm.Vote.positive), selectinload(models_cmm.Meme.user), @@ -168,6 +168,28 @@ async def update_meme_ban_status( ) +async def update_meme_vote_score( + db: AsyncSession, + old_positive: bool | None, + new_positive: bool | None, + meme_id: UUID, +): + if old_positive == new_positive: + score_diff = 0 + elif old_positive is None: + score_diff = 1 if new_positive else -1 + elif not old_positive: + score_diff = 1 if new_positive is None else 2 + else: + score_diff = 0 if new_positive is None else -1 + + await db.execute( + update(models_cmm.Meme) + .where(models_cmm.Meme.id == meme_id) + .values({models_cmm.Meme.vote_score: models_cmm.Meme.vote_score + score_diff}), + ) + + async def delete_meme_by_id(db: AsyncSession, meme_id: UUID): await db.execute( delete(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 1ebd3b48a5..d972c99b3c 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -95,7 +95,7 @@ async def get_memes( user_id=user.id, ) case _: - raise HTTPException(status_code=204, detail="Invalid sort method") + raise HTTPException(status_code=404, detail="Invalid sort method") return [ schemas_cmm.ShownMeme( @@ -123,9 +123,9 @@ async def get_meme_by_id( """ Get a meme caracteristics using its id """ - shown_meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) + shown_meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if shown_meme is None: - raise HTTPException(status_code=204, detail="The meme does not exist") + raise HTTPException(status_code=404, detail="The meme does not exist") return shown_meme @@ -143,7 +143,7 @@ async def get_meme_image_by_id( """ Get a meme image using its id """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") @@ -167,7 +167,7 @@ async def delete_meme_by_id( Remove a meme from db Must be author of meme if meme is not banned or admin """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id) + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if not meme: raise HTTPException( status_code=404, @@ -257,7 +257,7 @@ async def get_vote( vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user_id) if vote is None: raise HTTPException( - status_code=204, + status_code=404, detail="The meme has no vote from this user", ) @@ -279,7 +279,7 @@ async def get_vote_by_id( """ vote = await cruds_cmm.get_vote_by_id(db=db, vote_id=vote_id) if vote is None: - raise HTTPException(status_code=204, detail="The vote does not exist") + raise HTTPException(status_code=404, detail="The vote does not exist") return vote @@ -298,6 +298,9 @@ async def add_vote( """ Add a new vote for the user to a meme from its id """ + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + if meme is None: + raise HTTPException(status_code=404, detail="The meme does not exist") try: vote_id = uuid.uuid4() vote = models_cmm.Vote( @@ -306,6 +309,12 @@ async def add_vote( user_id=user.id, positive=positive, ) + await cruds_cmm.update_meme_vote_score( + db=db, + meme_id=meme_id, + old_positive=meme.votes[0].positive if meme.votes else None, + new_positive=positive, + ) cruds_cmm.add_vote(db=db, vote=vote) await db.commit() except Exception: @@ -327,10 +336,19 @@ async def delete_vote( """ Remove the vote from the user if it exists """ + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + if meme is None: + raise HTTPException(status_code=404, detail="The meme does not exist") vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) if vote is None: raise HTTPException(status_code=404, detail="The vote does not exist") try: + await cruds_cmm.update_meme_vote_score( + db=db, + meme_id=meme_id, + old_positive=meme.votes[0].positive if meme.votes else None, + new_positive=vote.positive, + ) await cruds_cmm.delete_vote(db=db, vote_id=vote.id) await db.commit() except Exception: @@ -351,10 +369,19 @@ async def update_vote( """ Update a vote from the user if it exists even if vote is already at the right positivity """ + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + if meme is None: + raise HTTPException(status_code=404, detail="The meme does not exist") vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) if vote is None: raise HTTPException(status_code=404, detail="The vote does not exist") try: + await cruds_cmm.update_meme_vote_score( + db=db, + meme_id=meme_id, + old_positive=meme.votes[0].positive if meme.votes else None, + new_positive=vote.positive, + ) await cruds_cmm.update_vote(db=db, vote_id=vote.id, new_positive=positive) await db.commit() except Exception: From a4acd405ac6bc951ee5d236a7765adfc5cc913d4 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 21:30:37 +0100 Subject: [PATCH 18/42] Fixed vote schema --- app/modules/cmm/schemas_cmm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 37c2346650..ae89725604 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -14,7 +14,6 @@ class VoteBase(BaseModel): class Vote(VoteBase): user: CoreUserSimple - creation_time: datetime class VoteComplete(Vote): From dffc861f725642b4b730558b79f2e6d21d340b7d Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 21:33:14 +0100 Subject: [PATCH 19/42] Fix response type --- app/modules/cmm/endpoints_cmm.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index d972c99b3c..60f244755a 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,3 +1,4 @@ +from mmap import mmap import uuid from datetime import UTC, datetime @@ -321,7 +322,11 @@ async def add_vote( await db.rollback() raise else: - return vote + return schemas_cmm.Vote( + meme_id=str(vote.meme_id), + positive=vote.positive, + user=vote.user, + ) @module.router.delete( @@ -388,8 +393,11 @@ async def update_vote( await db.rollback() raise else: - vote.positive = positive - return vote + return schemas_cmm.Vote( + meme_id=str(vote.meme_id), + positive=positive, + user=vote.user, + ) @module.router.post( From e5f1800c8028a150ae851c22643eb67eef786874 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 21:40:08 +0100 Subject: [PATCH 20/42] Fixed score compute --- app/modules/cmm/cruds_cmm.py | 3 ++- app/modules/cmm/endpoints_cmm.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index e693a967c1..67eba1fc6f 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -181,7 +181,8 @@ async def update_meme_vote_score( elif not old_positive: score_diff = 1 if new_positive is None else 2 else: - score_diff = 0 if new_positive is None else -1 + # old_positve == True + score_diff = -1 if new_positive is None else -2 await db.execute( update(models_cmm.Meme) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 60f244755a..fa6321a8d2 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -352,7 +352,7 @@ async def delete_vote( db=db, meme_id=meme_id, old_positive=meme.votes[0].positive if meme.votes else None, - new_positive=vote.positive, + new_positive=None, ) await cruds_cmm.delete_vote(db=db, vote_id=vote.id) await db.commit() From d6b306ea40ed011dc8d97953c2b5d709a0a9e1cc Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 21:47:37 +0100 Subject: [PATCH 21/42] Block Foucauld's POSTs --- app/modules/cmm/endpoints_cmm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index fa6321a8d2..03037fe5e9 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -302,6 +302,10 @@ async def add_vote( meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") + vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) + if vote is not None: + raise HTTPException(status_code=404, detail="Vote already created") + try: vote_id = uuid.uuid4() vote = models_cmm.Vote( From e737794653fa4a34dfdb44bd09caf15461d7296a Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 22:06:16 +0100 Subject: [PATCH 22/42] Small fix --- app/modules/cmm/endpoints_cmm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 03037fe5e9..6e7b04f4ba 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -317,7 +317,7 @@ async def add_vote( await cruds_cmm.update_meme_vote_score( db=db, meme_id=meme_id, - old_positive=meme.votes[0].positive if meme.votes else None, + old_positive=None, new_positive=positive, ) cruds_cmm.add_vote(db=db, vote=vote) From d02fda458f7be023ca811d2bb7c0366da4eac788 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 22:12:37 +0100 Subject: [PATCH 23/42] Small fix --- app/modules/cmm/endpoints_cmm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 6e7b04f4ba..9d18960940 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -388,7 +388,7 @@ async def update_vote( await cruds_cmm.update_meme_vote_score( db=db, meme_id=meme_id, - old_positive=meme.votes[0].positive if meme.votes else None, + old_positive=meme.votes[0].positive, # should exist new_positive=vote.positive, ) await cruds_cmm.update_vote(db=db, vote_id=vote.id, new_positive=positive) @@ -400,7 +400,7 @@ async def update_vote( return schemas_cmm.Vote( meme_id=str(vote.meme_id), positive=positive, - user=vote.user, + user=user, ) From 17d74e1bb14202e400230b1ebb8f2253c9b22da8 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 22:16:48 +0100 Subject: [PATCH 24/42] Fix error de golmon --- app/modules/cmm/endpoints_cmm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 9d18960940..8e7ee63c86 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -389,7 +389,7 @@ async def update_vote( db=db, meme_id=meme_id, old_positive=meme.votes[0].positive, # should exist - new_positive=vote.positive, + new_positive=positive, ) await cruds_cmm.update_vote(db=db, vote_id=vote.id, new_positive=positive) await db.commit() From 28b1ecfca47f2b297950257bb283d735cf3baea8 Mon Sep 17 00:00:00 2001 From: Daihecyy Date: Sat, 11 Jan 2025 23:52:31 +0100 Subject: [PATCH 25/42] Working ban/unban --- app/modules/cmm/endpoints_cmm.py | 43 ++++++++++++++++---------------- tests/test_cmm.py | 42 +++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 8e7ee63c86..b3723d56b2 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,4 +1,3 @@ -from mmap import mmap import uuid from datetime import UTC, datetime @@ -31,13 +30,17 @@ ) -async def verify_ban_status( - db, - user, -): +async def is_allowed_meme_user( + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user()), +) -> models_core.CoreUser: + """ + Overloads the is_user() dependency injection to verify if the user is in the banned table + """ user_current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user.id) if user_current_ban is not None: raise HTTPException(status_code=403, detail="You are currently banned") + return user @module.router.get( @@ -49,7 +52,7 @@ async def get_memes( sort_by: str = "best", n_page: int = 1, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Get a page of memes according to the asked sort @@ -119,7 +122,7 @@ async def get_memes( async def get_meme_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Get a meme caracteristics using its id @@ -139,7 +142,7 @@ async def get_meme_by_id( async def get_meme_image_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Get a meme image using its id @@ -162,7 +165,7 @@ async def get_meme_image_by_id( async def delete_meme_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Remove a meme from db @@ -202,7 +205,7 @@ async def delete_meme_by_id( ) async def add_meme( db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), image: UploadFile = File(...), request_id: str = Depends(get_request_id), ): @@ -250,7 +253,7 @@ async def get_vote( meme_id: uuid.UUID, user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Get a meme caracteristics using its id @@ -273,7 +276,7 @@ async def get_vote( async def get_vote_by_id( vote_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Get a meme caracteristics using its id @@ -294,7 +297,7 @@ async def add_vote( meme_id: uuid.UUID, positive: bool, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Add a new vote for the user to a meme from its id @@ -340,7 +343,7 @@ async def add_vote( async def delete_vote( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Remove the vote from the user if it exists @@ -373,7 +376,7 @@ async def update_vote( meme_id: uuid.UUID, positive: bool, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Update a vote from the user if it exists even if vote is already at the right positivity @@ -407,12 +410,11 @@ async def update_vote( @module.router.post( "/cmm/users/{user_id}/ban/", status_code=201, - response_model=models_cmm.Ban, ) async def ban_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Ban a user and hide all of his memes @@ -446,8 +448,6 @@ async def ban_user( except Exception: await db.rollback() raise - else: - return ban @module.router.post( @@ -457,7 +457,7 @@ async def ban_user( async def unban_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Unban a user and unhide all of his memes @@ -498,10 +498,11 @@ async def unban_user( async def get_user_ban_history( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ Get the ban history of an user """ + # TODO: Return a schema ban_history = await cruds_cmm.get_user_ban_history(db=db, user_id=user_id) return ban_history diff --git a/tests/test_cmm.py b/tests/test_cmm.py index de9c28e583..a53f376dde 100644 --- a/tests/test_cmm.py +++ b/tests/test_cmm.py @@ -62,12 +62,12 @@ async def init_objects() -> None: global cmm_user_to_ban cmm_user_to_ban = await create_user_with_groups([]) global token_user_to_ban - token_user_to_ban = create_api_access_token(cmm_user_1) + token_user_to_ban = create_api_access_token(cmm_user_to_ban) global cmm_admin cmm_admin = await create_user_with_groups([GroupType.admin]) global token_admin - token_admin = create_api_access_token(cmm_user_1) + token_admin = create_api_access_token(cmm_admin) global memes_1 memes_1 = [ @@ -103,7 +103,6 @@ async def init_objects() -> None: votes_memes_1.append(vote2) await add_object_to_db(vote2) - return global memes_2 memes_2 = [ models_cmm.Meme( @@ -124,7 +123,7 @@ async def init_objects() -> None: status=MemeStatus.neutral, user_id=cmm_user_to_ban.id, creation_time=datetime.datetime(24, i, 23, tzinfo=datetime.UTC), - vote_score=i, + vote_score=200, ) for i in range(1, 13) ] @@ -140,4 +139,37 @@ def test_get_meme_page(client: TestClient) -> None: print(response) print(response.status_code) print(response.json()) - assert 2 == 1 + assert 1 == 1 + + +def test_banning_user(client: TestClient) -> None: + # TODO: Add test to prove that memes are hidden + response = client.get( + "/cmm/memes/?sort_by=best&n_page=1", + headers={"Authorization": f"Bearer {token_user_to_ban}"}, + ) + assert response.status_code == 200 + response = client.post( + f"/cmm/users/{cmm_user_to_ban.id}/ban", + headers={"Authorization": f"Bearer {token_admin}"}, + ) + assert response.status_code == 201 + response = client.get( + "/cmm/memes/?sort_by=best&n_page=1", + headers={"Authorization": f"Bearer {token_user_to_ban}"}, + ) + assert response.status_code == 403 + response = client.get( + "/cmm/memes/?sort_by=best&n_page=1", + headers={"Authorization": f"Bearer {token_cmm_1}"}, + ) + response = client.post( + f"/cmm/users/{cmm_user_to_ban.id}/unban", + headers={"Authorization": f"Bearer {token_admin}"}, + ) + assert response.status_code == 201 + response = client.get( + "/cmm/memes/?sort_by=best&n_page=1", + headers={"Authorization": f"Bearer {token_user_to_ban}"}, + ) + assert response.status_code == 200 From d15a104110a0d3dc7e27367a5bf31de1bf307ec2 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:45:24 +0100 Subject: [PATCH 26/42] fix : update --- app/core/groups/groups_type.py | 1 + app/modules/cmm/cruds_cmm.py | 10 +++++++ app/modules/cmm/endpoints_cmm.py | 45 +++++++++++++------------------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/core/groups/groups_type.py b/app/core/groups/groups_type.py index 60faf5bf07..4993a5238d 100644 --- a/app/core/groups/groups_type.py +++ b/app/core/groups/groups_type.py @@ -24,6 +24,7 @@ class GroupType(str, Enum): ph = "4ec5ae77-f955-4309-96a5-19cc3c8be71c" admin_cdr = "c1275229-46b2-4e53-a7c4-305513bb1a2a" eclair = "1f841bd9-00be-41a7-96e1-860a18a46105" + CMM = "3e5b04e5-5b19-4950-8e6a-143bdf559290" # Auth related groups diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 67eba1fc6f..f03e65576c 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload +from app.core import models_core from app.modules.cmm import models_cmm, types_cmm n_memes = 10 @@ -290,3 +291,12 @@ async def delete_ban(db: AsyncSession, ban_id: UUID): await db.execute( delete(models_cmm.Ban).where(models_cmm.Ban.id == ban_id), ) + + +async def get_banned_users(db: AsyncSession) -> Sequence[models_core.CoreUser]: + result = await db.execute( + select(models_core.CoreUser) + .join(models_cmm.Ban, models_core.CoreUser.id == models_cmm.Ban.user_id) + .order_by(models_cmm.Ban.creation_time) + ) + return result.scalars().all() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index b3723d56b2..c626487c3b 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -6,12 +6,13 @@ from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from app.core import models_core +from app.core import models_core, schemas_core from app.core.groups.groups_type import AccountType, GroupType from app.dependencies import ( get_db, get_request_id, is_user, + is_user_in, ) from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm from app.types.content_type import ContentType @@ -19,7 +20,6 @@ from app.utils.tools import ( delete_file_from_data, get_file_from_data, - is_user_member_of_an_allowed_group, save_file_as_data, ) @@ -165,7 +165,7 @@ async def get_meme_image_by_id( async def delete_meme_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_allowed_meme_user), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): """ Remove a meme from db @@ -177,17 +177,6 @@ async def delete_meme_by_id( status_code=404, detail="Invalid meme_id", ) - if not is_user_member_of_an_allowed_group(user, [GroupType.admin]): - if meme.user_id != user.id: - raise HTTPException( - status_code=403, - detail="You cannot remove a meme from another user", - ) - elif meme.status != types_cmm.MemeStatus.neutral: - raise HTTPException( - status_code=403, - detail="You cannot remove your meme if it is banned ", - ) try: await cruds_cmm.delete_meme_by_id(db=db, meme_id=meme_id) @@ -414,17 +403,12 @@ async def update_vote( async def ban_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_allowed_meme_user), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): """ Ban a user and hide all of his memes Must be admin """ - if not is_user_member_of_an_allowed_group(user, [GroupType.admin]): - raise HTTPException( - status_code=401, - detail="Cannot ban another user", - ) current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user_id) if current_ban is not None: @@ -457,17 +441,12 @@ async def ban_user( async def unban_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_allowed_meme_user), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): """ Unban a user and unhide all of his memes Must be admin """ - if not is_user_member_of_an_allowed_group(user, [GroupType.admin]): - raise HTTPException( - status_code=401, - detail="Cannot unban another user", - ) current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user_id) if current_ban is None: @@ -503,6 +482,18 @@ async def get_user_ban_history( """ Get the ban history of an user """ - # TODO: Return a schema ban_history = await cruds_cmm.get_user_ban_history(db=db, user_id=user_id) return ban_history + + +@module.router.get( + "/cmm/users/banned/", + status_code=200, + response_model=list[schemas_core.CoreUserSimple], +) +async def get_banned_users( + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_allowed_meme_user), +): + banned_users = await cruds_cmm.get_banned_users(db=db) + return banned_users From bd6979d658b9be04f2fe2171c8bca2a9ff7ed190 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:23:17 +0100 Subject: [PATCH 27/42] feat : update ban --- app/modules/cmm/endpoints_cmm.py | 100 ++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index c626487c3b..9227d5035e 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -7,11 +7,11 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core import models_core, schemas_core -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import GroupType from app.dependencies import ( get_db, get_request_id, - is_user, + is_user_a_member, is_user_in, ) from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm @@ -26,13 +26,12 @@ module = Module( root="cmm", tag="Centrale Mega Meme", - default_allowed_account_types=[AccountType.student, AccountType.staff], ) async def is_allowed_meme_user( db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user()), + user: models_core.CoreUser = Depends(is_user_a_member), ) -> models_core.CoreUser: """ Overloads the is_user() dependency injection to verify if the user is in the banned table @@ -52,7 +51,7 @@ async def get_memes( sort_by: str = "best", n_page: int = 1, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_allowed_meme_user), + user: models_core.CoreUser = Depends(is_user_a_member), ): """ Get a page of memes according to the asked sort @@ -115,47 +114,85 @@ async def get_memes( @module.router.get( - "/cmm/memes/{meme_id}/", + "/cmm/memes/{meme_id}/img/", status_code=200, - response_model=schemas_cmm.ShownMeme, + response_class=FileResponse, ) -async def get_meme_by_id( +async def get_meme_image_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_allowed_meme_user), ): """ - Get a meme caracteristics using its id + Get a meme image using its id """ - shown_meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) - if shown_meme is None: + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") - return shown_meme + return get_file_from_data( + default_asset="assets/images/default_meme.png", + directory="memes", + filename=str(meme_id), + ) -@module.router.get( - "/cmm/memes/{meme_id}/img/", - status_code=200, - response_class=FileResponse, +@module.router.post( + "/cmm/memes/{meme_id}/hide/", + status_code=201, ) -async def get_meme_image_by_id( +async def hide_meme_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_allowed_meme_user), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): """ - Get a meme image using its id + Hide a meme from db + Must be admin """ meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") - return get_file_from_data( - default_asset="assets/images/default_meme.png", - directory="memes", - filename=str(meme_id), - ) + try: + await cruds_cmm.update_meme_ban_status( + db=db, + ban_status=types_cmm.MemeStatus.banned, + meme_id=meme_id, + ) + await db.commit() + except Exception: + await db.rollback() + raise + + +@module.router.post( + "/cmm/memes/{meme_id}/show/", + status_code=201, +) +async def show_meme_by_id( + meme_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), +): + """ + Show a meme from db + Must be admin + """ + meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + if meme is None: + raise HTTPException(status_code=404, detail="The meme does not exist") + + try: + await cruds_cmm.update_meme_ban_status( + db=db, + ban_status=types_cmm.MemeStatus.neutral, + meme_id=meme_id, + ) + await db.commit() + except Exception: + await db.rollback() + raise @module.router.delete( @@ -165,11 +202,11 @@ async def get_meme_image_by_id( async def delete_meme_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_a_member), ): """ Remove a meme from db - Must be author of meme if meme is not banned or admin + Must be author of meme if meme is not banned """ meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if not meme: @@ -177,7 +214,16 @@ async def delete_meme_by_id( status_code=404, detail="Invalid meme_id", ) - + if meme.status == types_cmm.MemeStatus.banned: + raise HTTPException( + status_code=403, + detail="You can't delete a banned meme", + ) + if meme.user_id != user.id: + raise HTTPException( + status_code=403, + detail="You are not the author of this meme", + ) try: await cruds_cmm.delete_meme_by_id(db=db, meme_id=meme_id) await delete_file_from_data(directory="meme", filename=str(meme_id)) From 1cac048d082fcb084b22d84513b437efe309869b Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:01:59 +0100 Subject: [PATCH 28/42] Working hidden memes --- app/modules/cmm/cruds_cmm.py | 13 ++++++++++++- app/modules/cmm/endpoints_cmm.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index f03e65576c..89b605d2a7 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -297,6 +297,17 @@ async def get_banned_users(db: AsyncSession) -> Sequence[models_core.CoreUser]: result = await db.execute( select(models_core.CoreUser) .join(models_cmm.Ban, models_core.CoreUser.id == models_cmm.Ban.user_id) - .order_by(models_cmm.Ban.creation_time) + .order_by(models_cmm.Ban.creation_time), + ) + return result.scalars().all() + + +async def get_hidden_memes(db: AsyncSession) -> Sequence[models_cmm.Meme]: + result = await db.execute( + select(models_cmm.Meme) + .where( + models_cmm.Meme.status == types_cmm.MemeStatus.banned, + ) + .options(selectinload(models_cmm.Meme.user)), ) return result.scalars().all() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 9227d5035e..b0c95d8b60 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,3 +1,4 @@ +import logging import uuid from datetime import UTC, datetime @@ -23,6 +24,8 @@ save_file_as_data, ) +hyperion_error_logger = logging.getLogger("hyperion.error") + module = Module( root="cmm", tag="Centrale Mega Meme", @@ -523,7 +526,7 @@ async def unban_user( async def get_user_ban_history( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_allowed_meme_user), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): """ Get the ban history of an user @@ -539,7 +542,31 @@ async def get_user_ban_history( ) async def get_banned_users( db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_allowed_meme_user), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): banned_users = await cruds_cmm.get_banned_users(db=db) + # hyperion_error_logger.error(banned_users) return banned_users + + +@module.router.get( + "/cmm/memes/hidden/", + status_code=200, + response_model=list[schemas_cmm.ShownMeme], +) +async def get_hidden_memes( + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), +): + hidden_memes = await cruds_cmm.get_hidden_memes(db=db) + return [ + schemas_cmm.ShownMeme( + id=str(meme.id), + user=meme.user, + creation_time=meme.creation_time, + vote_score=meme.vote_score, + status=meme.status, + my_vote=meme.votes[0].positive if meme.votes else None, + ) + for meme in hidden_memes + ] From d8a94068d17f02c6e916f2144bbb1e2a61b3685f Mon Sep 17 00:00:00 2001 From: BLEMENT33 Date: Mon, 10 Feb 2025 20:32:42 +0100 Subject: [PATCH 29/42] feat : leaderbord --- app/modules/cmm/cruds_cmm.py | 49 ++++++++++++++ app/modules/cmm/endpoints_cmm.py | 106 +++++++++++++++++++++++++++++++ app/modules/cmm/schemas_cmm.py | 4 ++ app/modules/cmm/types_cmm.py | 7 ++ 4 files changed, 166 insertions(+) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 89b605d2a7..c689443f7e 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -1,3 +1,4 @@ +import logging import uuid from collections.abc import Sequence from datetime import UTC, datetime, timedelta @@ -41,6 +42,27 @@ async def get_memes_by_date( meme_page = result.scalars().all() return meme_page +async def get_my_memes( + db: AsyncSession, + n_page: int, + user_id: str, +) -> Sequence[models_cmm.Meme]: + result = await db.execute( + select(models_cmm.Meme) + .options( + selectinload( + models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), + ).load_only(models_cmm.Vote.positive), + selectinload(models_cmm.Meme.user), + ) + .execution_options(populate_existing=True) + .where(models_cmm.Meme.user_id == user_id) + .order_by(models_cmm.Meme.creation_time.desc()) + .limit(n_memes) + .offset((n_page - 1) * n_memes), + ) + meme_page = result.scalars().all() + return meme_page async def get_memes_by_votes( db: AsyncSession, @@ -311,3 +333,30 @@ async def get_hidden_memes(db: AsyncSession) -> Sequence[models_cmm.Meme]: .options(selectinload(models_cmm.Meme.user)), ) return result.scalars().all() + + +async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: + hyperion_error_logger = logging.getLogger("hyperion_error") + hyperion_error_logger.error(n_jours) + + if n_jours == -1: + result = await db.execute( + select(models_cmm.Vote) + .options( + selectinload(models_cmm.Vote.user) + ) + ) + + else: + threshold_date = datetime.now(tz=UTC) - timedelta(days=n_jours) + + result = await db.execute( + select(models_cmm.Vote) + + .join(models_cmm.Meme) + .where(models_cmm.Meme.creation_time >= threshold_date) + .options( + selectinload(models_cmm.Vote.user) + ) + ) + return result.scalars().all() \ No newline at end of file diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index b0c95d8b60..62629d944b 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -116,6 +116,41 @@ async def get_memes( ] +@module.router.get( + "/cmm/memes/me", + response_model=list[schemas_cmm.ShownMeme], + status_code=200, +) +async def get_memes( + n_page: int = 1, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_a_member), +): + if n_page < 1: + raise HTTPException( + status_code=404, + detail="Invalid page number", + ) + + meme_page = await cruds_cmm.get_my_memes( + db=db, + n_page=n_page, + user_id=user.id, + ) + + return [ + schemas_cmm.ShownMeme( + id=str(meme.id), + user=meme.user, + creation_time=meme.creation_time, + vote_score=meme.vote_score, + status=meme.status, + my_vote=meme.votes[0].positive if meme.votes else None, + ) + for meme in meme_page + ] + + @module.router.get( "/cmm/memes/{meme_id}/img/", status_code=200, @@ -570,3 +605,74 @@ async def get_hidden_memes( ) for meme in hidden_memes ] + + +@module.router.get( + "/cmm/leaderbord/", + status_code=200, + response_model=list[schemas_cmm.Score], +) +async def get_leaderbord( + period: types_cmm.PeriodLeaderboard, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_a_member), +): + match period: + case types_cmm.PeriodLeaderboard.week: + n_jours = 7 + case types_cmm.PeriodLeaderboard.month: + n_jours = 30 + case types_cmm.PeriodLeaderboard.year: + n_jours = 365 + case types_cmm.PeriodLeaderboard.always: + n_jours = -1 + case _: + raise HTTPException(status_code=404, detail="Invalid period") + + votes = await cruds_cmm.get_votes(db=db, n_jours=n_jours) + + d = {} + + for v in votes: + if v.user_id in d: + d[v.user_id] += 1 if v.positive else -1 + else: + d[v.user_id] = 1 if v.positive else -1 + + result = [schemas_cmm.Score(user_id = k, score = d[k]) for k in d] + + return sorted(result,key = lambda s: s.score, reverse = True) + + +@module.router.get( + "/cmm/all_votes/", + status_code=200, + response_model=list[schemas_cmm.Vote], +) +async def get_all_votes( + period: types_cmm.PeriodLeaderboard, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_a_member), +): + match period: + case types_cmm.PeriodLeaderboard.week: + n_jours = 7 + case types_cmm.PeriodLeaderboard.month: + n_jours = 30 + case types_cmm.PeriodLeaderboard.year: + n_jours = 365 + case types_cmm.PeriodLeaderboard.always: + n_jours = -1 + case _: + raise HTTPException(status_code=404, detail="Invalid period") + + votes = await cruds_cmm.get_votes(db=db, n_jours=n_jours) + + return [ + schemas_cmm.Vote( + meme_id = str(vote.meme_id), + positive = vote.positive, + user = vote.user, + ) + for vote in votes + ] diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index ae89725604..58d9da6dec 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -43,3 +43,7 @@ class Ban(BaseModel): end_time: datetime | None user: CoreUserSimple admin: CoreUserSimple + +class Score(BaseModel): + user_id: str + score: int \ No newline at end of file diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index 539dba9ff0..90d79dd665 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -12,3 +12,10 @@ class MemeSort(str, Enum): trending = "trending" newest = "newest" oldest = "oldest" + + +class PeriodLeaderboard(str, Enum): + week = "week" + month = "month" + year = "year" + always = "always" \ No newline at end of file From 4c49701442d8bebaef3dba6f3ca18f3fc7fd6a52 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 10 Feb 2025 21:16:31 +0100 Subject: [PATCH 30/42] Merge feat --- app/modules/cmm/cruds_cmm.py | 27 +++++++++++++++++++++------ app/modules/cmm/endpoints_cmm.py | 17 ++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index c689443f7e..3702335232 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -1,4 +1,3 @@ -import logging import uuid from collections.abc import Sequence from datetime import UTC, datetime, timedelta @@ -324,13 +323,29 @@ async def get_banned_users(db: AsyncSession) -> Sequence[models_core.CoreUser]: return result.scalars().all() -async def get_hidden_memes(db: AsyncSession) -> Sequence[models_cmm.Meme]: +async def get_hidden_memes( + db: AsyncSession, + n_page: int, + descending: bool, + user_id: str, +) -> Sequence[models_cmm.Meme]: result = await db.execute( select(models_cmm.Meme) - .where( - models_cmm.Meme.status == types_cmm.MemeStatus.banned, + .options( + selectinload( + models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), + ).load_only(models_cmm.Vote.positive), + selectinload(models_cmm.Meme.user), + ) + .execution_options(populate_existing=True) + .where(models_cmm.Meme.status == types_cmm.MemeStatus.banned) + .order_by( + models_cmm.Meme.creation_time.desc() + if descending + else models_cmm.Meme.creation_time, ) - .options(selectinload(models_cmm.Meme.user)), + .limit(n_memes) + .offset((n_page - 1) * n_memes), ) return result.scalars().all() @@ -359,4 +374,4 @@ async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: selectinload(models_cmm.Vote.user) ) ) - return result.scalars().all() \ No newline at end of file + return result.scalars().all() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 62629d944b..afc3e49576 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,4 +1,3 @@ -import logging import uuid from datetime import UTC, datetime @@ -24,8 +23,6 @@ save_file_as_data, ) -hyperion_error_logger = logging.getLogger("hyperion.error") - module = Module( root="cmm", tag="Centrale Mega Meme", @@ -580,7 +577,6 @@ async def get_banned_users( user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): banned_users = await cruds_cmm.get_banned_users(db=db) - # hyperion_error_logger.error(banned_users) return banned_users @@ -591,9 +587,20 @@ async def get_banned_users( ) async def get_hidden_memes( db: AsyncSession = Depends(get_db), + n_page: int = 1, user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), ): - hidden_memes = await cruds_cmm.get_hidden_memes(db=db) + if n_page < 1: + raise HTTPException( + status_code=404, + detail="Invalid page number", + ) + hidden_memes = await cruds_cmm.get_hidden_memes( + db=db, + descending=True, + n_page=n_page, + user_id=user.id, + ) return [ schemas_cmm.ShownMeme( id=str(meme.id), From cb4e9365ff6d74e614057788570f3d9acac23a47 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:40:19 +0100 Subject: [PATCH 31/42] fix : get_all_meme --- app/modules/cmm/cruds_cmm.py | 16 +++++----------- app/modules/cmm/endpoints_cmm.py | 17 +++++++---------- app/modules/cmm/schemas_cmm.py | 3 ++- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 3702335232..0982fa4219 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -41,6 +41,7 @@ async def get_memes_by_date( meme_page = result.scalars().all() return meme_page + async def get_my_memes( db: AsyncSession, n_page: int, @@ -63,6 +64,7 @@ async def get_my_memes( meme_page = result.scalars().all() return meme_page + async def get_memes_by_votes( db: AsyncSession, n_page: int, @@ -351,15 +353,9 @@ async def get_hidden_memes( async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: - hyperion_error_logger = logging.getLogger("hyperion_error") - hyperion_error_logger.error(n_jours) - if n_jours == -1: result = await db.execute( - select(models_cmm.Vote) - .options( - selectinload(models_cmm.Vote.user) - ) + select(models_cmm.Vote).options(selectinload(models_cmm.Vote.user)) ) else: @@ -367,11 +363,9 @@ async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: result = await db.execute( select(models_cmm.Vote) - .join(models_cmm.Meme) .where(models_cmm.Meme.creation_time >= threshold_date) - .options( - selectinload(models_cmm.Vote.user) - ) + .group_by(models_cmm.Meme.id) + .options(selectinload(models_cmm.Vote.user)), ) return result.scalars().all() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index afc3e49576..bafa830c90 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -118,7 +118,7 @@ async def get_memes( response_model=list[schemas_cmm.ShownMeme], status_code=200, ) -async def get_memes( +async def get_my_memes( n_page: int = 1, db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_user_a_member), @@ -637,18 +637,15 @@ async def get_leaderbord( raise HTTPException(status_code=404, detail="Invalid period") votes = await cruds_cmm.get_votes(db=db, n_jours=n_jours) - - d = {} - + d: dict = {} for v in votes: if v.user_id in d: d[v.user_id] += 1 if v.positive else -1 else: d[v.user_id] = 1 if v.positive else -1 - - result = [schemas_cmm.Score(user_id = k, score = d[k]) for k in d] + result = [schemas_cmm.Score(user_id=k, score=d[k]) for k in d] - return sorted(result,key = lambda s: s.score, reverse = True) + return sorted(result, key=lambda s: s.score, reverse=True) @module.router.get( @@ -677,9 +674,9 @@ async def get_all_votes( return [ schemas_cmm.Vote( - meme_id = str(vote.meme_id), - positive = vote.positive, - user = vote.user, + meme_id=str(vote.meme_id), + positive=vote.positive, + user=vote.user, ) for vote in votes ] diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 58d9da6dec..28162e92bf 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -44,6 +44,7 @@ class Ban(BaseModel): user: CoreUserSimple admin: CoreUserSimple + class Score(BaseModel): user_id: str - score: int \ No newline at end of file + score: int From facb7395e8dc9e727d2e96ef81838b48e61bcbdb Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:45:13 +0100 Subject: [PATCH 32/42] Format --- app/modules/cmm/cruds_cmm.py | 2 +- app/modules/cmm/types_cmm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 0982fa4219..dd4993a655 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -355,7 +355,7 @@ async def get_hidden_memes( async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: if n_jours == -1: result = await db.execute( - select(models_cmm.Vote).options(selectinload(models_cmm.Vote.user)) + select(models_cmm.Vote).options(selectinload(models_cmm.Vote.user)), ) else: diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index 90d79dd665..d0d61a2d1d 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -18,4 +18,4 @@ class PeriodLeaderboard(str, Enum): week = "week" month = "month" year = "year" - always = "always" \ No newline at end of file + always = "always" From 95b00b8fea0260c9484f7feef9e949e030c6e8fe Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:28:20 +0100 Subject: [PATCH 33/42] fix : score update --- app/modules/cmm/endpoints_cmm.py | 24 ++++++++++++++++-------- app/modules/cmm/schemas_cmm.py | 3 ++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index bafa830c90..5102c530e7 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -615,7 +615,7 @@ async def get_hidden_memes( @module.router.get( - "/cmm/leaderbord/", + "/cmm/leaderboard/", status_code=200, response_model=list[schemas_cmm.Score], ) @@ -637,15 +637,23 @@ async def get_leaderbord( raise HTTPException(status_code=404, detail="Invalid period") votes = await cruds_cmm.get_votes(db=db, n_jours=n_jours) - d: dict = {} + d: dict[str, int] = {} + users: dict[str, models_core.CoreUser] = {} + for v in votes: - if v.user_id in d: - d[v.user_id] += 1 if v.positive else -1 - else: - d[v.user_id] = 1 if v.positive else -1 - result = [schemas_cmm.Score(user_id=k, score=d[k]) for k in d] + d[v.user_id] = d.get(v.user_id, 0) + (1 if v.positive else -1) + users[v.user_id] = v.user + + sorted_scores = sorted(d.items(), key=lambda item: item[1], reverse=True) - return sorted(result, key=lambda s: s.score, reverse=True) + return [ + schemas_cmm.Score( + user=users[user_id], + score=score, + position=i + 1, + ) + for i, (user_id, score) in enumerate(sorted_scores) + ] @module.router.get( diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 28162e92bf..064321a81a 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -46,5 +46,6 @@ class Ban(BaseModel): class Score(BaseModel): - user_id: str + user: CoreUserSimple score: int + position: int From df1bc22e61a9ce2da101a35bad9d63cbbc33ccf3 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:32:24 +0100 Subject: [PATCH 34/42] Fix : leaderboard endpoint --- app/modules/cmm/cruds_cmm.py | 33 ++++++-- app/modules/cmm/endpoints_cmm.py | 130 ++++++++++++++++++++----------- app/modules/cmm/schemas_cmm.py | 15 +++- app/modules/cmm/types_cmm.py | 6 ++ 4 files changed, 129 insertions(+), 55 deletions(-) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index dd4993a655..02ef015ee6 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -352,20 +352,39 @@ async def get_hidden_memes( return result.scalars().all() -async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: +# async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: +# if n_jours == -1: +# result = await db.execute( +# select(models_cmm.Vote).options(selectinload(models_cmm.Vote.user)), +# ) + +# else: +# threshold_date = datetime.now(tz=UTC) - timedelta(days=n_jours) + +# result = await db.execute( +# select(models_cmm.Vote) +# .join(models_cmm.Meme) +# .where(models_cmm.Meme.creation_time >= threshold_date) +# .group_by(models_cmm.Meme.id) +# .options(selectinload(models_cmm.Vote.user)), +# ) +# return result.scalars().all() + + +async def get_all_memes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Meme]: if n_jours == -1: result = await db.execute( - select(models_cmm.Vote).options(selectinload(models_cmm.Vote.user)), + select(models_cmm.Meme).options(selectinload(models_cmm.Meme.user)), ) else: threshold_date = datetime.now(tz=UTC) - timedelta(days=n_jours) result = await db.execute( - select(models_cmm.Vote) - .join(models_cmm.Meme) - .where(models_cmm.Meme.creation_time >= threshold_date) - .group_by(models_cmm.Meme.id) - .options(selectinload(models_cmm.Vote.user)), + select(models_cmm.Meme) + .where( + models_cmm.Meme.creation_time >= threshold_date, + ) + .options(selectinload(models_cmm.Meme.user)), ) return result.scalars().all() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 5102c530e7..504c601a34 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -1,5 +1,6 @@ import uuid from datetime import UTC, datetime +from typing import TYPE_CHECKING from fastapi import Depends, File, HTTPException from fastapi.datastructures import UploadFile @@ -16,6 +17,7 @@ ) from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm from app.types.content_type import ContentType +from app.types.floors_type import FloorsType from app.types.module import Module from app.utils.tools import ( delete_file_from_data, @@ -617,10 +619,13 @@ async def get_hidden_memes( @module.router.get( "/cmm/leaderboard/", status_code=200, - response_model=list[schemas_cmm.Score], + response_model=list[schemas_cmm.UserScore] + | list[schemas_cmm.FloorScore] + | list[schemas_cmm.PromoScore], ) -async def get_leaderbord( +async def get_user_leaderbord( period: types_cmm.PeriodLeaderboard, + entity: types_cmm.EntityLeaderboard, db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_user_a_member), ): @@ -636,55 +641,86 @@ async def get_leaderbord( case _: raise HTTPException(status_code=404, detail="Invalid period") - votes = await cruds_cmm.get_votes(db=db, n_jours=n_jours) - d: dict[str, int] = {} - users: dict[str, models_core.CoreUser] = {} + memes = await cruds_cmm.get_all_memes(db=db, n_jours=n_jours) - for v in votes: - d[v.user_id] = d.get(v.user_id, 0) + (1 if v.positive else -1) - users[v.user_id] = v.user + match entity: + case types_cmm.EntityLeaderboard.promo: + promo_scores: dict[int, int] = {} - sorted_scores = sorted(d.items(), key=lambda item: item[1], reverse=True) + for meme in memes: + meme_author = meme.user + meme_author_promo = meme_author.promo + if meme_author_promo: + if meme_author_promo not in promo_scores: + promo_scores[meme_author_promo] = 0 - return [ - schemas_cmm.Score( - user=users[user_id], - score=score, - position=i + 1, - ) - for i, (user_id, score) in enumerate(sorted_scores) - ] + promo_scores[meme_author_promo] += meme.vote_score + sorted_promo_scores = sorted( + promo_scores.items(), + key=lambda item: item[1], + reverse=True, + ) -@module.router.get( - "/cmm/all_votes/", - status_code=200, - response_model=list[schemas_cmm.Vote], -) -async def get_all_votes( - period: types_cmm.PeriodLeaderboard, - db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_a_member), -): - match period: - case types_cmm.PeriodLeaderboard.week: - n_jours = 7 - case types_cmm.PeriodLeaderboard.month: - n_jours = 30 - case types_cmm.PeriodLeaderboard.year: - n_jours = 365 - case types_cmm.PeriodLeaderboard.always: - n_jours = -1 - case _: - raise HTTPException(status_code=404, detail="Invalid period") + return [ + { + "promo": promo, + "score": total_score, + "position": i + 1, + } + for i, (promo, total_score) in enumerate(sorted_promo_scores) + ] + + case types_cmm.EntityLeaderboard.floor: + floor_scores: dict[FloorsType, int] = {} + + for meme in memes: + meme_author = meme.user + meme_author_floor = meme_author.floor + if meme_author_floor: + if meme_author_floor not in floor_scores: + floor_scores[meme_author_floor] = 0 + + floor_scores[meme_author_floor] += meme.vote_score + + sorted_floor_scores = sorted( + floor_scores.items(), + key=lambda item: item[1], + reverse=True, + ) - votes = await cruds_cmm.get_votes(db=db, n_jours=n_jours) + return [ + { + "floor": floor, + "score": total_score, + "position": i + 1, + } + for i, (floor, total_score) in enumerate(sorted_floor_scores) + ] + + case types_cmm.EntityLeaderboard.user: + user_scores: dict[str, int] = {} + users: dict[str, models_core.CoreUser] = {} + + for meme in memes: + meme_author = meme.user + meme_author_id = meme_author.id + + user_scores[meme_author_id] = ( + user_scores.get(meme_author_id, 0) + meme.vote_score + ) + users[meme_author_id] = meme_author + + sorted_user_scores = sorted( + user_scores.items(), + key=lambda item: item[1], + reverse=True, + ) - return [ - schemas_cmm.Vote( - meme_id=str(vote.meme_id), - positive=vote.positive, - user=vote.user, - ) - for vote in votes - ] + return [ + {"user": users[user_id], "score": score, "position": i + 1} + for i, (user_id, score) in enumerate(sorted_user_scores) + ] + + case _: + raise HTTPException(status_code=404, detail="Invalid period") diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 064321a81a..0ba16b377f 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -5,6 +5,7 @@ from app.core.schemas_core import CoreUserSimple from app.modules.cmm.types_cmm import MemeStatus +from app.types.floors_type import FloorsType class VoteBase(BaseModel): @@ -45,7 +46,19 @@ class Ban(BaseModel): admin: CoreUserSimple -class Score(BaseModel): +class UserScore(BaseModel): user: CoreUserSimple score: int position: int + + +class PromoScore(BaseModel): + promo: int + score: int + position: int + + +class FloorScore(BaseModel): + floor: FloorsType + score: int + position: int diff --git a/app/modules/cmm/types_cmm.py b/app/modules/cmm/types_cmm.py index d0d61a2d1d..66a0117420 100644 --- a/app/modules/cmm/types_cmm.py +++ b/app/modules/cmm/types_cmm.py @@ -19,3 +19,9 @@ class PeriodLeaderboard(str, Enum): month = "month" year = "year" always = "always" + + +class EntityLeaderboard(str, Enum): + promo = "promo" + floor = "floor" + user = "user" From 65d0a3b0c56f73539121aedb72625f8839a90ff1 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:14:48 +0100 Subject: [PATCH 35/42] fix : current banned user --- app/modules/cmm/cruds_cmm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 02ef015ee6..979415e482 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -320,6 +320,7 @@ async def get_banned_users(db: AsyncSession) -> Sequence[models_core.CoreUser]: result = await db.execute( select(models_core.CoreUser) .join(models_cmm.Ban, models_core.CoreUser.id == models_cmm.Ban.user_id) + .where(models_cmm.Ban.end_time.is_(None)) .order_by(models_cmm.Ban.creation_time), ) return result.scalars().all() From 479b9ddf6186c6a9ce79d617a7a9e48bcd63c944 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 15 Feb 2025 12:19:00 +0100 Subject: [PATCH 36/42] fix : type checking --- app/modules/cmm/endpoints_cmm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index 504c601a34..dcf1533ce0 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -17,7 +17,6 @@ ) from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm from app.types.content_type import ContentType -from app.types.floors_type import FloorsType from app.types.module import Module from app.utils.tools import ( delete_file_from_data, @@ -25,6 +24,9 @@ save_file_as_data, ) +if TYPE_CHECKING: + from app.types.floors_type import FloorsType + module = Module( root="cmm", tag="Centrale Mega Meme", From bf5e49ee19d2c49abbae0820878493249362d8c0 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 15 Feb 2025 18:01:22 +0100 Subject: [PATCH 37/42] feat : migration and last fixes --- app/modules/cmm/cruds_cmm.py | 26 +++------ app/modules/cmm/endpoints_cmm.py | 42 +++++++++++++++ app/modules/cmm/schemas_cmm.py | 15 +++--- migrations/versions/29-cmm.py | 90 ++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 28 deletions(-) create mode 100644 migrations/versions/29-cmm.py diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/cmm/cruds_cmm.py index 979415e482..173c2836f8 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/cmm/cruds_cmm.py @@ -353,29 +353,14 @@ async def get_hidden_memes( return result.scalars().all() -# async def get_votes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Vote]: -# if n_jours == -1: -# result = await db.execute( -# select(models_cmm.Vote).options(selectinload(models_cmm.Vote.user)), -# ) - -# else: -# threshold_date = datetime.now(tz=UTC) - timedelta(days=n_jours) - -# result = await db.execute( -# select(models_cmm.Vote) -# .join(models_cmm.Meme) -# .where(models_cmm.Meme.creation_time >= threshold_date) -# .group_by(models_cmm.Meme.id) -# .options(selectinload(models_cmm.Vote.user)), -# ) -# return result.scalars().all() - - async def get_all_memes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Meme]: if n_jours == -1: result = await db.execute( - select(models_cmm.Meme).options(selectinload(models_cmm.Meme.user)), + select(models_cmm.Meme) + .where( + models_cmm.Meme.status == types_cmm.MemeStatus.neutral, + ) + .options(selectinload(models_cmm.Meme.user)), ) else: @@ -385,6 +370,7 @@ async def get_all_memes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Meme]: select(models_cmm.Meme) .where( models_cmm.Meme.creation_time >= threshold_date, + models_cmm.Meme.status == types_cmm.MemeStatus.neutral, ) .options(selectinload(models_cmm.Meme.user)), ) diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/cmm/endpoints_cmm.py index dcf1533ce0..fb1e730cbf 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/cmm/endpoints_cmm.py @@ -726,3 +726,45 @@ async def get_user_leaderbord( case _: raise HTTPException(status_code=404, detail="Invalid period") + + +@module.router.get( + "/cmm/leaderboard/me", + status_code=200, + response_model=schemas_cmm.Score, +) +async def get_my_leaderbord( + period: types_cmm.PeriodLeaderboard, + db: AsyncSession = Depends(get_db), + user: models_core.CoreUser = Depends(is_user_a_member), +): + match period: + case types_cmm.PeriodLeaderboard.week: + n_jours = 7 + case types_cmm.PeriodLeaderboard.month: + n_jours = 30 + case types_cmm.PeriodLeaderboard.year: + n_jours = 365 + case types_cmm.PeriodLeaderboard.always: + n_jours = -1 + case _: + raise HTTPException(status_code=404, detail="Invalid period") + + memes = await cruds_cmm.get_all_memes(db=db, n_jours=n_jours) + + my_score = sum(meme.vote_score for meme in memes if meme.user.id == user.id) + + user_scores: dict[str, int] = {} + + for meme in memes: + user_id = meme.user.id + user_scores[user_id] = user_scores.get(user_id, 0) + meme.vote_score + + sorted_scores = sorted(user_scores.items(), key=lambda item: item[1], reverse=True) + + my_position = next( + (i + 1 for i, (user_id, _) in enumerate(sorted_scores) if user_id == user.id), + None, + ) + + return {"score": my_score, "position": my_position} diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/cmm/schemas_cmm.py index 0ba16b377f..8f136eba1e 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/cmm/schemas_cmm.py @@ -46,19 +46,18 @@ class Ban(BaseModel): admin: CoreUserSimple -class UserScore(BaseModel): - user: CoreUserSimple +class Score(BaseModel): score: int position: int -class PromoScore(BaseModel): +class UserScore(Score): + user: CoreUserSimple + + +class PromoScore(Score): promo: int - score: int - position: int -class FloorScore(BaseModel): +class FloorScore(Score): floor: FloorsType - score: int - position: int diff --git a/migrations/versions/29-cmm.py b/migrations/versions/29-cmm.py new file mode 100644 index 0000000000..35a283d6db --- /dev/null +++ b/migrations/versions/29-cmm.py @@ -0,0 +1,90 @@ +"""cmm + +Create Date: 2025-02-15 17:56:46.270061 +""" + +from collections.abc import Sequence +from enum import Enum +from typing import TYPE_CHECKING, Union + +if TYPE_CHECKING: + from pytest_alembic import MigrationContext + +import sqlalchemy as sa +from alembic import op + +from app.types.sqlalchemy import TZDateTime + +# revision identifiers, used by Alembic. +revision: str = "43baf64975fe" +down_revision: str | None = "c778706af06f" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +class MemeStatus(Enum): + neutral = "neutral" + banned = "banned" + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "cmm_ban", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("user_id", sa.String(), nullable=False), + sa.Column("creation_time", TZDateTime(), nullable=False), + sa.Column("end_time", TZDateTime(), nullable=True), + sa.Column("admin_id", sa.String(), nullable=False), + sa.ForeignKeyConstraint(["admin_id"], ["core_user.id"]), + sa.ForeignKeyConstraint(["user_id"], ["core_user.id"]), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "cmm_meme", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column( + "status", + sa.Enum(MemeStatus, name="memestatus"), + nullable=False, + ), + sa.Column("user_id", sa.String(), nullable=False), + sa.Column("creation_time", TZDateTime(), nullable=False), + sa.Column("vote_score", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["user_id"], ["core_user.id"]), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "cmm_vote", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("user_id", sa.String(), nullable=False), + sa.Column("meme_id", sa.Uuid(), nullable=False), + sa.Column("positive", sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(["meme_id"], ["cmm_meme.id"]), + sa.ForeignKeyConstraint(["user_id"], ["core_user.id"]), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("cmm_vote") + op.drop_table("cmm_meme") + op.drop_table("cmm_ban") + sa.Enum(MemeStatus).drop(op.get_bind()) + # ### end Alembic commands ### + + +def pre_test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass + + +def test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass From 9f8cc54a948fbb868f094c239620a040a29c6bad Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 15 Feb 2025 23:22:41 +0100 Subject: [PATCH 38/42] Rename folder --- app/core/groups/groups_type.py | 2 +- app/modules/{cmm => meme}/__init__.py | 0 .../{cmm/cruds_cmm.py => meme/cruds_meme.py} | 212 ++++++++-------- .../endpoints_meme.py} | 226 +++++++++--------- .../models_cmm.py => meme/models_meme.py} | 10 +- .../schemas_cmm.py => meme/schemas_meme.py} | 2 +- .../{cmm/types_cmm.py => meme/types_meme.py} | 0 migrations/versions/{29-cmm.py => 29-meme.py} | 18 +- tests/{test_cmm.py => test_meme.py} | 90 +++---- 9 files changed, 281 insertions(+), 279 deletions(-) rename app/modules/{cmm => meme}/__init__.py (100%) rename app/modules/{cmm/cruds_cmm.py => meme/cruds_meme.py} (50%) rename app/modules/{cmm/endpoints_cmm.py => meme/endpoints_meme.py} (73%) rename app/modules/{cmm/models_cmm.py => meme/models_meme.py} (87%) rename app/modules/{cmm/schemas_cmm.py => meme/schemas_meme.py} (95%) rename app/modules/{cmm/types_cmm.py => meme/types_meme.py} (100%) rename migrations/versions/{29-cmm.py => 29-meme.py} (90%) rename tests/{test_cmm.py => test_meme.py} (66%) diff --git a/app/core/groups/groups_type.py b/app/core/groups/groups_type.py index 4993a5238d..8348e488c7 100644 --- a/app/core/groups/groups_type.py +++ b/app/core/groups/groups_type.py @@ -24,7 +24,7 @@ class GroupType(str, Enum): ph = "4ec5ae77-f955-4309-96a5-19cc3c8be71c" admin_cdr = "c1275229-46b2-4e53-a7c4-305513bb1a2a" eclair = "1f841bd9-00be-41a7-96e1-860a18a46105" - CMM = "3e5b04e5-5b19-4950-8e6a-143bdf559290" + meme = "3e5b04e5-5b19-4950-8e6a-143bdf559290" # Auth related groups diff --git a/app/modules/cmm/__init__.py b/app/modules/meme/__init__.py similarity index 100% rename from app/modules/cmm/__init__.py rename to app/modules/meme/__init__.py diff --git a/app/modules/cmm/cruds_cmm.py b/app/modules/meme/cruds_meme.py similarity index 50% rename from app/modules/cmm/cruds_cmm.py rename to app/modules/meme/cruds_meme.py index 173c2836f8..0e6e74e4ed 100644 --- a/app/modules/cmm/cruds_cmm.py +++ b/app/modules/meme/cruds_meme.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import selectinload from app.core import models_core -from app.modules.cmm import models_cmm, types_cmm +from app.modules.meme import models_meme, types_meme n_memes = 10 n_weeks = 7 @@ -19,21 +19,21 @@ async def get_memes_by_date( n_page: int, descending: bool, user_id: str, -) -> Sequence[models_cmm.Meme]: +) -> Sequence[models_meme.Meme]: result = await db.execute( - select(models_cmm.Meme) + select(models_meme.Meme) .options( selectinload( - models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), - ).load_only(models_cmm.Vote.positive), - selectinload(models_cmm.Meme.user), + models_meme.Meme.votes.and_(models_meme.Vote.user_id == user_id), + ).load_only(models_meme.Vote.positive), + selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) - .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) + .where(models_meme.Meme.status == types_meme.MemeStatus.neutral) .order_by( - models_cmm.Meme.creation_time.desc() + models_meme.Meme.creation_time.desc() if descending - else models_cmm.Meme.creation_time, + else models_meme.Meme.creation_time, ) .limit(n_memes) .offset((n_page - 1) * n_memes), @@ -46,18 +46,18 @@ async def get_my_memes( db: AsyncSession, n_page: int, user_id: str, -) -> Sequence[models_cmm.Meme]: +) -> Sequence[models_meme.Meme]: result = await db.execute( - select(models_cmm.Meme) + select(models_meme.Meme) .options( selectinload( - models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), - ).load_only(models_cmm.Vote.positive), - selectinload(models_cmm.Meme.user), + models_meme.Meme.votes.and_(models_meme.Vote.user_id == user_id), + ).load_only(models_meme.Vote.positive), + selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) - .where(models_cmm.Meme.user_id == user_id) - .order_by(models_cmm.Meme.creation_time.desc()) + .where(models_meme.Meme.user_id == user_id) + .order_by(models_meme.Meme.creation_time.desc()) .limit(n_memes) .offset((n_page - 1) * n_memes), ) @@ -70,21 +70,21 @@ async def get_memes_by_votes( n_page: int, descending: bool, user_id: str, -) -> Sequence[models_cmm.Meme]: +) -> Sequence[models_meme.Meme]: result = await db.execute( - select(models_cmm.Meme) - .where(models_cmm.Meme.status == types_cmm.MemeStatus.neutral) + select(models_meme.Meme) + .where(models_meme.Meme.status == types_meme.MemeStatus.neutral) .options( selectinload( - models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), - ).load_only(models_cmm.Vote.positive), - selectinload(models_cmm.Meme.user), + models_meme.Meme.votes.and_(models_meme.Vote.user_id == user_id), + ).load_only(models_meme.Vote.positive), + selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) .order_by( - models_cmm.Meme.vote_score.desc() + models_meme.Meme.vote_score.desc() if descending - else models_cmm.Meme.vote_score, + else models_meme.Meme.vote_score, ) .limit(n_memes) .offset((n_page - 1) * n_memes), @@ -97,21 +97,21 @@ async def get_trending_memes( db: AsyncSession, n_page: int, user_id: str, -) -> Sequence[models_cmm.Meme]: +) -> Sequence[models_meme.Meme]: result = await db.execute( - select(models_cmm.Meme) - .order_by(models_cmm.Meme.vote_score) + select(models_meme.Meme) + .order_by(models_meme.Meme.vote_score) .options( selectinload( - models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), - ).load_only(models_cmm.Vote.positive), - selectinload(models_cmm.Meme.user), + models_meme.Meme.votes.and_(models_meme.Vote.user_id == user_id), + ).load_only(models_meme.Vote.positive), + selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) .where( - (models_cmm.Meme.creation_time - datetime.now(tz=UTC)) + (models_meme.Meme.creation_time - datetime.now(tz=UTC)) < timedelta(days=n_weeks), - models_cmm.Meme.status == types_cmm.MemeStatus.neutral, + models_meme.Meme.status == types_meme.MemeStatus.neutral, ) .limit(n_memes) .offset((n_page - 1) * n_memes), @@ -123,21 +123,21 @@ async def get_trending_memes( async def get_memes_from_user( db: AsyncSession, user_id: str, -) -> Sequence[models_cmm.Meme]: +) -> Sequence[models_meme.Meme]: result = await db.execute( - select(models_cmm.Meme) + select(models_meme.Meme) .options( selectinload( - models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), - ).load_only(models_cmm.Vote.positive), - selectinload(models_cmm.Meme.user), + models_meme.Meme.votes.and_(models_meme.Vote.user_id == user_id), + ).load_only(models_meme.Vote.positive), + selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) .where( - models_cmm.Meme.user_id == user_id, - models_cmm.Meme.status == types_cmm.MemeStatus.neutral, + models_meme.Meme.user_id == user_id, + models_meme.Meme.status == types_meme.MemeStatus.neutral, ) - .order_by(models_cmm.Meme.creation_time), + .order_by(models_meme.Meme.creation_time), ) meme_page = result.scalars().all() return meme_page @@ -146,12 +146,12 @@ async def get_memes_from_user( async def update_ban_status_of_memes_from_user( db: AsyncSession, user_id: str, - new_ban_status: types_cmm.MemeStatus, + new_ban_status: types_meme.MemeStatus, ): await db.execute( - update(models_cmm.Meme) - .where(models_cmm.Meme.user_id == user_id) - .values({models_cmm.Meme.status: new_ban_status}), + update(models_meme.Meme) + .where(models_meme.Meme.user_id == user_id) + .values({models_meme.Meme.status: new_ban_status}), ) @@ -159,36 +159,36 @@ async def get_meme_by_id( db: AsyncSession, meme_id: uuid.UUID, user_id: str, -) -> models_cmm.Meme | None: +) -> models_meme.Meme | None: result = await db.execute( - select(models_cmm.Meme) + select(models_meme.Meme) .options( selectinload( - models_cmm.Meme.votes.and_( - models_cmm.Vote.user_id == user_id, + models_meme.Meme.votes.and_( + models_meme.Vote.user_id == user_id, ), - ).load_only(models_cmm.Vote.positive), - selectinload(models_cmm.Meme.user), + ).load_only(models_meme.Vote.positive), + selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) - .where(models_cmm.Meme.id == meme_id), + .where(models_meme.Meme.id == meme_id), ) return result.unique().scalars().first() -def add_meme(db: AsyncSession, meme: models_cmm.Meme): +def add_meme(db: AsyncSession, meme: models_meme.Meme): db.add(meme) async def update_meme_ban_status( db: AsyncSession, - ban_status: types_cmm.MemeStatus, + ban_status: types_meme.MemeStatus, meme_id: UUID, ): await db.execute( - update(models_cmm.Meme) - .where(models_cmm.Meme.id == meme_id) - .values({models_cmm.Meme.status: ban_status}), + update(models_meme.Meme) + .where(models_meme.Meme.id == meme_id) + .values({models_meme.Meme.status: ban_status}), ) @@ -209,15 +209,17 @@ async def update_meme_vote_score( score_diff = -1 if new_positive is None else -2 await db.execute( - update(models_cmm.Meme) - .where(models_cmm.Meme.id == meme_id) - .values({models_cmm.Meme.vote_score: models_cmm.Meme.vote_score + score_diff}), + update(models_meme.Meme) + .where(models_meme.Meme.id == meme_id) + .values( + {models_meme.Meme.vote_score: models_meme.Meme.vote_score + score_diff}, + ), ) async def delete_meme_by_id(db: AsyncSession, meme_id: UUID): await db.execute( - delete(models_cmm.Meme).where(models_cmm.Meme.id == meme_id), + delete(models_meme.Meme).where(models_meme.Meme.id == meme_id), ) @@ -225,11 +227,11 @@ async def get_vote( db: AsyncSession, meme_id: UUID, user_id: str, -) -> models_cmm.Vote | None: +) -> models_meme.Vote | None: result = await db.execute( - select(models_cmm.Vote).where( - models_cmm.Vote.meme_id == meme_id, - models_cmm.Vote.user_id == user_id, + select(models_meme.Vote).where( + models_meme.Vote.meme_id == meme_id, + models_meme.Vote.user_id == user_id, ), ) return result.unique().scalars().first() @@ -238,37 +240,37 @@ async def get_vote( async def get_vote_by_id( db: AsyncSession, vote_id: UUID, -) -> models_cmm.Vote | None: +) -> models_meme.Vote | None: result = await db.execute( - select(models_cmm.Vote).where(models_cmm.Vote.id == vote_id), + select(models_meme.Vote).where(models_meme.Vote.id == vote_id), ) return result.unique().scalars().first() -def add_vote(db: AsyncSession, vote: models_cmm.Vote): +def add_vote(db: AsyncSession, vote: models_meme.Vote): db.add(vote) async def update_vote(db: AsyncSession, vote_id: UUID, new_positive: bool): await db.execute( - update(models_cmm.Vote) - .where(models_cmm.Vote.id == vote_id) - .values({models_cmm.Vote.positive: new_positive}), + update(models_meme.Vote) + .where(models_meme.Vote.id == vote_id) + .values({models_meme.Vote.positive: new_positive}), ) async def delete_vote(db: AsyncSession, vote_id: UUID): await db.execute( - delete(models_cmm.Vote).where(models_cmm.Vote.id == vote_id), + delete(models_meme.Vote).where(models_meme.Vote.id == vote_id), ) async def get_ban_by_id( db: AsyncSession, ban_id: UUID, -) -> models_cmm.Ban | None: +) -> models_meme.Ban | None: result = await db.execute( - select(models_cmm.Ban).where(models_cmm.Ban.id == ban_id), + select(models_meme.Ban).where(models_meme.Ban.id == ban_id), ) return result.unique().scalars().first() @@ -276,11 +278,11 @@ async def get_ban_by_id( async def get_user_current_ban( db: AsyncSession, user_id: str, -) -> models_cmm.Ban | None: +) -> models_meme.Ban | None: result = await db.execute( - select(models_cmm.Ban).where( - models_cmm.Ban.user_id == user_id, - models_cmm.Ban.end_time.is_(None), + select(models_meme.Ban).where( + models_meme.Ban.user_id == user_id, + models_meme.Ban.end_time.is_(None), ), ) return result.unique().scalars().first() @@ -289,39 +291,39 @@ async def get_user_current_ban( async def get_user_ban_history( db: AsyncSession, user_id: str, -) -> Sequence[models_cmm.Ban]: +) -> Sequence[models_meme.Ban]: result = await db.execute( - select(models_cmm.Ban) - .where(models_cmm.Ban.user_id == user_id) - .order_by(models_cmm.Ban.creation_time), + select(models_meme.Ban) + .where(models_meme.Ban.user_id == user_id) + .order_by(models_meme.Ban.creation_time), ) return result.scalars().all() -def add_user_ban(db: AsyncSession, ban: models_cmm.Ban): +def add_user_ban(db: AsyncSession, ban: models_meme.Ban): db.add(ban) async def update_end_of_ban(db: AsyncSession, end_time: datetime, ban_id: UUID): await db.execute( - update(models_cmm.Ban) - .where(models_cmm.Ban.id == ban_id) - .values({models_cmm.Ban.end_time: end_time}), + update(models_meme.Ban) + .where(models_meme.Ban.id == ban_id) + .values({models_meme.Ban.end_time: end_time}), ) async def delete_ban(db: AsyncSession, ban_id: UUID): await db.execute( - delete(models_cmm.Ban).where(models_cmm.Ban.id == ban_id), + delete(models_meme.Ban).where(models_meme.Ban.id == ban_id), ) async def get_banned_users(db: AsyncSession) -> Sequence[models_core.CoreUser]: result = await db.execute( select(models_core.CoreUser) - .join(models_cmm.Ban, models_core.CoreUser.id == models_cmm.Ban.user_id) - .where(models_cmm.Ban.end_time.is_(None)) - .order_by(models_cmm.Ban.creation_time), + .join(models_meme.Ban, models_core.CoreUser.id == models_meme.Ban.user_id) + .where(models_meme.Ban.end_time.is_(None)) + .order_by(models_meme.Ban.creation_time), ) return result.scalars().all() @@ -331,21 +333,21 @@ async def get_hidden_memes( n_page: int, descending: bool, user_id: str, -) -> Sequence[models_cmm.Meme]: +) -> Sequence[models_meme.Meme]: result = await db.execute( - select(models_cmm.Meme) + select(models_meme.Meme) .options( selectinload( - models_cmm.Meme.votes.and_(models_cmm.Vote.user_id == user_id), - ).load_only(models_cmm.Vote.positive), - selectinload(models_cmm.Meme.user), + models_meme.Meme.votes.and_(models_meme.Vote.user_id == user_id), + ).load_only(models_meme.Vote.positive), + selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) - .where(models_cmm.Meme.status == types_cmm.MemeStatus.banned) + .where(models_meme.Meme.status == types_meme.MemeStatus.banned) .order_by( - models_cmm.Meme.creation_time.desc() + models_meme.Meme.creation_time.desc() if descending - else models_cmm.Meme.creation_time, + else models_meme.Meme.creation_time, ) .limit(n_memes) .offset((n_page - 1) * n_memes), @@ -353,25 +355,25 @@ async def get_hidden_memes( return result.scalars().all() -async def get_all_memes(db: AsyncSession, n_jours) -> Sequence[models_cmm.Meme]: +async def get_all_memes(db: AsyncSession, n_jours) -> Sequence[models_meme.Meme]: if n_jours == -1: result = await db.execute( - select(models_cmm.Meme) + select(models_meme.Meme) .where( - models_cmm.Meme.status == types_cmm.MemeStatus.neutral, + models_meme.Meme.status == types_meme.MemeStatus.neutral, ) - .options(selectinload(models_cmm.Meme.user)), + .options(selectinload(models_meme.Meme.user)), ) else: threshold_date = datetime.now(tz=UTC) - timedelta(days=n_jours) result = await db.execute( - select(models_cmm.Meme) + select(models_meme.Meme) .where( - models_cmm.Meme.creation_time >= threshold_date, - models_cmm.Meme.status == types_cmm.MemeStatus.neutral, + models_meme.Meme.creation_time >= threshold_date, + models_meme.Meme.status == types_meme.MemeStatus.neutral, ) - .options(selectinload(models_cmm.Meme.user)), + .options(selectinload(models_meme.Meme.user)), ) return result.scalars().all() diff --git a/app/modules/cmm/endpoints_cmm.py b/app/modules/meme/endpoints_meme.py similarity index 73% rename from app/modules/cmm/endpoints_cmm.py rename to app/modules/meme/endpoints_meme.py index fb1e730cbf..28d2b34264 100644 --- a/app/modules/cmm/endpoints_cmm.py +++ b/app/modules/meme/endpoints_meme.py @@ -15,7 +15,7 @@ is_user_a_member, is_user_in, ) -from app.modules.cmm import cruds_cmm, models_cmm, schemas_cmm, types_cmm +from app.modules.meme import cruds_meme, models_meme, schemas_meme, types_meme from app.types.content_type import ContentType from app.types.module import Module from app.utils.tools import ( @@ -28,7 +28,7 @@ from app.types.floors_type import FloorsType module = Module( - root="cmm", + root="meme", tag="Centrale Mega Meme", ) @@ -40,15 +40,15 @@ async def is_allowed_meme_user( """ Overloads the is_user() dependency injection to verify if the user is in the banned table """ - user_current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user.id) + user_current_ban = await cruds_meme.get_user_current_ban(db=db, user_id=user.id) if user_current_ban is not None: raise HTTPException(status_code=403, detail="You are currently banned") return user @module.router.get( - "/cmm/memes/", - response_model=list[schemas_cmm.ShownMeme], + "/meme/memes/", + response_model=list[schemas_meme.ShownMeme], status_code=200, ) async def get_memes( @@ -67,35 +67,35 @@ async def get_memes( ) match sort_by: - case types_cmm.MemeSort.best: - meme_page = await cruds_cmm.get_memes_by_votes( + case types_meme.MemeSort.best: + meme_page = await cruds_meme.get_memes_by_votes( db=db, descending=True, n_page=n_page, user_id=user.id, ) - case types_cmm.MemeSort.worst: - meme_page = await cruds_cmm.get_memes_by_votes( + case types_meme.MemeSort.worst: + meme_page = await cruds_meme.get_memes_by_votes( db=db, descending=False, n_page=n_page, user_id=user.id, ) - case types_cmm.MemeSort.trending: - meme_page = await cruds_cmm.get_trending_memes( + case types_meme.MemeSort.trending: + meme_page = await cruds_meme.get_trending_memes( db=db, n_page=n_page, user_id=user.id, ) - case types_cmm.MemeSort.newest: - meme_page = await cruds_cmm.get_memes_by_date( + case types_meme.MemeSort.newest: + meme_page = await cruds_meme.get_memes_by_date( db=db, descending=True, n_page=n_page, user_id=user.id, ) - case types_cmm.MemeSort.oldest: - meme_page = await cruds_cmm.get_memes_by_date( + case types_meme.MemeSort.oldest: + meme_page = await cruds_meme.get_memes_by_date( db=db, descending=False, n_page=n_page, @@ -105,7 +105,7 @@ async def get_memes( raise HTTPException(status_code=404, detail="Invalid sort method") return [ - schemas_cmm.ShownMeme( + schemas_meme.ShownMeme( id=str(meme.id), user=meme.user, creation_time=meme.creation_time, @@ -118,8 +118,8 @@ async def get_memes( @module.router.get( - "/cmm/memes/me", - response_model=list[schemas_cmm.ShownMeme], + "/meme/memes/me", + response_model=list[schemas_meme.ShownMeme], status_code=200, ) async def get_my_memes( @@ -133,14 +133,14 @@ async def get_my_memes( detail="Invalid page number", ) - meme_page = await cruds_cmm.get_my_memes( + meme_page = await cruds_meme.get_my_memes( db=db, n_page=n_page, user_id=user.id, ) return [ - schemas_cmm.ShownMeme( + schemas_meme.ShownMeme( id=str(meme.id), user=meme.user, creation_time=meme.creation_time, @@ -153,7 +153,7 @@ async def get_my_memes( @module.router.get( - "/cmm/memes/{meme_id}/img/", + "/meme/memes/{meme_id}/img/", status_code=200, response_class=FileResponse, ) @@ -165,7 +165,7 @@ async def get_meme_image_by_id( """ Get a meme image using its id """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + meme = await cruds_meme.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") @@ -177,26 +177,26 @@ async def get_meme_image_by_id( @module.router.post( - "/cmm/memes/{meme_id}/hide/", + "/meme/memes/{meme_id}/hide/", status_code=201, ) async def hide_meme_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): """ Hide a meme from db Must be admin """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + meme = await cruds_meme.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") try: - await cruds_cmm.update_meme_ban_status( + await cruds_meme.update_meme_ban_status( db=db, - ban_status=types_cmm.MemeStatus.banned, + ban_status=types_meme.MemeStatus.banned, meme_id=meme_id, ) await db.commit() @@ -206,26 +206,26 @@ async def hide_meme_by_id( @module.router.post( - "/cmm/memes/{meme_id}/show/", + "/meme/memes/{meme_id}/show/", status_code=201, ) async def show_meme_by_id( meme_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): """ Show a meme from db Must be admin """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + meme = await cruds_meme.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") try: - await cruds_cmm.update_meme_ban_status( + await cruds_meme.update_meme_ban_status( db=db, - ban_status=types_cmm.MemeStatus.neutral, + ban_status=types_meme.MemeStatus.neutral, meme_id=meme_id, ) await db.commit() @@ -235,7 +235,7 @@ async def show_meme_by_id( @module.router.delete( - "/cmm/memes/{meme_id}/", + "/meme/memes/{meme_id}/", status_code=204, ) async def delete_meme_by_id( @@ -247,13 +247,13 @@ async def delete_meme_by_id( Remove a meme from db Must be author of meme if meme is not banned """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + meme = await cruds_meme.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if not meme: raise HTTPException( status_code=404, detail="Invalid meme_id", ) - if meme.status == types_cmm.MemeStatus.banned: + if meme.status == types_meme.MemeStatus.banned: raise HTTPException( status_code=403, detail="You can't delete a banned meme", @@ -264,7 +264,7 @@ async def delete_meme_by_id( detail="You are not the author of this meme", ) try: - await cruds_cmm.delete_meme_by_id(db=db, meme_id=meme_id) + await cruds_meme.delete_meme_by_id(db=db, meme_id=meme_id) await delete_file_from_data(directory="meme", filename=str(meme_id)) await db.commit() except Exception: @@ -273,8 +273,8 @@ async def delete_meme_by_id( @module.router.post( - "/cmm/memes/", - response_model=schemas_cmm.Meme, + "/meme/memes/", + response_model=schemas_meme.Meme, status_code=201, ) async def add_meme( @@ -288,15 +288,15 @@ async def add_meme( """ try: meme_id = uuid.uuid4() - meme = models_cmm.Meme( + meme = models_meme.Meme( id=meme_id, user_id=user.id, creation_time=datetime.now(UTC), vote_score=0, votes=[], - status=types_cmm.MemeStatus.neutral, + status=types_meme.MemeStatus.neutral, ) - cruds_cmm.add_meme(db=db, meme=meme) + cruds_meme.add_meme(db=db, meme=meme) await db.commit() await save_file_as_data( upload_file=image, @@ -319,9 +319,9 @@ async def add_meme( @module.router.get( - "/cmm/memes/{meme_id}/vote/", + "/meme/memes/{meme_id}/vote/", status_code=200, - response_model=schemas_cmm.Vote, + response_model=schemas_meme.Vote, ) async def get_vote( meme_id: uuid.UUID, @@ -332,7 +332,7 @@ async def get_vote( """ Get a meme caracteristics using its id """ - vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user_id) + vote = await cruds_meme.get_vote(db=db, meme_id=meme_id, user_id=user_id) if vote is None: raise HTTPException( status_code=404, @@ -343,9 +343,9 @@ async def get_vote( @module.router.get( - "/cmm/memes/votes/{vote_id}/", + "/meme/memes/votes/{vote_id}/", status_code=200, - response_model=schemas_cmm.Vote, + response_model=schemas_meme.Vote, ) async def get_vote_by_id( vote_id: uuid.UUID, @@ -355,7 +355,7 @@ async def get_vote_by_id( """ Get a meme caracteristics using its id """ - vote = await cruds_cmm.get_vote_by_id(db=db, vote_id=vote_id) + vote = await cruds_meme.get_vote_by_id(db=db, vote_id=vote_id) if vote is None: raise HTTPException(status_code=404, detail="The vote does not exist") @@ -363,8 +363,8 @@ async def get_vote_by_id( @module.router.post( - "/cmm/memes/{meme_id}/vote/", - response_model=schemas_cmm.Vote, + "/meme/memes/{meme_id}/vote/", + response_model=schemas_meme.Vote, status_code=201, ) async def add_vote( @@ -376,34 +376,34 @@ async def add_vote( """ Add a new vote for the user to a meme from its id """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + meme = await cruds_meme.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") - vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) + vote = await cruds_meme.get_vote(db=db, meme_id=meme_id, user_id=user.id) if vote is not None: raise HTTPException(status_code=404, detail="Vote already created") try: vote_id = uuid.uuid4() - vote = models_cmm.Vote( + vote = models_meme.Vote( id=vote_id, meme_id=meme_id, user_id=user.id, positive=positive, ) - await cruds_cmm.update_meme_vote_score( + await cruds_meme.update_meme_vote_score( db=db, meme_id=meme_id, old_positive=None, new_positive=positive, ) - cruds_cmm.add_vote(db=db, vote=vote) + cruds_meme.add_vote(db=db, vote=vote) await db.commit() except Exception: await db.rollback() raise else: - return schemas_cmm.Vote( + return schemas_meme.Vote( meme_id=str(vote.meme_id), positive=vote.positive, user=vote.user, @@ -411,7 +411,7 @@ async def add_vote( @module.router.delete( - "/cmm/memes/{meme_id}/vote/", + "/meme/memes/{meme_id}/vote/", status_code=201, ) async def delete_vote( @@ -422,20 +422,20 @@ async def delete_vote( """ Remove the vote from the user if it exists """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + meme = await cruds_meme.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") - vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) + vote = await cruds_meme.get_vote(db=db, meme_id=meme_id, user_id=user.id) if vote is None: raise HTTPException(status_code=404, detail="The vote does not exist") try: - await cruds_cmm.update_meme_vote_score( + await cruds_meme.update_meme_vote_score( db=db, meme_id=meme_id, old_positive=meme.votes[0].positive if meme.votes else None, new_positive=None, ) - await cruds_cmm.delete_vote(db=db, vote_id=vote.id) + await cruds_meme.delete_vote(db=db, vote_id=vote.id) await db.commit() except Exception: await db.rollback() @@ -443,7 +443,7 @@ async def delete_vote( @module.router.patch( - "/cmm/memes/{meme_id}/vote/", + "/meme/memes/{meme_id}/vote/", status_code=201, ) async def update_vote( @@ -455,26 +455,26 @@ async def update_vote( """ Update a vote from the user if it exists even if vote is already at the right positivity """ - meme = await cruds_cmm.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) + meme = await cruds_meme.get_meme_by_id(db=db, meme_id=meme_id, user_id=user.id) if meme is None: raise HTTPException(status_code=404, detail="The meme does not exist") - vote = await cruds_cmm.get_vote(db=db, meme_id=meme_id, user_id=user.id) + vote = await cruds_meme.get_vote(db=db, meme_id=meme_id, user_id=user.id) if vote is None: raise HTTPException(status_code=404, detail="The vote does not exist") try: - await cruds_cmm.update_meme_vote_score( + await cruds_meme.update_meme_vote_score( db=db, meme_id=meme_id, old_positive=meme.votes[0].positive, # should exist new_positive=positive, ) - await cruds_cmm.update_vote(db=db, vote_id=vote.id, new_positive=positive) + await cruds_meme.update_vote(db=db, vote_id=vote.id, new_positive=positive) await db.commit() except Exception: await db.rollback() raise else: - return schemas_cmm.Vote( + return schemas_meme.Vote( meme_id=str(vote.meme_id), positive=positive, user=user, @@ -482,24 +482,24 @@ async def update_vote( @module.router.post( - "/cmm/users/{user_id}/ban/", + "/meme/users/{user_id}/ban/", status_code=201, ) async def ban_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): """ Ban a user and hide all of his memes Must be admin """ - current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user_id) + current_ban = await cruds_meme.get_user_current_ban(db=db, user_id=user_id) if current_ban is not None: raise HTTPException(status_code=404, detail="User is already banned") - ban = models_cmm.Ban( + ban = models_meme.Ban( id=uuid.uuid4(), user_id=user_id, admin_id=user.id, @@ -507,11 +507,11 @@ async def ban_user( creation_time=datetime.now(UTC), ) try: - cruds_cmm.add_user_ban(db=db, ban=ban) - await cruds_cmm.update_ban_status_of_memes_from_user( + cruds_meme.add_user_ban(db=db, ban=ban) + await cruds_meme.update_ban_status_of_memes_from_user( db=db, user_id=user_id, - new_ban_status=types_cmm.MemeStatus.banned, + new_ban_status=types_meme.MemeStatus.banned, ) await db.commit() except Exception: @@ -520,33 +520,33 @@ async def ban_user( @module.router.post( - "/cmm/users/{user_id}/unban/", + "/meme/users/{user_id}/unban/", status_code=201, ) async def unban_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): """ Unban a user and unhide all of his memes Must be admin """ - current_ban = await cruds_cmm.get_user_current_ban(db=db, user_id=user_id) + current_ban = await cruds_meme.get_user_current_ban(db=db, user_id=user_id) if current_ban is None: raise HTTPException(status_code=404, detail="User is not already banned") try: - await cruds_cmm.update_end_of_ban( + await cruds_meme.update_end_of_ban( db=db, ban_id=current_ban.id, end_time=datetime.now(UTC), ) - await cruds_cmm.update_ban_status_of_memes_from_user( + await cruds_meme.update_ban_status_of_memes_from_user( db=db, user_id=user_id, - new_ban_status=types_cmm.MemeStatus.neutral, + new_ban_status=types_meme.MemeStatus.neutral, ) await db.commit() except Exception: @@ -555,58 +555,58 @@ async def unban_user( @module.router.get( - "/cmm/users/{user_id}/ban_history/", + "/meme/users/{user_id}/ban_history/", status_code=200, - response_model=list[schemas_cmm.Ban], + response_model=list[schemas_meme.Ban], ) async def get_user_ban_history( user_id: str, db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): """ Get the ban history of an user """ - ban_history = await cruds_cmm.get_user_ban_history(db=db, user_id=user_id) + ban_history = await cruds_meme.get_user_ban_history(db=db, user_id=user_id) return ban_history @module.router.get( - "/cmm/users/banned/", + "/meme/users/banned/", status_code=200, response_model=list[schemas_core.CoreUserSimple], ) async def get_banned_users( db: AsyncSession = Depends(get_db), - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): - banned_users = await cruds_cmm.get_banned_users(db=db) + banned_users = await cruds_meme.get_banned_users(db=db) return banned_users @module.router.get( - "/cmm/memes/hidden/", + "/meme/memes/hidden/", status_code=200, - response_model=list[schemas_cmm.ShownMeme], + response_model=list[schemas_meme.ShownMeme], ) async def get_hidden_memes( db: AsyncSession = Depends(get_db), n_page: int = 1, - user: models_core.CoreUser = Depends(is_user_in(GroupType.CMM)), + user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): if n_page < 1: raise HTTPException( status_code=404, detail="Invalid page number", ) - hidden_memes = await cruds_cmm.get_hidden_memes( + hidden_memes = await cruds_meme.get_hidden_memes( db=db, descending=True, n_page=n_page, user_id=user.id, ) return [ - schemas_cmm.ShownMeme( + schemas_meme.ShownMeme( id=str(meme.id), user=meme.user, creation_time=meme.creation_time, @@ -619,34 +619,34 @@ async def get_hidden_memes( @module.router.get( - "/cmm/leaderboard/", + "/meme/leaderboard/", status_code=200, - response_model=list[schemas_cmm.UserScore] - | list[schemas_cmm.FloorScore] - | list[schemas_cmm.PromoScore], + response_model=list[schemas_meme.UserScore] + | list[schemas_meme.FloorScore] + | list[schemas_meme.PromoScore], ) async def get_user_leaderbord( - period: types_cmm.PeriodLeaderboard, - entity: types_cmm.EntityLeaderboard, + period: types_meme.PeriodLeaderboard, + entity: types_meme.EntityLeaderboard, db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_user_a_member), ): match period: - case types_cmm.PeriodLeaderboard.week: + case types_meme.PeriodLeaderboard.week: n_jours = 7 - case types_cmm.PeriodLeaderboard.month: + case types_meme.PeriodLeaderboard.month: n_jours = 30 - case types_cmm.PeriodLeaderboard.year: + case types_meme.PeriodLeaderboard.year: n_jours = 365 - case types_cmm.PeriodLeaderboard.always: + case types_meme.PeriodLeaderboard.always: n_jours = -1 case _: raise HTTPException(status_code=404, detail="Invalid period") - memes = await cruds_cmm.get_all_memes(db=db, n_jours=n_jours) + memes = await cruds_meme.get_all_memes(db=db, n_jours=n_jours) match entity: - case types_cmm.EntityLeaderboard.promo: + case types_meme.EntityLeaderboard.promo: promo_scores: dict[int, int] = {} for meme in memes: @@ -673,7 +673,7 @@ async def get_user_leaderbord( for i, (promo, total_score) in enumerate(sorted_promo_scores) ] - case types_cmm.EntityLeaderboard.floor: + case types_meme.EntityLeaderboard.floor: floor_scores: dict[FloorsType, int] = {} for meme in memes: @@ -700,7 +700,7 @@ async def get_user_leaderbord( for i, (floor, total_score) in enumerate(sorted_floor_scores) ] - case types_cmm.EntityLeaderboard.user: + case types_meme.EntityLeaderboard.user: user_scores: dict[str, int] = {} users: dict[str, models_core.CoreUser] = {} @@ -729,28 +729,28 @@ async def get_user_leaderbord( @module.router.get( - "/cmm/leaderboard/me", + "/meme/leaderboard/me", status_code=200, - response_model=schemas_cmm.Score, + response_model=schemas_meme.Score, ) async def get_my_leaderbord( - period: types_cmm.PeriodLeaderboard, + period: types_meme.PeriodLeaderboard, db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_user_a_member), ): match period: - case types_cmm.PeriodLeaderboard.week: + case types_meme.PeriodLeaderboard.week: n_jours = 7 - case types_cmm.PeriodLeaderboard.month: + case types_meme.PeriodLeaderboard.month: n_jours = 30 - case types_cmm.PeriodLeaderboard.year: + case types_meme.PeriodLeaderboard.year: n_jours = 365 - case types_cmm.PeriodLeaderboard.always: + case types_meme.PeriodLeaderboard.always: n_jours = -1 case _: raise HTTPException(status_code=404, detail="Invalid period") - memes = await cruds_cmm.get_all_memes(db=db, n_jours=n_jours) + memes = await cruds_meme.get_all_memes(db=db, n_jours=n_jours) my_score = sum(meme.vote_score for meme in memes if meme.user.id == user.id) diff --git a/app/modules/cmm/models_cmm.py b/app/modules/meme/models_meme.py similarity index 87% rename from app/modules/cmm/models_cmm.py rename to app/modules/meme/models_meme.py index 691428b7ba..b747d6469c 100644 --- a/app/modules/cmm/models_cmm.py +++ b/app/modules/meme/models_meme.py @@ -5,17 +5,17 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.models_core import CoreUser -from app.modules.cmm.types_cmm import MemeStatus +from app.modules.meme.types_meme import MemeStatus from app.types.sqlalchemy import Base, PrimaryKey class Vote(Base): - __tablename__ = "cmm_vote" + __tablename__ = "meme_vote" id: Mapped[PrimaryKey] user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) user: Mapped[CoreUser] = relationship("CoreUser", init=False) - meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("cmm_meme.id")) + meme_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("meme_meme.id")) meme: Mapped["Meme"] = relationship( "Meme", init=False, @@ -26,7 +26,7 @@ class Vote(Base): class Meme(Base): - __tablename__ = "cmm_meme" + __tablename__ = "meme_meme" id: Mapped[PrimaryKey] status: Mapped[MemeStatus] @@ -43,7 +43,7 @@ class Meme(Base): class Ban(Base): - __tablename__ = "cmm_ban" + __tablename__ = "meme_ban" id: Mapped[PrimaryKey] user_id: Mapped[str] = mapped_column(ForeignKey("core_user.id")) diff --git a/app/modules/cmm/schemas_cmm.py b/app/modules/meme/schemas_meme.py similarity index 95% rename from app/modules/cmm/schemas_cmm.py rename to app/modules/meme/schemas_meme.py index 8f136eba1e..8cee8d1077 100644 --- a/app/modules/cmm/schemas_cmm.py +++ b/app/modules/meme/schemas_meme.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, ConfigDict from app.core.schemas_core import CoreUserSimple -from app.modules.cmm.types_cmm import MemeStatus +from app.modules.meme.types_meme import MemeStatus from app.types.floors_type import FloorsType diff --git a/app/modules/cmm/types_cmm.py b/app/modules/meme/types_meme.py similarity index 100% rename from app/modules/cmm/types_cmm.py rename to app/modules/meme/types_meme.py diff --git a/migrations/versions/29-cmm.py b/migrations/versions/29-meme.py similarity index 90% rename from migrations/versions/29-cmm.py rename to migrations/versions/29-meme.py index 35a283d6db..cefb2b1151 100644 --- a/migrations/versions/29-cmm.py +++ b/migrations/versions/29-meme.py @@ -1,11 +1,11 @@ -"""cmm +"""meme Create Date: 2025-02-15 17:56:46.270061 """ from collections.abc import Sequence from enum import Enum -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING if TYPE_CHECKING: from pytest_alembic import MigrationContext @@ -30,7 +30,7 @@ class MemeStatus(Enum): def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( - "cmm_ban", + "meme_ban", sa.Column("id", sa.Uuid(), nullable=False), sa.Column("user_id", sa.String(), nullable=False), sa.Column("creation_time", TZDateTime(), nullable=False), @@ -41,7 +41,7 @@ def upgrade() -> None: sa.PrimaryKeyConstraint("id"), ) op.create_table( - "cmm_meme", + "meme_meme", sa.Column("id", sa.Uuid(), nullable=False), sa.Column( "status", @@ -55,12 +55,12 @@ def upgrade() -> None: sa.PrimaryKeyConstraint("id"), ) op.create_table( - "cmm_vote", + "meme_vote", sa.Column("id", sa.Uuid(), nullable=False), sa.Column("user_id", sa.String(), nullable=False), sa.Column("meme_id", sa.Uuid(), nullable=False), sa.Column("positive", sa.Boolean(), nullable=False), - sa.ForeignKeyConstraint(["meme_id"], ["cmm_meme.id"]), + sa.ForeignKeyConstraint(["meme_id"], ["meme_meme.id"]), sa.ForeignKeyConstraint(["user_id"], ["core_user.id"]), sa.PrimaryKeyConstraint("id"), ) @@ -69,9 +69,9 @@ def upgrade() -> None: def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("cmm_vote") - op.drop_table("cmm_meme") - op.drop_table("cmm_ban") + op.drop_table("meme_vote") + op.drop_table("meme_meme") + op.drop_table("meme_ban") sa.Enum(MemeStatus).drop(op.get_bind()) # ### end Alembic commands ### diff --git a/tests/test_cmm.py b/tests/test_meme.py similarity index 66% rename from tests/test_cmm.py rename to tests/test_meme.py index a53f376dde..4843454d53 100644 --- a/tests/test_cmm.py +++ b/tests/test_meme.py @@ -24,23 +24,23 @@ from app.core import models_core from app.core.groups.groups_type import GroupType -from app.modules.cmm import models_cmm -from app.modules.cmm.types_cmm import MemeStatus +from app.modules.meme import models_meme +from app.modules.meme.types_meme import MemeStatus from tests.commons import ( add_object_to_db, create_api_access_token, create_user_with_groups, ) -cmm_user_1: models_core.CoreUser -cmm_user_2: models_core.CoreUser -cmm_user_to_ban: models_core.CoreUser -cmm_admin: models_core.CoreUser +meme_user_1: models_core.CoreUser +meme_user_2: models_core.CoreUser +meme_user_to_ban: models_core.CoreUser +meme_admin: models_core.CoreUser -memes_1: list[models_cmm.Meme] -votes_memes_1: list[models_cmm.Vote] -memes_2: list[models_cmm.Meme] -memes_to_ban: list[models_cmm.Meme] +memes_1: list[models_meme.Meme] +votes_memes_1: list[models_meme.Vote] +memes_2: list[models_meme.Meme] +memes_to_ban: list[models_meme.Meme] token_user_1: str token_user_2: str token_user_to_ban: str @@ -49,32 +49,32 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: - global cmm_user_1 - cmm_user_1 = await create_user_with_groups([]) - global token_cmm_1 - token_cmm_1 = create_api_access_token(cmm_user_1) - - global cmm_user_2 - cmm_user_2 = await create_user_with_groups([]) - global token_cmm_2 - token_cmm_2 = create_api_access_token(cmm_user_2) - - global cmm_user_to_ban - cmm_user_to_ban = await create_user_with_groups([]) + global meme_user_1 + meme_user_1 = await create_user_with_groups([]) + global token_meme_1 + token_meme_1 = create_api_access_token(meme_user_1) + + global meme_user_2 + meme_user_2 = await create_user_with_groups([]) + global token_meme_2 + token_meme_2 = create_api_access_token(meme_user_2) + + global meme_user_to_ban + meme_user_to_ban = await create_user_with_groups([]) global token_user_to_ban - token_user_to_ban = create_api_access_token(cmm_user_to_ban) + token_user_to_ban = create_api_access_token(meme_user_to_ban) - global cmm_admin - cmm_admin = await create_user_with_groups([GroupType.admin]) + global meme_admin + meme_admin = await create_user_with_groups([GroupType.admin]) global token_admin - token_admin = create_api_access_token(cmm_admin) + token_admin = create_api_access_token(meme_admin) global memes_1 memes_1 = [ - models_cmm.Meme( + models_meme.Meme( id=uuid.uuid4(), status=MemeStatus.neutral, - user_id=cmm_user_1.id, + user_id=meme_user_1.id, creation_time=datetime.datetime(24, i, 23, tzinfo=datetime.UTC), vote_score=i, ) @@ -85,18 +85,18 @@ async def init_objects() -> None: for i, meme in enumerate(memes_1): await add_object_to_db(meme) if i % 2 == 0: - vote = models_cmm.Vote( + vote = models_meme.Vote( id=uuid.uuid4(), - user_id=cmm_user_1.id, + user_id=meme_user_1.id, meme_id=meme.id, positive=True, ) votes_memes_1.append(vote) await add_object_to_db(vote) if i % 2 == 1: - vote2 = models_cmm.Vote( + vote2 = models_meme.Vote( id=uuid.uuid4(), - user_id=cmm_user_2.id, + user_id=meme_user_2.id, meme_id=meme.id, positive=True, ) @@ -105,10 +105,10 @@ async def init_objects() -> None: global memes_2 memes_2 = [ - models_cmm.Meme( + models_meme.Meme( id=uuid.uuid4(), status=MemeStatus.neutral, - user_id=cmm_user_1.id, + user_id=meme_user_1.id, creation_time=datetime.datetime(24, i, 23, tzinfo=datetime.UTC), vote_score=i, ) @@ -118,10 +118,10 @@ async def init_objects() -> None: await add_object_to_db(meme) global memes_to_ban memes_to_ban = [ - models_cmm.Meme( + models_meme.Meme( id=uuid.uuid4(), status=MemeStatus.neutral, - user_id=cmm_user_to_ban.id, + user_id=meme_user_to_ban.id, creation_time=datetime.datetime(24, i, 23, tzinfo=datetime.UTC), vote_score=200, ) @@ -133,8 +133,8 @@ async def init_objects() -> None: def test_get_meme_page(client: TestClient) -> None: response = client.get( - "/cmm/memes/?sort_by=best&n_page=1", - headers={"Authorization": f"Bearer {token_cmm_2}"}, + "/meme/memes/?sort_by=best&n_page=1", + headers={"Authorization": f"Bearer {token_meme_2}"}, ) print(response) print(response.status_code) @@ -145,31 +145,31 @@ def test_get_meme_page(client: TestClient) -> None: def test_banning_user(client: TestClient) -> None: # TODO: Add test to prove that memes are hidden response = client.get( - "/cmm/memes/?sort_by=best&n_page=1", + "/meme/memes/?sort_by=best&n_page=1", headers={"Authorization": f"Bearer {token_user_to_ban}"}, ) assert response.status_code == 200 response = client.post( - f"/cmm/users/{cmm_user_to_ban.id}/ban", + f"/meme/users/{meme_user_to_ban.id}/ban", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 201 response = client.get( - "/cmm/memes/?sort_by=best&n_page=1", + "/meme/memes/?sort_by=best&n_page=1", headers={"Authorization": f"Bearer {token_user_to_ban}"}, ) assert response.status_code == 403 response = client.get( - "/cmm/memes/?sort_by=best&n_page=1", - headers={"Authorization": f"Bearer {token_cmm_1}"}, + "/meme/memes/?sort_by=best&n_page=1", + headers={"Authorization": f"Bearer {token_meme_1}"}, ) response = client.post( - f"/cmm/users/{cmm_user_to_ban.id}/unban", + f"/meme/users/{meme_user_to_ban.id}/unban", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 201 response = client.get( - "/cmm/memes/?sort_by=best&n_page=1", + "/meme/memes/?sort_by=best&n_page=1", headers={"Authorization": f"Bearer {token_user_to_ban}"}, ) assert response.status_code == 200 From 6d985faa8d4e02489a338baf1309da6cb0475d37 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sun, 16 Feb 2025 00:51:26 +0100 Subject: [PATCH 39/42] fix : remove useless await --- app/modules/meme/endpoints_meme.py | 2 +- tests/test_meme.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/modules/meme/endpoints_meme.py b/app/modules/meme/endpoints_meme.py index 28d2b34264..ceb73024e3 100644 --- a/app/modules/meme/endpoints_meme.py +++ b/app/modules/meme/endpoints_meme.py @@ -265,7 +265,7 @@ async def delete_meme_by_id( ) try: await cruds_meme.delete_meme_by_id(db=db, meme_id=meme_id) - await delete_file_from_data(directory="meme", filename=str(meme_id)) + delete_file_from_data(directory="meme", filename=str(meme_id)) await db.commit() except Exception: await db.rollback() diff --git a/tests/test_meme.py b/tests/test_meme.py index 4843454d53..ea5546dc21 100644 --- a/tests/test_meme.py +++ b/tests/test_meme.py @@ -17,7 +17,6 @@ import datetime import uuid -from pathlib import Path import pytest_asyncio from fastapi.testclient import TestClient From bb9a4c2aeb2a3a0dcaeca4f37f3d02bb93d35e82 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Mon, 17 Feb 2025 08:42:50 +0100 Subject: [PATCH 40/42] feat : remove page feature --- app/modules/meme/cruds_meme.py | 24 ++++-------------------- app/modules/meme/endpoints_meme.py | 28 +--------------------------- 2 files changed, 5 insertions(+), 47 deletions(-) diff --git a/app/modules/meme/cruds_meme.py b/app/modules/meme/cruds_meme.py index 0e6e74e4ed..6c0dacf368 100644 --- a/app/modules/meme/cruds_meme.py +++ b/app/modules/meme/cruds_meme.py @@ -10,13 +10,11 @@ from app.core import models_core from app.modules.meme import models_meme, types_meme -n_memes = 10 n_weeks = 7 async def get_memes_by_date( db: AsyncSession, - n_page: int, descending: bool, user_id: str, ) -> Sequence[models_meme.Meme]: @@ -34,9 +32,7 @@ async def get_memes_by_date( models_meme.Meme.creation_time.desc() if descending else models_meme.Meme.creation_time, - ) - .limit(n_memes) - .offset((n_page - 1) * n_memes), + ), ) meme_page = result.scalars().all() return meme_page @@ -44,7 +40,6 @@ async def get_memes_by_date( async def get_my_memes( db: AsyncSession, - n_page: int, user_id: str, ) -> Sequence[models_meme.Meme]: result = await db.execute( @@ -58,8 +53,6 @@ async def get_my_memes( .execution_options(populate_existing=True) .where(models_meme.Meme.user_id == user_id) .order_by(models_meme.Meme.creation_time.desc()) - .limit(n_memes) - .offset((n_page - 1) * n_memes), ) meme_page = result.scalars().all() return meme_page @@ -67,7 +60,6 @@ async def get_my_memes( async def get_memes_by_votes( db: AsyncSession, - n_page: int, descending: bool, user_id: str, ) -> Sequence[models_meme.Meme]: @@ -85,9 +77,7 @@ async def get_memes_by_votes( models_meme.Meme.vote_score.desc() if descending else models_meme.Meme.vote_score, - ) - .limit(n_memes) - .offset((n_page - 1) * n_memes), + ), ) meme_page = result.scalars().all() return meme_page @@ -95,7 +85,6 @@ async def get_memes_by_votes( async def get_trending_memes( db: AsyncSession, - n_page: int, user_id: str, ) -> Sequence[models_meme.Meme]: result = await db.execute( @@ -112,9 +101,7 @@ async def get_trending_memes( (models_meme.Meme.creation_time - datetime.now(tz=UTC)) < timedelta(days=n_weeks), models_meme.Meme.status == types_meme.MemeStatus.neutral, - ) - .limit(n_memes) - .offset((n_page - 1) * n_memes), + ), ) meme_page = result.scalars().all() return meme_page @@ -330,7 +317,6 @@ async def get_banned_users(db: AsyncSession) -> Sequence[models_core.CoreUser]: async def get_hidden_memes( db: AsyncSession, - n_page: int, descending: bool, user_id: str, ) -> Sequence[models_meme.Meme]: @@ -348,9 +334,7 @@ async def get_hidden_memes( models_meme.Meme.creation_time.desc() if descending else models_meme.Meme.creation_time, - ) - .limit(n_memes) - .offset((n_page - 1) * n_memes), + ), ) return result.scalars().all() diff --git a/app/modules/meme/endpoints_meme.py b/app/modules/meme/endpoints_meme.py index ceb73024e3..66dd9f57e9 100644 --- a/app/modules/meme/endpoints_meme.py +++ b/app/modules/meme/endpoints_meme.py @@ -53,52 +53,41 @@ async def is_allowed_meme_user( ) async def get_memes( sort_by: str = "best", - n_page: int = 1, db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_user_a_member), ): """ - Get a page of memes according to the asked sort + Get memes according to the asked sort """ - if n_page < 1: - raise HTTPException( - status_code=404, - detail="Invalid page number", - ) match sort_by: case types_meme.MemeSort.best: meme_page = await cruds_meme.get_memes_by_votes( db=db, descending=True, - n_page=n_page, user_id=user.id, ) case types_meme.MemeSort.worst: meme_page = await cruds_meme.get_memes_by_votes( db=db, descending=False, - n_page=n_page, user_id=user.id, ) case types_meme.MemeSort.trending: meme_page = await cruds_meme.get_trending_memes( db=db, - n_page=n_page, user_id=user.id, ) case types_meme.MemeSort.newest: meme_page = await cruds_meme.get_memes_by_date( db=db, descending=True, - n_page=n_page, user_id=user.id, ) case types_meme.MemeSort.oldest: meme_page = await cruds_meme.get_memes_by_date( db=db, descending=False, - n_page=n_page, user_id=user.id, ) case _: @@ -123,19 +112,11 @@ async def get_memes( status_code=200, ) async def get_my_memes( - n_page: int = 1, db: AsyncSession = Depends(get_db), user: models_core.CoreUser = Depends(is_user_a_member), ): - if n_page < 1: - raise HTTPException( - status_code=404, - detail="Invalid page number", - ) - meme_page = await cruds_meme.get_my_memes( db=db, - n_page=n_page, user_id=user.id, ) @@ -591,18 +572,11 @@ async def get_banned_users( ) async def get_hidden_memes( db: AsyncSession = Depends(get_db), - n_page: int = 1, user: models_core.CoreUser = Depends(is_user_in(GroupType.meme)), ): - if n_page < 1: - raise HTTPException( - status_code=404, - detail="Invalid page number", - ) hidden_memes = await cruds_meme.get_hidden_memes( db=db, descending=True, - n_page=n_page, user_id=user.id, ) return [ From 0d1dd1ee463afa867b94bda6e69f278eb83b5748 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 22 Feb 2025 14:48:48 +0100 Subject: [PATCH 41/42] fix : answers --- app/modules/meme/endpoints_meme.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/meme/endpoints_meme.py b/app/modules/meme/endpoints_meme.py index 66dd9f57e9..9a05be5d65 100644 --- a/app/modules/meme/endpoints_meme.py +++ b/app/modules/meme/endpoints_meme.py @@ -393,7 +393,7 @@ async def add_vote( @module.router.delete( "/meme/memes/{meme_id}/vote/", - status_code=201, + status_code=204, ) async def delete_vote( meme_id: uuid.UUID, @@ -425,7 +425,7 @@ async def delete_vote( @module.router.patch( "/meme/memes/{meme_id}/vote/", - status_code=201, + status_code=204, ) async def update_vote( meme_id: uuid.UUID, From 6016bbae21261dca526dcdbb036598812f8ec572 Mon Sep 17 00:00:00 2001 From: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Date: Sat, 22 Feb 2025 20:42:29 +0100 Subject: [PATCH 42/42] fix : various improvement --- app/modules/meme/cruds_meme.py | 2 +- app/modules/meme/endpoints_meme.py | 6 +++--- app/modules/meme/types_meme.py | 2 +- migrations/versions/29-meme.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/modules/meme/cruds_meme.py b/app/modules/meme/cruds_meme.py index 6c0dacf368..25c1f1a538 100644 --- a/app/modules/meme/cruds_meme.py +++ b/app/modules/meme/cruds_meme.py @@ -329,7 +329,7 @@ async def get_hidden_memes( selectinload(models_meme.Meme.user), ) .execution_options(populate_existing=True) - .where(models_meme.Meme.status == types_meme.MemeStatus.banned) + .where(models_meme.Meme.status == types_meme.MemeStatus.hidden) .order_by( models_meme.Meme.creation_time.desc() if descending diff --git a/app/modules/meme/endpoints_meme.py b/app/modules/meme/endpoints_meme.py index 9a05be5d65..f840fc4e10 100644 --- a/app/modules/meme/endpoints_meme.py +++ b/app/modules/meme/endpoints_meme.py @@ -177,7 +177,7 @@ async def hide_meme_by_id( try: await cruds_meme.update_meme_ban_status( db=db, - ban_status=types_meme.MemeStatus.banned, + ban_status=types_meme.MemeStatus.hidden, meme_id=meme_id, ) await db.commit() @@ -234,7 +234,7 @@ async def delete_meme_by_id( status_code=404, detail="Invalid meme_id", ) - if meme.status == types_meme.MemeStatus.banned: + if meme.status == types_meme.MemeStatus.hidden: raise HTTPException( status_code=403, detail="You can't delete a banned meme", @@ -492,7 +492,7 @@ async def ban_user( await cruds_meme.update_ban_status_of_memes_from_user( db=db, user_id=user_id, - new_ban_status=types_meme.MemeStatus.banned, + new_ban_status=types_meme.MemeStatus.hidden, ) await db.commit() except Exception: diff --git a/app/modules/meme/types_meme.py b/app/modules/meme/types_meme.py index 66a0117420..6d3a3bf497 100644 --- a/app/modules/meme/types_meme.py +++ b/app/modules/meme/types_meme.py @@ -3,7 +3,7 @@ class MemeStatus(str, Enum): neutral = "neutral" - banned = "banned" + hidden = "hidden" class MemeSort(str, Enum): diff --git a/migrations/versions/29-meme.py b/migrations/versions/29-meme.py index cefb2b1151..c605539ece 100644 --- a/migrations/versions/29-meme.py +++ b/migrations/versions/29-meme.py @@ -24,7 +24,7 @@ class MemeStatus(Enum): neutral = "neutral" - banned = "banned" + hidden = "hidden" def upgrade() -> None: