diff --git a/backend/api/annotations/resources.py b/backend/api/annotations/resources.py index 9f78699b96..591421bb4a 100644 --- a/backend/api/annotations/resources.py +++ b/backend/api/annotations/resources.py @@ -3,22 +3,30 @@ from backend.services.project_service import ProjectService from backend.services.task_annotations_service import TaskAnnotationsService from fastapi import APIRouter, Depends, Request -from backend.db import get_session from starlette.authentication import requires from loguru import logger +from backend.db import get_db +from databases import Database +from backend.services.users.authentication_service import login_required +from backend.models.dtos.user_dto import AuthUserDTO router = APIRouter( prefix="/projects", tags=["projects"], - dependencies=[Depends(get_session)], + dependencies=[Depends(get_db)], responses={404: {"description": "Not found"}}, ) -# class AnnotationsRestAPI(Resource): @router.get("/{project_id}/annotations/{annotation_type}/") @router.get("/{project_id}/annotations/") -async def get(request: Request, project_id: int, annotation_type: str = None): +async def get( + request: Request, + project_id: int, + annotation_type: str = None, + user: AuthUserDTO = Depends(login_required), + db: Database = Depends(get_db), +): """ Get all task annotations for a project --- diff --git a/backend/api/projects/favorites.py b/backend/api/projects/favorites.py index 2f0dd66afd..131dc69288 100644 --- a/backend/api/projects/favorites.py +++ b/backend/api/projects/favorites.py @@ -1,26 +1,25 @@ -# from flask_restful import Resource - -from backend.models.dtos.project_dto import ProjectFavoriteDTO from backend.services.project_service import ProjectService - -# from backend.services.users.authentication_service import token_auth from fastapi import APIRouter, Depends, Request -from backend.db import get_session -from starlette.authentication import requires +from backend.db import get_db +from databases import Database +from backend.services.users.authentication_service import login_required +from backend.models.dtos.user_dto import AuthUserDTO router = APIRouter( prefix="/projects", tags=["projects"], - dependencies=[Depends(get_session)], + dependencies=[Depends(get_db)], responses={404: {"description": "Not found"}}, ) -# class ProjectsFavoritesAPI(Resource): -# @token_auth.login_required @router.get("/{project_id}/favorite/") -@requires("authenticated") -async def get(request: Request, project_id: int): +async def get( + request: Request, + project_id: int, + user: AuthUserDTO = Depends(login_required), + db: Database = Depends(get_db), +): """ Validate that project is favorited --- @@ -51,17 +50,19 @@ async def get(request: Request, project_id: int): description: Internal Server Error """ user_id = request.user.display_name if request.user else None - favorited = ProjectService.is_favorited(project_id, user_id) + favorited = await ProjectService.is_favorited(project_id, user_id, db) if favorited is True: - return {"favorited": True}, 200 + return {"favorited": True} + return {"favorited": False} - return {"favorited": False}, 200 - -# @token_auth.login_required @router.post("/{project_id}/favorite/") -@requires("authenticated") -async def post(request: Request, project_id: int): +async def post( + request: Request, + project_id: int, + user: AuthUserDTO = Depends(login_required), + db: Database = Depends(get_db), +): """ Set a project as favorite --- @@ -91,19 +92,18 @@ async def post(request: Request, project_id: int): 500: description: Internal Server Error """ - authenticated_user_id = request.user.display_name if request.user else None - favorite_dto = ProjectFavoriteDTO() - favorite_dto.project_id = project_id - favorite_dto.user_id = authenticated_user_id - ProjectService.favorite(project_id, authenticated_user_id) - return {"project_id": project_id}, 200 + await ProjectService.favorite(project_id, user.id, db) + return {"project_id": project_id} -# @token_auth.login_required @router.delete("/{project_id}/favorite/") -@requires("authenticated") -async def delete(request: Request, project_id: int): +async def delete( + request: Request, + project_id: int, + user: AuthUserDTO = Depends(login_required), + db: Database = Depends(get_db), +): """ Unsets a project as favorite --- @@ -134,8 +134,7 @@ async def delete(request: Request, project_id: int): description: Internal Server Error """ try: - ProjectService.unfavorite(project_id, request.user.display_name) + await ProjectService.unfavorite(project_id, user.id, db) except ValueError as e: return {"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]}, 400 - - return {"project_id": project_id}, 200 + return {"project_id": project_id} diff --git a/backend/api/users/resources.py b/backend/api/users/resources.py index fe48f19d10..77e5cad679 100644 --- a/backend/api/users/resources.py +++ b/backend/api/users/resources.py @@ -138,11 +138,12 @@ async def get(request: Request, session: AsyncSession = Depends(get_session)): return users_dto.model_dump(by_alias=True), 200 -# class UsersQueriesFavoritesAPI(): -# @token_auth.login_required @router.get("/queries/favorites/") -@requires("authenticated") -async def get(request: Request): +async def get( + request: Request, + user: AuthUserDTO = Depends(login_required), + db: Database = Depends(get_db), +): """ Get projects favorited by a user --- @@ -165,8 +166,8 @@ async def get(request: Request): 500: description: Internal Server Error """ - favs_dto = UserService.get_projects_favorited(request.user.display_name) - return favs_dto.model_dump(by_alias=True), 200 + favs_dto = await UserService.get_projects_favorited(user.id, db) + return favs_dto # class UsersQueriesUsernameAPI(): diff --git a/backend/models/dtos/project_dto.py b/backend/models/dtos/project_dto.py index 291564afa3..f58b94b2db 100644 --- a/backend/models/dtos/project_dto.py +++ b/backend/models/dtos/project_dto.py @@ -322,6 +322,9 @@ def __init__(self, favorited_projects: List[ProjectDTO] = None, **kwargs): favorited_projects: List[ProjectDTO] = Field(default=[], alias="favoritedProjects") + class Config: + populate_by_name = True + class ProjectSearchDTO(BaseModel): """Describes the criteria users use to filter active projects""" diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py index 75d8f6d751..8eea48c0d5 100644 --- a/backend/models/postgis/project.py +++ b/backend/models/postgis/project.py @@ -566,24 +566,64 @@ async def exists(project_id: int, db: Database) -> bool: return True - def is_favorited(self, user_id: int) -> bool: - user = session.get(User, user_id) - if user not in self.favorited: - return False + # def is_favorited(self, user_id: int) -> bool: + # user = session.get(User, user_id) + # if user not in self.favorited: + # return False - return True + # return True - def favorite(self, user_id: int): - user = session.get(User, user_id) - self.favorited.append(user) - session.commit() + @staticmethod + async def is_favorited(project_id: int, user_id: int, db: Database) -> bool: + query = """ + SELECT 1 + FROM project_favorites + WHERE user_id = :user_id + AND project_id = :project_id + LIMIT 1 + """ - def unfavorite(self, user_id: int): - user = session.get(User, user_id) - if user not in self.favorited: - raise ValueError("NotFeatured- Project not been favorited by user") - self.favorited.remove(user) - session.commit() + result = await db.fetch_one( + query, values={"user_id": user_id, "project_id": project_id} + ) + return result is not None + + @staticmethod + async def favorite(project_id: int, user_id: int, db: Database): + check_query = """ + SELECT 1 FROM project_favorites WHERE project_id = :project_id AND user_id = :user_id + """ + exists = await db.fetch_one( + check_query, {"project_id": project_id, "user_id": user_id} + ) + + if not exists: + insert_query = """ + INSERT INTO project_favorites (project_id, user_id) + VALUES (:project_id, :user_id) + """ + await db.execute( + insert_query, {"project_id": project_id, "user_id": user_id} + ) + + @staticmethod + async def unfavorite(project_id: int, user_id: int, db: Database): + check_query = """ + SELECT 1 FROM project_favorites + WHERE project_id = :project_id AND user_id = :user_id + """ + exists = await db.fetch_one( + check_query, {"project_id": project_id, "user_id": user_id} + ) + + if not exists: + raise ValueError("NotFeatured - Project has not been favorited by user") + + delete_query = """ + DELETE FROM project_favorites + WHERE project_id = :project_id AND user_id = :user_id + """ + await db.execute(delete_query, {"project_id": project_id, "user_id": user_id}) def set_as_featured(self): if self.featured is True: diff --git a/backend/services/project_service.py b/backend/services/project_service.py index be01128147..5067988c4b 100644 --- a/backend/services/project_service.py +++ b/backend/services/project_service.py @@ -628,20 +628,19 @@ async def get_featured_projects( return dto @staticmethod - def is_favorited(project_id: int, user_id: int) -> bool: - project = ProjectService.get_project_by_id(project_id) - - return project.is_favorited(user_id) + async def is_favorited(project_id: int, user_id: int, db: Database) -> bool: + await ProjectService.exists(project_id, db) + return await Project.is_favorited(project_id, user_id, db) @staticmethod - def favorite(project_id: int, user_id: int): - project = ProjectService.get_project_by_id(project_id) - project.favorite(user_id) + async def favorite(project_id: int, user_id: int, db: Database): + await ProjectService.exists(project_id, db) + await Project.favorite(project_id, user_id, db) @staticmethod - def unfavorite(project_id: int, user_id: int): - project = ProjectService.get_project_by_id(project_id) - project.unfavorite(user_id) + async def unfavorite(project_id: int, user_id: int, db: Database): + await ProjectService.exists(project_id, db) + await Project.unfavorite(project_id, user_id, db) @staticmethod def get_project_title(project_id: int, preferred_locale: str = "en") -> str: diff --git a/backend/services/users/user_service.py b/backend/services/users/user_service.py index 48845fba8b..ffc7ea95c4 100644 --- a/backend/services/users/user_service.py +++ b/backend/services/users/user_service.py @@ -128,13 +128,24 @@ async def update_user( return user @staticmethod - def get_projects_favorited(user_id: int) -> ProjectFavoritesDTO: - user = UserService.get_user_by_id(user_id) - projects_dto = [f.as_dto_for_admin(f.id) for f in user.favorites] + async def get_projects_favorited(user_id: int, db: Database) -> ProjectFavoritesDTO: + # Query to get the project IDs favorited by the user + project_ids_query = """ + SELECT project_id + FROM project_favorites + WHERE user_id = :user_id + """ + project_ids_rows = await db.fetch_all(project_ids_query, {"user_id": user_id}) + if not project_ids_rows: + return ProjectFavoritesDTO(favorited_projects=[]) + + projects_dto = [ + await Project.as_dto_for_admin(row["project_id"], db) + for row in project_ids_rows + ] fav_dto = ProjectFavoritesDTO() fav_dto.favorited_projects = projects_dto - return fav_dto @staticmethod