Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions backend/controllers/currently_watching_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions backend/daos/currently_watching_dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ func (dao *CurrentlyWatchingDao) GetCurrentlyWatchingById(userID uint, mediaId u
return &currentlyWatching, 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(&currentlyWatching).Error; err != nil {
return nil, err
}

return &currentlyWatching, nil
}

func (dao *CurrentlyWatchingDao) GetCurrentlyWatchingByUserId(userID uint, includeDeleted bool) ([]*db.CurrentlyWatching, error) {
databaseInstance := database.GetInstance()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions backend/routes/api/v1/currently_watching_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions backend/services/currently_watching_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/api/services/currentlyWatching.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ export const updateCurrentlyWatching = async (data: CurrentlyWatching) => {
}
}

export const getIsOnWatchList = async (userID: number, tmdbID: number, includeDeleted = false): Promise<boolean> => {
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<CurrentlyWatching[]> => {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -25,7 +25,9 @@ export const MediaDetailsModalHeader: React.FC<MediaDetailsModalHeaderProps> = (
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<boolean>(false);
const [isOnWatchListLoading, setIsOnWatchListLoading] = useState<boolean>(true);

// Define styles as a JSON object
const styles = {
Expand Down Expand Up @@ -88,6 +90,22 @@ export const MediaDetailsModalHeader: React.FC<MediaDetailsModalHeaderProps> = (
}
};

//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"
Expand Down Expand Up @@ -116,6 +134,7 @@ export const MediaDetailsModalHeader: React.FC<MediaDetailsModalHeaderProps> = (
try {
await onAddToList(media, user, currentEpisode?.SeasonNumber, currentEpisode?.EpisodeNumber)
showSnackbar("Successfully added to watchlist")
setIsOnWatchList(true)
} catch (error) {
showSnackbar("Error added to watchlist")
}
Expand Down Expand Up @@ -174,10 +193,24 @@ export const MediaDetailsModalHeader: React.FC<MediaDetailsModalHeaderProps> = (
<Tooltip title={t('dictionary.addToMyList')} arrow>
<IconButton
onClick={onAdd}
sx={styles.roundButton}
sx={{
...styles.roundButton,
'&.Mui-disabled': {
backgroundColor: "rgba(255, 255, 255, 0.3)",
},
}}
size={isMobile ? "small" : "medium"}
disabled={isOnWatchListLoading || isOnWatchList} // Disable button while loading
disableRipple
disableFocusRipple
>
<Add />
{isOnWatchListLoading ? (
<CircularProgress size={24} />
) : isOnWatchList ? (
<Check />
) : (
<Add />
)}
</IconButton>
</Tooltip>
<Tooltip title={t('dictonary.rate')} arrow>
Expand All @@ -189,8 +222,9 @@ export const MediaDetailsModalHeader: React.FC<MediaDetailsModalHeaderProps> = (
</IconButton>
</Tooltip>
</Box>
)}
)
}
{SnackbarComponent}
</Box>
</Box >
);
};