diff --git a/backend/controllers/currently_watching_controller.go b/backend/controllers/currently_watching_controller.go index 54d199e..2a072a1 100644 --- a/backend/controllers/currently_watching_controller.go +++ b/backend/controllers/currently_watching_controller.go @@ -128,6 +128,66 @@ func (contr *CurrentlyWatchingController) GetCurrentlyWatchingHandler(c *gin.Con c.JSON(200, watch) } +// GetCurrentlyWatchingHandler retrieves a currently watching record by TMDB ID +// @Summary Retrieve a currently watching record +// @Description Get a currently watching record by TMDB ID +// @Tags currently-watching +// @Accept json +// @Produce json +// @Param userID path int true "User ID" +// @Param tmdbID path int true "TMDB ID" +// @Param includeDeleted query bool false "Set to false to exclude soft deleted record" default(false) +// @Success 200 {object} db.CurrentlyWatching "Successfully retrieved the currently watching record" +// @Failure 400 {object} map[string]interface{} "Error: Record not found" +// @Router /currently-watching/{userID}/{tmdbID}/ [get] +func (contr *CurrentlyWatchingController) GetCurrentlyWatchingHandlerByTMDBID(c *gin.Context) { + // Parse userID from path + userIDStr := c.Param("userID") + userID, err := strconv.Atoi(userIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Invalid user ID. Error: " + err.Error(), + }) + return + } + + // Parse tmdbID from path + tmdbIDStr := c.Param("tmdbID") + tmdbID, err := strconv.Atoi(tmdbIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Invalid TMDB ID. Error: " + err.Error(), + }) + return + } + + // Parse includeDeleted query parameter (default: false) + includeDeletedStr := c.DefaultQuery("includeDeleted", "false") + includeDeleted, err := strconv.ParseBool(includeDeletedStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Invalid includeDeleted query. Error: " + err.Error(), + }) + return + } + + // Call the service function to fetch the currently watching record + watch, err := contr.service.GetCurrentlyWatchingByTMDBId(uint(userID), uint(tmdbID), includeDeleted) + if err != nil { + c.JSON(400, gin.H{ + "message": "No currently watching records found. Error: " + err.Error(), + }) + return + } + + // Return true if the record exists, otherwise false + if watch == nil { + c.JSON(200, gin.H{"exists": false}) + } else { + c.JSON(200, gin.H{"exists": true}) + } +} + // UpdateCurrentlyWatchingHandler updates a currently watching record // @Summary Update a currently watching record // @Description update a currently watching record diff --git a/backend/daos/currently_watching_dao.go b/backend/daos/currently_watching_dao.go index a56639e..6efd434 100644 --- a/backend/daos/currently_watching_dao.go +++ b/backend/daos/currently_watching_dao.go @@ -46,6 +46,28 @@ func (dao *CurrentlyWatchingDao) GetCurrentlyWatchingById(userID uint, mediaId u return ¤tlyWatching, nil } +func (dao *CurrentlyWatchingDao) GetCurrentlyWatchingByTMDBId(userID uint, TMDBID uint, includeDeleted bool) (*db.CurrentlyWatching, error) { + databaseInstance := database.GetInstance() + var currentlyWatching db.CurrentlyWatching + + // Join the media table to filter by tmdb_id + query := databaseInstance. + Joins("JOIN media ON media.id = currently_watchings.media_id"). + Where("currently_watchings.user_id = ? AND media.tmdb_id = ?", userID, TMDBID) + + // Exclude deleted records if includeDeleted is false + if !includeDeleted { + query = query.Where("currently_watchings.deleted_at IS NULL") + } + + // Execute query + if err := query.First(¤tlyWatching).Error; err != nil { + return nil, err + } + + return ¤tlyWatching, nil +} + func (dao *CurrentlyWatchingDao) GetCurrentlyWatchingByUserId(userID uint, includeDeleted bool) ([]*db.CurrentlyWatching, error) { databaseInstance := database.GetInstance() diff --git a/backend/daos/interfaces/currently_watching_dao_interface.go b/backend/daos/interfaces/currently_watching_dao_interface.go index cfe91a3..b2d849e 100644 --- a/backend/daos/interfaces/currently_watching_dao_interface.go +++ b/backend/daos/interfaces/currently_watching_dao_interface.go @@ -5,6 +5,7 @@ import "github.com/STREAM-BUSTER/stream-buster/models/db" type CurrentlyWatchingDaoInterface interface { CreateCurrentlyWatching(watch *db.CurrentlyWatching) (*db.CurrentlyWatching, error) GetCurrentlyWatchingById(userID uint, mediaId uint, includeDeleted bool) (*db.CurrentlyWatching, error) + GetCurrentlyWatchingByTMDBId(userID uint, TMDBID uint, includeDeleted bool) (*db.CurrentlyWatching, error) GetCurrentlyWatchingByUserId(userID uint, includeDeleted bool) ([]*db.CurrentlyWatching, error) UpdateCurrentlyWatching(updatedWatch *db.CurrentlyWatching) (*db.CurrentlyWatching, error) GetWatchlist(userId uint) ([]db.CurrentlyWatching, error) diff --git a/backend/main.go b/backend/main.go index 66bccbd..5d0749d 100644 --- a/backend/main.go +++ b/backend/main.go @@ -30,7 +30,7 @@ import ( // @externalDocs.url https://swagger.io/resources/open-api/ func main() { // Uncomment the following line to run the db initialization for updates - database.InitializeDb() + // database.InitializeDb() // Initialize the router router := routes.InitRouter() diff --git a/backend/routes/api/v1/currently_watching_routes.go b/backend/routes/api/v1/currently_watching_routes.go index 1525783..23669ec 100644 --- a/backend/routes/api/v1/currently_watching_routes.go +++ b/backend/routes/api/v1/currently_watching_routes.go @@ -12,6 +12,7 @@ func SetCurrentlyWatchingRoutes(router *gin.RouterGroup) { { group.POST("/", controller.CreateCurrentlyWatchingHandler) group.GET("/getall", controller.GetAllCurrentlyWatchingHandler) + group.GET("/:userID/:tmdbID", controller.GetCurrentlyWatchingHandlerByTMDBID) group.GET("/watchlist", controller.GetWatchlist) group.PUT("/update", controller.UpdateCurrentlyWatchingHandler) group.DELETE("/delete/:mediaId", controller.DeleteCurrentlyWatchingHandler) diff --git a/backend/services/currently_watching_service.go b/backend/services/currently_watching_service.go index 86e5889..a68a292 100644 --- a/backend/services/currently_watching_service.go +++ b/backend/services/currently_watching_service.go @@ -24,6 +24,11 @@ func (service *CurrentlyWatchingService) GetCurrentlyWatchingById(userID uint, m return service.dao.GetCurrentlyWatchingById(userID, mediaId, includeDeleted) } +// Method to retrieve a CurrentlyWatching entry by userID and mediaId +func (service *CurrentlyWatchingService) GetCurrentlyWatchingByTMDBId(userID uint, TMDBID uint, includeDeleted bool) (*db.CurrentlyWatching, error) { + return service.dao.GetCurrentlyWatchingByTMDBId(userID, TMDBID, includeDeleted) +} + // Method to retrieve a CurrentlyWatching entry by userID and mediaId func (service *CurrentlyWatchingService) GetCurrentlyWatchingByUserId(userID uint, includeDeleted bool) ([]*db.CurrentlyWatching, error) { return service.dao.GetCurrentlyWatchingByUserId(userID, includeDeleted) diff --git a/backend/services/interfaces/currently_watching_service_interface.go b/backend/services/interfaces/currently_watching_service_interface.go index 4b6c125..b306b78 100644 --- a/backend/services/interfaces/currently_watching_service_interface.go +++ b/backend/services/interfaces/currently_watching_service_interface.go @@ -6,6 +6,7 @@ type CurrentlyWatchingServiceInterface interface { CreateCurrentlyWatching(watch *db.CurrentlyWatching) (*db.CurrentlyWatching, error) GetCurrentlyWatchingById(userID uint, mediaId uint, includeDeleted bool) (*db.CurrentlyWatching, error) GetCurrentlyWatchingByUserId(userID uint, includeDeleted bool) ([]*db.CurrentlyWatching, error) + GetCurrentlyWatchingByTMDBId(userID uint, TMDBID uint, includeDeleted bool) (*db.CurrentlyWatching, error) UpdateCurrentlyWatching(updatedWatch *db.CurrentlyWatching) (*db.CurrentlyWatching, error) GetWatchlist(userID uint) ([]db.CurrentlyWatching, error) DeleteCurrentlyWatching(userId uint, mediaId uint) error diff --git a/frontend/src/api/services/currentlyWatching.service.ts b/frontend/src/api/services/currentlyWatching.service.ts index 293efb6..1e135bb 100644 --- a/frontend/src/api/services/currentlyWatching.service.ts +++ b/frontend/src/api/services/currentlyWatching.service.ts @@ -38,6 +38,17 @@ export const updateCurrentlyWatching = async (data: CurrentlyWatching) => { } } +export const getIsOnWatchList = async (userID: number, tmdbID: number, includeDeleted = false): Promise => { + try { + const result = await instance.get(`/currently-watching/${userID}/${tmdbID}`, { + params: { includeDeleted } + }); + return result.data.exists; + } catch (error) { + console.error("Error fetching watch list:", error); + throw error; + } +}; export const getWatchList = async (): Promise => { try { diff --git a/frontend/src/components/media-details-modal/media-details-modal-header/MediaDetailsModalHeader.tsx b/frontend/src/components/media-details-modal/media-details-modal-header/MediaDetailsModalHeader.tsx index fc48460..3387841 100644 --- a/frontend/src/components/media-details-modal/media-details-modal-header/MediaDetailsModalHeader.tsx +++ b/frontend/src/components/media-details-modal/media-details-modal-header/MediaDetailsModalHeader.tsx @@ -1,13 +1,13 @@ -import React from 'react'; -import { Box, IconButton, Button, Typography, Tooltip, useMediaQuery, useTheme } from '@mui/material'; -import { PlayArrow, Add, ThumbUp, Close } from '@mui/icons-material'; +import React, { useEffect, useState } from 'react'; +import { Box, IconButton, Button, Typography, Tooltip, useMediaQuery, useTheme, CircularProgress } from '@mui/material'; +import { PlayArrow, Add, ThumbUp, Close, Check } from '@mui/icons-material'; import { Movie } from '../../../models/movie'; import { TV } from '../../../models/tv'; import { useNavigate } from 'react-router-dom'; import { Episode } from '../../../models/episode'; import { useTranslation } from 'react-i18next'; import { useUser } from '../../../hooks/useUser'; -import { onAddToList } from '../../../api/services/currentlyWatching.service'; +import { getIsOnWatchList, onAddToList } from '../../../api/services/currentlyWatching.service'; import { useSnackbar } from '../../../hooks/useSnackBar'; import { AvailabilityInfo } from './AvailabilityInfo'; @@ -25,7 +25,9 @@ export const MediaDetailsModalHeader: React.FC = ( const user = useUser(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const { showSnackbar, SnackbarComponent } = useSnackbar() + const { showSnackbar, SnackbarComponent } = useSnackbar(); + const [isOnWatchList, setIsOnWatchList] = useState(false); + const [isOnWatchListLoading, setIsOnWatchListLoading] = useState(true); // Define styles as a JSON object const styles = { @@ -88,6 +90,22 @@ export const MediaDetailsModalHeader: React.FC = ( } }; + //useEffects + useEffect(() => { + const onPageLoad = async () => { + try { + setIsOnWatchListLoading(true); + const watchList: boolean = await getIsOnWatchList(user.user?.ID!, media.Media?.TMDBID!); + setIsOnWatchList(watchList); + } catch (error) { + console.error("Error fetching watchlist status:", error); + } finally { + setIsOnWatchListLoading(false); + } + }; + + onPageLoad(); + }, []); // Constants const defaultBackdropImage = "https://cdn.prod.website-files.com/5e261bc81db8f19fa664899d/64add0eb758ddc8d390ed4a0_out-0.png" @@ -116,6 +134,7 @@ export const MediaDetailsModalHeader: React.FC = ( try { await onAddToList(media, user, currentEpisode?.SeasonNumber, currentEpisode?.EpisodeNumber) showSnackbar("Successfully added to watchlist") + setIsOnWatchList(true) } catch (error) { showSnackbar("Error added to watchlist") } @@ -174,10 +193,24 @@ export const MediaDetailsModalHeader: React.FC = ( - + {isOnWatchListLoading ? ( + + ) : isOnWatchList ? ( + + ) : ( + + )} @@ -189,8 +222,9 @@ export const MediaDetailsModalHeader: React.FC = ( - )} + ) + } {SnackbarComponent} - + ); };