Skip to content

Commit

Permalink
feat: Bookmark catalog-items (#2303)
Browse files Browse the repository at this point in the history
* feat: Bookmark catalog-items
  • Loading branch information
aleixhub authored Dec 10, 2024
1 parent 412f4f7 commit c6808ec
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 18 deletions.
35 changes: 35 additions & 0 deletions catalog/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,41 @@ async def provision_rating_post(request):
url=f"{ratings_api}/api/ratings/v1/request/{request_uid}",
)


@routes.get("/api/user-manager/bookmarks")
async def provision_rating_get(request):
user = await get_proxy_user(request)
email = user['metadata']['name']
return await api_proxy(
headers=request.headers,
method="GET",
url=f"{ratings_api}/api/user-manager/v1/bookmarks/{email}",
)

@routes.post("/api/user-manager/bookmarks")
async def bookmark_post(request):
user = await get_proxy_user(request)
data = await request.json()
data["email"] = user['metadata']['name']
return await api_proxy(
data=json.dumps(data),
headers=request.headers,
method="POST",
url=f"{ratings_api}/api/user-manager/v1/bookmarks",
)

@routes.delete("/api/user-manager/bookmarks")
async def bookmark_delete(request):
user = await get_proxy_user(request)
data = await request.json()
data["email"] = user['metadata']['name']
return await api_proxy(
data=json.dumps(data),
headers=request.headers,
method="DELETE",
url=f"{ratings_api}/api/user-manager/v1/bookmarks",
)

@routes.get("/api/ratings/catalogitem/{asset_uuid}/history")
async def provision_rating_get_history(request):
asset_uuid = request.match_info.get('asset_uuid')
Expand Down
2 changes: 1 addition & 1 deletion catalog/helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ api:
threads: 1
image:
#override:
tag: v1.0.6
tag: v1.0.7
repository: quay.io/redhat-gpte/babylon-catalog-api
pullPolicy: IfNotPresent
imagePullSecrets: []
Expand Down
2 changes: 1 addition & 1 deletion helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ catalog:
image:
pullPolicy: IfNotPresent
repository: quay.io/redhat-gpte/babylon-catalog-api
tag: v1.0.6
tag: v1.0.7
loggingLevel: INFO
replicaCount: 1
resources:
Expand Down
3 changes: 2 additions & 1 deletion ratings/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from fastapi.responses import HTMLResponse
from models import Database as db
from models.custom_base import create_tables
from routers import ratings
from routers import ratings, bookmarks
from babylon import Babylon


Expand Down Expand Up @@ -69,3 +69,4 @@ async def index(request: Request):

# Including Routers
app.include_router(ratings.router)
app.include_router(bookmarks.router)
3 changes: 2 additions & 1 deletion ratings/api/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
import os

from .custom_base import Base, CustomBase, CustomBaseUuid, CustomBaseProvisionUuid, create_tables
from .custom_base import Base, CustomBase, CustomBaseMinimal, CustomBaseUuid, CustomBaseProvisionUuid, create_tables
from .database import Database
from .reporting_config import ReportingConfig
from .catalog_item import CatalogItem
Expand All @@ -13,6 +13,7 @@
from .provision_request import ProvisionRequest
from .provision import Provision
from .rating import Rating
from .bookmark import Bookmark


async def startup():
Expand Down
50 changes: 50 additions & 0 deletions ratings/api/models/bookmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from __future__ import annotations
from typing import Optional, List
import os
from datetime import datetime, timedelta
from sqlalchemy.orm import (
Mapped,
mapped_column,
relationship,
selectinload,
)
from sqlalchemy import (
Boolean,
DDL,
ForeignKey,
Integer,
String,
and_,
desc,
exists,
event,
or_,
select,
text,
)
from . import CustomBaseMinimal
from .database import Database as db
from .catalog_item import CatalogItem
import logging

logger = logging.getLogger()

class Bookmark(CustomBaseMinimal):
__tablename__ = 'bookmarks'
__date_field__ = 'created_at'

user_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'), unique=False, nullable=False, comment='User id', primary_key=True)
asset_uuid: Mapped[str] = mapped_column(String, ForeignKey('catalog_items.asset_uuid'), unique=False, nullable=False, primary_key=True)

user: Mapped["User"] = relationship("User", back_populates="bookmarks")

async def check_existing(self) -> Optional[Bookmark]:
async with db.get_session() as session:
stmt = select(Bookmark)
stmt = stmt.where(and_(Bookmark.user_id == self.user_id,
Bookmark.asset_uuid == self.asset_uuid
)
)
result = await session.execute(stmt)
return result.scalars().first()

18 changes: 18 additions & 0 deletions ratings/api/models/custom_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ async def check_existing(self) -> Optional['Base']:
async def save(self) -> Optional['Base']:
existing = await self.check_existing()
async with db.get_session() as session:
logger.info(existing)
if existing:
for key, value in vars(self).items():
if key and str(key).startswith('_sa_instance_state'):
Expand All @@ -51,6 +52,17 @@ async def save(self) -> Optional['Base']:
await session.refresh(resource)
return resource

async def delete(self) -> bool:
existing = await self.check_existing()
async with db.get_session() as session:
if existing:
await session.delete(existing)
await session.commit()
return True
else:
raise Exception("Element not found")
return False

@staticmethod
def format_sql(sql):
keywords = ['SELECT', 'LEFT JOIN',
Expand Down Expand Up @@ -123,6 +135,12 @@ def to_dict(self, include_relationships=False):

return data

class CustomBaseMinimal(Base):
__abstract__ = True
__date_field__ = None

created_at = Column(DateTime, server_default=text("(NOW() at time zone 'utc')"), index=True)
updated_at = Column(DateTime, index=True)

class CustomBase(Base):
__abstract__ = True
Expand Down
14 changes: 3 additions & 11 deletions ratings/api/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from . import CustomBase
from .provision_request import ProvisionRequest
from .provision import Provision
from .bookmark import Bookmark
from .database import Database as db

user_min_update_days = int(os.getenv('USER_UPDATE_MIN_DAYS', 30))
Expand Down Expand Up @@ -51,8 +52,6 @@ class User(CustomBase):
user_group: Mapped[str] = mapped_column(String, nullable=False, index=True)
user_source: Mapped[str] = mapped_column(String, index=True)

# manager_levels: Mapped[List['UserManagerLevel']] = relationship('UserManagerLevel')

provisions: Mapped[List[Provision]] = relationship(Provision,
back_populates="user",
primaryjoin="User.id == Provision.user_id"
Expand All @@ -61,6 +60,7 @@ class User(CustomBase):
back_populates='user',
foreign_keys=[ProvisionRequest.user_id]
)
bookmarks: Mapped[List[Bookmark]] = relationship(Bookmark, back_populates="user", lazy="joined")

async def check_existing(self) -> Optional[User]:
async with db.get_session() as session:
Expand All @@ -69,7 +69,6 @@ async def check_existing(self) -> Optional[User]:
User.username == self.username
)
)
stmt = stmt.options(selectinload(User.manager_levels))
result = await session.execute(stmt)
return result.scalars().first()

Expand All @@ -78,7 +77,6 @@ async def get_by_email(cls, email: str) -> Optional[User]:
async with db.get_session() as session:
stmt = select(User)
stmt = stmt.where(User.email == email)
stmt = stmt.options(selectinload(User.manager_levels))
stmt = stmt.order_by(desc(User.updated_at)).limit(1)

result = await session.execute(stmt)
Expand All @@ -92,7 +90,6 @@ async def get_by_email_or_username(cls, email_or_username: str) -> Optional[User
User.username == email_or_username
)
)
stmt = stmt.options(selectinload(User.manager_levels))
stmt = stmt.order_by(desc(User.updated_at)).limit(1)

result = await session.execute(stmt)
Expand All @@ -109,7 +106,6 @@ async def get_by_email_or_username_last_updated(cls, email_or_username: str) ->
User.updated_at > thirty_days_ago
)
)
stmt = stmt.options(selectinload(User.manager_levels))
stmt = stmt.order_by(desc(User.updated_at)).limit(1)

result = await session.execute(stmt)
Expand All @@ -120,7 +116,6 @@ async def get_by_employee_number(cls, employee_number: int) -> Optional[User]:
async with db.get_session() as session:
stmt = select(User)
stmt = stmt.where(User.employee_number == employee_number)
stmt = stmt.options(selectinload(User.manager_levels))
stmt = stmt.order_by(User.employee_number).limit(1)

result = await session.execute(stmt)
Expand All @@ -131,7 +126,7 @@ async def get_by_id(cls, student_id: int) -> Optional[User]:
async with db.get_session() as session:
stmt = select(User)
stmt = stmt.where(User.id == student_id)
stmt = stmt.options(selectinload(User.manager_levels)).limit(1)
stmt = stmt.limit(1)
result = await session.execute(stmt)
return result.scalars().first()

Expand All @@ -144,7 +139,6 @@ async def get_email_last_updated(cls, email: str) -> Optional[User]:
User.updated_at > thirty_days_ago
)
)
stmt = stmt.options(selectinload(User.manager_levels))
stmt = stmt.order_by(desc(User.updated_at)).limit(1)

result = await session.execute(stmt)
Expand All @@ -162,7 +156,6 @@ async def get_employee_last_updated(cls,
User.updated_at > thirty_days_ago
)
)
stmt = stmt.options(selectinload(User.manager_levels))
stmt = stmt.order_by(desc(User.updated_at)).limit(1)

result = await session.execute(stmt)
Expand All @@ -177,7 +170,6 @@ async def get_username_last_updated(cls, username: str) -> Optional[User]:
User.updated_at > thirty_days_ago
)
)
stmt = stmt.options(selectinload(User.manager_levels))
stmt = stmt.order_by(desc(User.updated_at)).limit(1)
result = await session.execute(stmt)
return result.scalars().first()
Expand Down
2 changes: 1 addition & 1 deletion ratings/api/routers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import Query
from . import ratings

from . import bookmarks

def get_pagination_params(
page: int = Query(1, description="Número da página a ser recuperada"),
Expand Down
77 changes: 77 additions & 0 deletions ratings/api/routers/bookmarks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import List, Optional
import logging
from fastapi import APIRouter, HTTPException, Depends
from schemas import (
BookmarkSchema,
BookmarkListSchema
)
from models import Bookmark, User
logger = logging.getLogger('babylon-ratings')


tags = ["user-manager"]

router = APIRouter(tags=tags)


@router.get("/api/user-manager/v1/bookmarks/{email}",
response_model=BookmarkListSchema,
summary="Get favorites catalog item asset")
async def bookmarks_get(email: str) -> BookmarkListSchema:

logger.info(f"Getting favorites for user {email}")
try:
user = await User.get_by_email(email)
if user:
logger.info(user.bookmarks)
return BookmarkListSchema(bookmarks=user.bookmarks)
else:
raise HTTPException(status_code=404, detail="User email doesn't exists") from e
except Exception as e:
logger.error(f"Error getting favorite: {e}", stack_info=True)
raise HTTPException(status_code=500, detail="Error getting favorites") from e

@router.post("/api/user-manager/v1/bookmarks",
response_model=BookmarkListSchema,
summary="Add bookmark",
)
async def bookmarks_post(email: str,
asset_uuid: str) -> {}:

logger.info(f"Add favorite item for user {email}")
try:
user = await User.get_by_email(email)
if user:
logger.info(user)
bookmark = Bookmark.from_dict({"user_id": user.id, "asset_uuid": asset_uuid})
await bookmark.save()
user = await User.get_by_email(email)
return BookmarkListSchema(bookmarks=user.bookmarks)
else:
raise HTTPException(status_code=404, detail="User email doesn't exists") from e
except Exception as e:
logger.error(f"Error saving favorite: {e}", stack_info=True)
raise HTTPException(status_code=500, detail="Error saving favorites") from e

@router.delete("/api/user-manager/v1/bookmarks",
response_model={},
summary="Delete bookmark",
)
async def bookmarks_delete(email: str,
asset_uuid: str) -> BookmarkListSchema:

logger.info(f"Delete favorite item for user {email}")
try:
user = await User.get_by_email(email)
if user:
bookmark = Bookmark.from_dict({"user_id": user.id, "asset_uuid": asset_uuid})
await bookmark.delete()
user = await User.get_by_email(email)
return BookmarkListSchema(bookmarks=user.bookmarks)
else:
raise HTTPException(status_code=404, detail="User email doesn't exists") from e

except Exception as e:
logger.error(f"Error deleting favorite: {e}", stack_info=True)
raise HTTPException(status_code=404, detail="Error deleting favorite") from e

1 change: 1 addition & 0 deletions ratings/api/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from .request import RequestSchema
from .user import UserSchema
from .workshop import WorkshopSchema, WorkshopRequestSchema
from .bookmarks import BookmarkSchema, BookmarkListSchema
19 changes: 19 additions & 0 deletions ratings/api/schemas/bookmarks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel, Field


class BookmarkSchema(BaseModel):
asset_uuid: Optional[str] = Field(None, description="The asset uuid of the catalog item.")
user_id: int = Field(..., description="The unique identifier for the user.")
created_at: Optional[datetime] = Field(None, description="The date the bookmark was created.")
updated_at: Optional[datetime] = Field(None, description="The date the bookmark was updated.")

class Config:
from_attributes = True

class BookmarkListSchema(BaseModel):
bookmarks: Optional[List[BookmarkSchema]] = Field(..., description="The list of bookmarks.")

class Config:
from_attributes = True
4 changes: 2 additions & 2 deletions ratings/helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ apiVersion: v2
name: babylon-ratings
description: A Helm chart for the babylon ratings component.
type: application
version: 1.0.8
appVersion: 1.0.8
version: 1.0.9
appVersion: 1.0.9

0 comments on commit c6808ec

Please sign in to comment.