diff --git a/sql/migration/0001_initial.up.sql b/sql/migration/0001_initial.up.sql index c9d5d62..c513a93 100644 --- a/sql/migration/0001_initial.up.sql +++ b/sql/migration/0001_initial.up.sql @@ -66,12 +66,12 @@ CREATE UNIQUE INDEX ON episodes (show_imdb_id, season, episode); CREATE TRIGGER update_episodes_updated_at BEFORE UPDATE ON episodes FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); CREATE TABLE shows_tracked ( - show_imdb_id text NOT NULL REFERENCES shows (imdb_id) ON DELETE CASCADE, + imdb_id text NOT NULL REFERENCES shows (imdb_id) ON DELETE CASCADE, user_id uuid NOT NULL REFERENCES users (id) ON DELETE CASCADE, season smallint NOT NULL, episode smallint NOT NULL ); -CREATE INDEX ON shows_tracked (show_imdb_id, user_id); +CREATE UNIQUE INDEX ON shows_tracked (imdb_id, user_id); CREATE INDEX ON shows_tracked (user_id); CREATE TABLE movies ( @@ -92,3 +92,10 @@ CREATE TABLE movies ( ); CREATE INDEX ON movies (imdb_id); CREATE TRIGGER update_movies_updated_at BEFORE UPDATE ON movies FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); + +CREATE TABLE movies_tracked ( + imdb_id text NOT NULL REFERENCES movies (imdb_id) ON DELETE CASCADE, + user_id uuid NOT NULL REFERENCES users (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX ON movies_tracked (imdb_id, user_id); +CREATE INDEX ON movies_tracked (user_id); diff --git a/src/internal/external_medias/handlers.go b/src/internal/external_medias/handlers.go index 6ad4674..83a55ed 100644 --- a/src/internal/external_medias/handlers.go +++ b/src/internal/external_medias/handlers.go @@ -108,7 +108,7 @@ func GetMovies(env *web.Env, user *users.User, source string, category string, f movieList := []*movies.Movie{} for _, id := range movieIds { movie := movies.New(id) - err := movie.GetDetails(env, force) + err := movie.GetDetails(env, user, force) if err != nil { env.Log.Errorf("error while getting movie details : %s", err) continue @@ -131,7 +131,7 @@ func GetMovies(env *web.Env, user *users.User, source string, category string, f } // GetShows get some shows -func GetShows(env *web.Env, source string, category string, force bool) ([]*shows.Show, error) { +func GetShows(env *web.Env, user *users.User, source string, category string, force bool) ([]*shows.Show, error) { showIds, err := GetMediaIDs(env, "show", source, category, force) if err != nil { return nil, err @@ -140,7 +140,7 @@ func GetShows(env *web.Env, source string, category string, force bool) ([]*show showList := []*shows.Show{} for _, id := range showIds { show := shows.New(id) - err := show.GetDetails(env, force) + err := show.GetDetails(env, user, force) if err != nil { env.Log.Errorf("error while getting show details : %s", err) continue @@ -208,8 +208,14 @@ func ExploreShows(env *web.Env, w http.ResponseWriter, r *http.Request) error { category = "popular" } + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user")) + } + // Get the medias without trying to refresh them - shows, err := GetShows(env, source, category, false) + shows, err := GetShows(env, user, source, category, false) if err != nil { return env.RenderError(w, err) } @@ -244,7 +250,7 @@ func Refresh(env *web.Env, w http.ResponseWriter, r *http.Request) error { v := auth.GetCurrentUser(r, env.Log) user, ok := v.(*users.User) if !ok { - return fmt.Errorf("invalid user type") + return env.RenderError(w, fmt.Errorf("invalid user type")) } // We'll refresh the medias for each sources @@ -273,11 +279,17 @@ func RefreshShows(env *web.Env, w http.ResponseWriter, r *http.Request) error { category = "popular" } + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, fmt.Errorf("invalid user type")) + } + // We'll refresh the medias for each sources for _, source := range ShowMediaSources { env.Log.Debugf("refreshing %s", source) // GetMedias and refresh them - _, err := GetShows(env, source, category, true) + _, err := GetShows(env, user, source, category, true) if err != nil { return env.RenderError(w, err) } diff --git a/src/internal/movies/handlers.go b/src/internal/movies/handlers.go index 982fb88..5cda431 100644 --- a/src/internal/movies/handlers.go +++ b/src/internal/movies/handlers.go @@ -106,7 +106,7 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error { for _, m := range movies { m.Detailers = []polochon.Detailer{detailer} - err := m.GetDetails(env, false) + err := m.GetDetails(env, user, false) if err != nil { env.Log.Error(err) } @@ -120,8 +120,14 @@ func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) err vars := mux.Vars(r) id := vars["id"] + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return fmt.Errorf("invalid user type") + } + m := New(id) - if err := m.GetDetails(env, true); err != nil { + if err := m.GetDetails(env, user, true); err != nil { return err } @@ -170,7 +176,7 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error { movieList := []*Movie{} for _, m := range movies { movie := New(m.ImdbID) - err := movie.GetDetails(env, false) + err := movie.GetDetails(env, user, false) if err != nil { env.Log.Errorf("error while getting movie details : %s", err) continue @@ -207,18 +213,18 @@ func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { v := auth.GetCurrentUser(r, env.Log) user, ok := v.(*users.User) if !ok { - return fmt.Errorf("invalid user type") + return env.RenderError(w, errors.New("invalid user type")) } var polochonConfig config.UserPolochon err := user.GetConfig("polochon", &polochonConfig) if err != nil { - return err + return env.RenderError(w, err) } client, err := papi.New(polochonConfig.URL) if err != nil { - return err + return env.RenderError(w, err) } if polochonConfig.Token != "" { @@ -227,3 +233,57 @@ func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { return client.Delete(&papi.Movie{ImdbID: id}) } + +// AddToWishlist adds a movie to the user's wishlist +func AddToWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + id := vars["id"] + + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) + } + + m := New(id) + if err := m.AddToWishlist(env, user); err != nil { + return env.RenderError(w, err) + } + + return env.RenderOK(w, "Movie added to wishlist") +} + +// DeleteFromWishlist deletes a movie from the user's wishlist +func DeleteFromWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + id := vars["id"] + + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) + } + + m := New(id) + if err := m.DeleteFromWishlist(env, user); err != nil { + return env.RenderError(w, err) + } + + return env.RenderOK(w, "Movie deleted from wishlist") +} + +// GetWishlistHandler returns the wishlisted movies of a user +func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) + } + + movies, err := GetWishlist(env, user) + if err != nil { + return env.RenderError(w, err) + } + + return env.RenderJSON(w, movies) +} diff --git a/src/internal/movies/movies.go b/src/internal/movies/movies.go index e08925c..0af223f 100644 --- a/src/internal/movies/movies.go +++ b/src/internal/movies/movies.go @@ -14,6 +14,7 @@ import ( "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/torrents" + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" ) @@ -32,12 +33,48 @@ const ( RETURNING id;` getMovieQueryByImdbID = ` - SELECT * - FROM movies WHERE imdb_id=$1;` + SELECT + movies.id, + movies.title, + movies.imdb_id, + movies.tmdb_id, + movies.votes, + movies.rating, + movies.plot, + movies.year, + movies.original_title, + movies.runtime, + movies.genres, + movies.sort_title, + movies.tagline, + movies.created_at, + movies.updated_at, + movies_tracked.user_id + FROM movies LEFT JOIN movies_tracked + ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$2 + WHERE movies.imdb_id=$1;` getMovieQueryByID = ` - SELECT * - FROM movies WHERE id=$1;` + SELECT + movies.id, + movies.title, + movies.imdb_id, + movies.tmdb_id, + movies.votes, + movies.rating, + movies.plot, + movies.year, + movies.original_title, + movies.runtime, + movies.genres, + movies.sort_title, + movies.tagline, + movies.created_at, + movies.updated_at, + movies_tracked.user_id + FROM movies LEFT JOIN movies_tracked + ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$2 + WHERE movies.id=$1;` deleteMovieQuery = `DELETE FROM movies WHERE id=$1;` ) @@ -47,6 +84,7 @@ type MovieDB struct { ID string `db:"id"` ImdbID string `db:"imdb_id"` TmdbID int `db:"tmdb_id"` + UserID *string `db:"user_id"` Title string `db:"title"` OriginalTitle string `db:"original_title"` SortTitle string `db:"sort_title"` @@ -104,12 +142,16 @@ func (m *Movie) FillFromDB(mDB *MovieDB) { m.Genres = mDB.Genres m.SortTitle = mDB.SortTitle m.Tagline = mDB.Tagline + if mDB.UserID != nil { + m.Wishlisted = true + } } // Movie represents a movie type Movie struct { sqly.BaseModel polochon.Movie + Wishlisted bool `json:"wishlisted"` PolochonURL string `json:"polochon_url"` PosterURL string `json:"poster_url"` } @@ -124,13 +166,13 @@ func New(imdbID string) *Movie { } // Get returns show details in database from id or imdbid or an error -func (m *Movie) Get(env *web.Env) error { +func (m *Movie) Get(env *web.Env, user *users.User) error { var mDB MovieDB var err error if m.ID != "" { - err = env.Database.QueryRowx(getMovieQueryByID, m.ID).StructScan(&mDB) + err = env.Database.QueryRowx(getMovieQueryByID, m.ID, user.ID).StructScan(&mDB) } else if m.ImdbID != "" { - err = env.Database.QueryRowx(getMovieQueryByImdbID, m.ImdbID).StructScan(&mDB) + err = env.Database.QueryRowx(getMovieQueryByImdbID, m.ImdbID, user.ID).StructScan(&mDB) } else { err = fmt.Errorf("Can't get movie details, you have to specify an ID or ImdbID") } @@ -151,7 +193,7 @@ func (m *Movie) Get(env *web.Env) error { // // If force is used, the detailer will be used even if the movie is found in // database -func (m *Movie) GetDetails(env *web.Env, force bool) error { +func (m *Movie) GetDetails(env *web.Env, user *users.User, force bool) error { log := env.Log.WithFields(logrus.Fields{ "imdb_id": m.ImdbID, "function": "movies.GetDetails", @@ -163,7 +205,7 @@ func (m *Movie) GetDetails(env *web.Env, force bool) error { } var err error - err = m.Get(env) + err = m.Get(env, user) switch err { case nil: log.Debug("movie found in database") diff --git a/src/internal/movies/wishlist.go b/src/internal/movies/wishlist.go new file mode 100644 index 0000000..dd6c3e5 --- /dev/null +++ b/src/internal/movies/wishlist.go @@ -0,0 +1,83 @@ +package movies + +import ( + "fmt" + + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users" + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" +) + +const ( + upsertWishlistQuery = ` + INSERT INTO movies_tracked (imdb_id, user_id) + VALUES ($1, $2) + ON CONFLICT (imdb_id, user_id) + DO UPDATE + SET imdb_id=$1, user_id=$2;` + + getWishlistQueryByUserID = ` + SELECT + movies.id, + movies.title, + movies.imdb_id, + movies.tmdb_id, + movies.votes, + movies.rating, + movies.plot, + movies.year, + movies.original_title, + movies.runtime, + movies.genres, + movies.sort_title, + movies.tagline, + movies.created_at, + movies.updated_at, + movies_tracked.user_id + FROM movies INNER JOIN movies_tracked + ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$1;` + + deleteWishlistedQueryByID = `DELETE FROM movies_tracked WHERE imdb_id=$1 AND user_id=$2;` +) + +// AddToWishlist Adds a movie to a user's wishlist +func (m *Movie) AddToWishlist(env *web.Env, user *users.User) error { + _, err := env.Database.Exec(upsertWishlistQuery, m.ImdbID, user.ID) + if err != nil { + return err + } + return nil +} + +// DeleteFromWishlist deletes a movie from a user's wishlist +func (m *Movie) DeleteFromWishlist(env *web.Env, user *users.User) error { + r, err := env.Database.Exec(deleteWishlistedQueryByID, m.ImdbID, user.ID) + if err != nil { + return err + } + count, _ := r.RowsAffected() + if count != 1 { + return fmt.Errorf("Unexpected number of row deleted: %d", count) + } + return nil +} + +// GetWishlist returns a list of movies wishlisted by user +func GetWishlist(env *web.Env, user *users.User) ([]*Movie, error) { + var moviesDB = []*MovieDB{} + err := env.Database.Select(&moviesDB, getWishlistQueryByUserID, user.ID) + if err != nil { + return nil, err + } + + var movies []*Movie + for _, movieDB := range moviesDB { + movie := New(movieDB.ImdbID) + // Set the poster url + movie.PosterURL = movie.GetPosterURL(env) + + movie.FillFromDB(movieDB) + movies = append(movies, movie) + } + + return movies, nil +} diff --git a/src/internal/shows/handlers.go b/src/internal/shows/handlers.go index 29d17b8..3381b5e 100644 --- a/src/internal/shows/handlers.go +++ b/src/internal/shows/handlers.go @@ -7,20 +7,38 @@ import ( "github.com/gorilla/mux" polochon "github.com/odwrtw/polochon/lib" + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth" + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" ) -// GetDetailsHandler retrieves details for a movie +// GetDetailsHandler retrieves details of a show func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { + return DetailsHandler(env, w, r, false) +} + +// RefreahDetailsHandler refresh details of a show +func RefreshDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { + return DetailsHandler(env, w, r, true) +} + +// DetailsHandler handles details of a show +func DetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request, force bool) error { vars := mux.Vars(r) id := vars["id"] - s := New(id) - if err := s.GetDetails(env, false); err != nil { - return err + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) } - if err := s.GetEpisodes(env); err != nil { - return err + + s := New(id) + if err := s.GetDetails(env, user, force); err != nil { + return env.RenderError(w, err) + } + if err := s.GetEpisodes(env, force); err != nil { + return env.RenderError(w, err) } return env.RenderJSON(w, s) @@ -39,6 +57,12 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error { return env.RenderError(w, errors.New("no given key")) } + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) + } + var shows []*polochon.Show searchers := env.Config.ShowSearchers for _, searcher := range searchers { @@ -54,7 +78,7 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error { showList := []*Show{} for _, s := range shows { show := New(s.ImdbID) - err := show.GetDetails(env, false) + err := show.GetDetails(env, user, false) if err != nil { env.Log.Errorf("error while getting show details : %s", err) continue @@ -64,3 +88,69 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error { return env.RenderJSON(w, showList) } + +// AddToWishlist adds a show to the user's wishlist +func AddToWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + id := vars["id"] + + var data struct { + Season int `json:"season"` + Episode int `json:"episode"` + } + if r.ContentLength > 0 { + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { + return env.RenderError(w, errors.New("failed to get POST data season and episode "+err.Error())) + } + } + + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) + } + + s := New(id) + if err := s.AddToWishlist(env, user, data.Season, data.Episode); err != nil { + env.Log.Warnf("Error while adding to db : %s", err) + return env.RenderError(w, err) + } + + return env.RenderOK(w, "Show added to wishlist") +} + +// DeleteFromWishlist deletes a show from the user's wishlist +func DeleteFromWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + id := vars["id"] + + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) + } + + s := New(id) + if err := s.DeleteFromWishlist(env, user); err != nil { + env.Log.Warnf("Error while deleting to db : %s", err) + return env.RenderError(w, err) + } + + return env.RenderOK(w, "Show deleted from wishlist") +} + +// GetWishlistHandler returns the tracked shows of a user +func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return env.RenderError(w, errors.New("invalid user type")) + } + + shows, err := GetWishlist(env, user) + if err != nil { + return env.RenderError(w, err) + } + + return env.RenderJSON(w, shows) +} diff --git a/src/internal/shows/shows.go b/src/internal/shows/shows.go index a3503b4..5123f28 100644 --- a/src/internal/shows/shows.go +++ b/src/internal/shows/shows.go @@ -46,11 +46,12 @@ const ( shows.tvdb_id, shows.year, shows.first_aired, - shows.created_at, + shows_tracked.season, + shows_tracked.episode, shows.updated_at, - COALESCE(shows_tracked.season,0), - COALESCE(shows_tracked.episode,0) AS trackedepisode - FROM shows LEFT JOIN shows_tracked ON shows.id=shows_tracked.show_imdb_id AND shows_tracked.user_id=$2 + shows.created_at + FROM shows LEFT JOIN shows_tracked + ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2 WHERE shows.imdb_id=$1;` getShowWithUserQueryByID = ` @@ -63,11 +64,11 @@ const ( shows.tvdb_id, shows.year, shows.first_aired, - shows.created_at, + shows_tracked.season, + shows_tracked.episode, shows.updated_at, - COALESCE(shows_tracked.season,0), - COALESCE(shows_tracked.episode,0) - FROM shows LEFT JOIN shows_tracked ON shows.id=shows_tracked.show_imdb_id AND shows_tracked.user_id=$2 + shows.created_at + FROM shows LEFT JOIN shows_tracked ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2 WHERE shows.id=$1;` ) @@ -81,8 +82,8 @@ type Show struct { sqly.BaseModel polochon.Show Episodes []*Episode `json:"episodes"` - TrackedSeason int `json:"tracked_season"` - TrackedEpisode int `json:"tracked_episode"` + TrackedSeason *int `json:"tracked_season"` + TrackedEpisode *int `json:"tracked_episode"` BannerURL string `json:"banner_url"` FanartURL string `json:"fanart_url"` PosterURL string `json:"poster_url"` @@ -90,16 +91,18 @@ type Show struct { // ShowDB represents the Show in the DB type ShowDB struct { - ID string `db:"id"` - ImdbID string `db:"imdb_id"` - TvdbID int `db:"tvdb_id"` - Title string `db:"title"` - Rating float32 `db:"rating"` - Plot string `db:"plot"` - Year int `db:"year"` - FirstAired time.Time `db:"first_aired"` - Created time.Time `db:"created_at"` - Updated time.Time `db:"updated_at"` + ID string `db:"id"` + ImdbID string `db:"imdb_id"` + TvdbID int `db:"tvdb_id"` + TrackedSeason *int `db:"season"` + TrackedEpisode *int `db:"episode"` + Title string `db:"title"` + Rating float32 `db:"rating"` + Plot string `db:"plot"` + Year int `db:"year"` + FirstAired time.Time `db:"first_aired"` + Created time.Time `db:"created_at"` + Updated time.Time `db:"updated_at"` } // NewShowDB returns a Show ready to be put in DB from a @@ -131,6 +134,8 @@ func (s *Show) FillFromDB(sDB *ShowDB) { s.FirstAired = &sDB.FirstAired s.Created = sDB.Created s.Updated = sDB.Updated + s.TrackedSeason = sDB.TrackedSeason + s.TrackedEpisode = sDB.TrackedEpisode } // New returns a new Show with a polochon ShowConfig @@ -142,21 +147,20 @@ func New(imdbID string) *Show { } } -// Get returns show details in database from id or imdbid or an error -func (s *Show) Get(env *web.Env) error { - var sDB ShowDB +// Get returns a show with user info like tracked +func (s *Show) Get(env *web.Env, user *users.User) error { var err error + var sDB ShowDB if s.ID != "" { - err = env.Database.QueryRowx(getShowQueryByID, s.ID).StructScan(&sDB) + err = env.Database.QueryRowx(getShowWithUserQueryByID, s.ID, user.ID).StructScan(&sDB) } else if s.ImdbID != "" { - err = env.Database.QueryRowx(getShowQueryByImdbID, s.ImdbID).StructScan(&sDB) + err = env.Database.QueryRowx(getShowWithUserQueryByImdbID, s.ImdbID, user.ID).StructScan(&sDB) } else { err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID") } if err != nil { return err } - // Set the poster url s.PosterURL = s.GetPosterURL(env) @@ -164,29 +168,10 @@ func (s *Show) Get(env *web.Env) error { return nil } -// GetAsUser returns a show with user info like tracked -func (s *Show) GetAsUser(db *sqlx.DB, user *users.User) error { - var err error - if s.ID != "" { - err = db.QueryRowx(getShowWithUserQueryByID, s.ID, user.ID).StructScan(s) - } else if s.ImdbID != "" { - err = db.QueryRowx(getShowWithUserQueryByImdbID, s.ImdbID, user.ID).StructScan(s) - } else { - err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID") - } - if err != nil { - if err.Error() == "sql: no rows in result set" { - return ErrNotFound - } - return err - } - return nil -} - // GetDetails retrieves details for the show, first try to // get info from db, if not exists, use polochon.Detailer // and save informations in the database for future use -func (s *Show) GetDetails(env *web.Env, force bool) error { +func (s *Show) GetDetails(env *web.Env, user *users.User, force bool) error { log := env.Log.WithFields(logrus.Fields{ "imdb_id": s.ImdbID, "function": "shows.GetDetails", @@ -198,7 +183,7 @@ func (s *Show) GetDetails(env *web.Env, force bool) error { } var err error - err = s.Get(env) + err = s.Get(env, user) switch err { case nil: log.Debug("show found in database") @@ -282,44 +267,6 @@ func (s *Show) imgURL(env *web.Env, imgType string) string { return fmt.Sprintf("img/shows/%s-%s.jpg", s.ImdbID, imgType) } -// GetDetailsAsUser like GetDetails but with User context -func (s *Show) GetDetailsAsUser(db *sqlx.DB, user *users.User, log *logrus.Entry) error { - var err error - err = s.GetAsUser(db, user) - if err == nil { - // found ok - return nil - } - if err != ErrNotFound { - // Unexpected error - return err - } - err = s.Show.GetDetails(log) - if err != nil { - return err - } - - s.Episodes = []*Episode{} - for _, pe := range s.Show.Episodes { - s.Episodes = append(s.Episodes, &Episode{ShowEpisode: *pe}) - } - - err = s.Upsert(db) - if err != nil { - return err - } - return nil -} - -// IsTracked returns true if the show is tracked use this function -// after retrieve the show with GetAsUser or other *AsUser functions -func (s *Show) IsTracked() bool { - if s.TrackedSeason != 0 && s.TrackedEpisode != 0 { - return true - } - return false -} - // Upsert a show in the database func (s *Show) Upsert(db *sqlx.DB) error { sDB := NewShowDB(s) @@ -357,7 +304,7 @@ func (s *Show) Delete(db *sqlx.DB) error { } // GetEpisodes from database -func (s *Show) GetEpisodes(env *web.Env) error { +func (s *Show) GetEpisodes(env *web.Env, force bool) error { // We retrive episode's info from database populate the s.Episodes member var episodesDB = []*EpisodeDB{} err := env.Database.Select(&episodesDB, getEpisodesQuery, s.ImdbID) @@ -372,7 +319,7 @@ func (s *Show) GetEpisodes(env *web.Env) error { episode := NewEpisode() episode.FillFromDB(episodeDB) s.Episodes = append(s.Episodes, episode) - err = episode.GetTorrents(env, false) + err = episode.GetTorrents(env, force) if err != nil { env.Log.Debugf("error while getting episode torrent: %q", err) } diff --git a/src/internal/shows/wishlist.go b/src/internal/shows/wishlist.go new file mode 100644 index 0000000..b8db094 --- /dev/null +++ b/src/internal/shows/wishlist.go @@ -0,0 +1,87 @@ +package shows + +import ( + "fmt" + + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users" + "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" +) + +const ( + upsertWishlistQuery = ` + INSERT INTO shows_tracked (imdb_id, user_id, season, episode) + VALUES ($1, $2, $3, $4) + ON CONFLICT (imdb_id, user_id) + DO UPDATE + SET imdb_id=$1, user_id=$2, season=$3, episode=$4;` + + getWishlistQueryByUserID = ` + SELECT + shows.id, + shows.imdb_id, + shows.title, + shows.rating, + shows.plot, + shows.tvdb_id, + shows.year, + shows.first_aired, + shows_tracked.season, + shows_tracked.episode, + shows.updated_at, + shows.created_at + FROM shows INNER JOIN shows_tracked + ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$1;` + + deleteWishlistedQueryByID = `DELETE FROM shows_tracked WHERE imdb_id=$1 AND user_id=$2;` +) + +// AddToWishlist Adds a show to a user's wishlist +func (s *Show) AddToWishlist(env *web.Env, user *users.User, season, episode int) error { + _, err := env.Database.Exec(upsertWishlistQuery, s.ImdbID, user.ID, season, episode) + if err != nil { + return err + } + return nil +} + +// IsTracked returns true if the show is tracked use this function +// after retrieve the show with GetAsUser or other *AsUser functions +func (s *Show) IsTracked() bool { + if s.TrackedSeason != nil && s.TrackedEpisode != nil { + return true + } + return false +} + +// DeleteFromWishlist deletes a show from a user's wishlist +func (s *Show) DeleteFromWishlist(env *web.Env, user *users.User) error { + r, err := env.Database.Exec(deleteWishlistedQueryByID, s.ImdbID, user.ID) + if err != nil { + return err + } + count, _ := r.RowsAffected() + if count != 1 { + return fmt.Errorf("Unexpected number of row deleted: %d", count) + } + return nil +} + +// GetWishlist returns a list of shows tracked by user +func GetWishlist(env *web.Env, user *users.User) ([]*Show, error) { + var showsDB = []*ShowDB{} + err := env.Database.Select(&showsDB, getWishlistQueryByUserID, user.ID) + if err != nil { + return nil, err + } + + var shows []*Show + for _, showDB := range showsDB { + // Set the poster url + show := New(showDB.ImdbID) + show.PosterURL = show.GetPosterURL(env) + show.FillFromDB(showDB) + shows = append(shows, show) + } + + return shows, nil +} diff --git a/src/internal/sqly/sqly.go b/src/internal/sqly/sqly.go index faee992..b83f42a 100644 --- a/src/internal/sqly/sqly.go +++ b/src/internal/sqly/sqly.go @@ -20,9 +20,9 @@ func init() { // BaseModel have to be embeded in all your struct which reflect a table type BaseModel struct { - ID string - Updated time.Time `db:"updated_at"` - Created time.Time `db:"created_at"` + ID string `json:"id"` + Updated time.Time `db:"updated_at" json:"updated_at"` + Created time.Time `db:"created_at" json:"created_at"` } // RunWithLastestMigration runs your test with database migration set to the lastest diff --git a/src/internal/web/render.go b/src/internal/web/render.go index 49aa863..e81b5ac 100644 --- a/src/internal/web/render.go +++ b/src/internal/web/render.go @@ -4,6 +4,7 @@ import "net/http" // RenderError renders an error func (e *Env) RenderError(w http.ResponseWriter, err error) error { + e.Log.Warn(err) return e.render(w, "error", err.Error()) } diff --git a/src/main.go b/src/main.go index f0d8982..8685491 100644 --- a/src/main.go +++ b/src/main.go @@ -85,11 +85,21 @@ func main() { // env.Handle("/shows/polochon", shows.FromPolochon).WithRole(users.UserRole) env.Handle("/shows/{id:tt[0-9]+}", shows.GetDetailsHandler).WithRole(users.UserRole).Methods("GET") + env.Handle("/shows/{id:tt[0-9]+}/refresh", shows.RefreshDetailsHandler).WithRole(users.UserRole).Methods("POST") env.Handle("/shows/refresh", extmedias.RefreshShows).WithRole(users.UserRole).Methods("POST") env.Handle("/shows/explore", extmedias.ExploreShows).WithRole(users.UserRole).Methods("GET") env.Handle("/shows/search", shows.SearchShow).WithRole(users.UserRole).Methods("POST") + env.Handle("/torrents", torrents.DownloadHandler).WithRole(users.UserRole).Methods("POST") + env.Handle("/wishlist/shows", shows.GetWishlistHandler).WithRole(users.UserRole).Methods("GET") + env.Handle("/wishlist/shows/{id:tt[0-9]+}", shows.AddToWishlist).WithRole(users.UserRole).Methods("POST") + env.Handle("/wishlist/shows/{id:tt[0-9]+}", shows.DeleteFromWishlist).WithRole(users.UserRole).Methods("DELETE") + + env.Handle("/wishlist/movies", movies.GetWishlistHandler).WithRole(users.UserRole).Methods("GET") + env.Handle("/wishlist/movies/{id:tt[0-9]+}", movies.AddToWishlist).WithRole(users.UserRole).Methods("POST") + env.Handle("/wishlist/movies/{id:tt[0-9]+}", movies.DeleteFromWishlist).WithRole(users.UserRole).Methods("DELETE") + n := negroni.Classic() n.Use(authMiddleware) n.Use(negroni.NewStatic(http.Dir(cf.PublicDir)))