Merge branch 'wishlist' into 'master'

Add Wishlist to the party

See merge request !33
This commit is contained in:
Grégoire Delattre 2017-01-23 11:56:33 +00:00
commit 879bc2a412
11 changed files with 459 additions and 120 deletions

View File

@ -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 TRIGGER update_episodes_updated_at BEFORE UPDATE ON episodes FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
CREATE TABLE shows_tracked ( 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, user_id uuid NOT NULL REFERENCES users (id) ON DELETE CASCADE,
season smallint NOT NULL, season smallint NOT NULL,
episode 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 INDEX ON shows_tracked (user_id);
CREATE TABLE movies ( CREATE TABLE movies (
@ -92,3 +92,10 @@ CREATE TABLE movies (
); );
CREATE INDEX ON movies (imdb_id); 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 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);

View File

@ -108,7 +108,7 @@ func GetMovies(env *web.Env, user *users.User, source string, category string, f
movieList := []*movies.Movie{} movieList := []*movies.Movie{}
for _, id := range movieIds { for _, id := range movieIds {
movie := movies.New(id) movie := movies.New(id)
err := movie.GetDetails(env, force) err := movie.GetDetails(env, user, force)
if err != nil { if err != nil {
env.Log.Errorf("error while getting movie details : %s", err) env.Log.Errorf("error while getting movie details : %s", err)
continue continue
@ -131,7 +131,7 @@ func GetMovies(env *web.Env, user *users.User, source string, category string, f
} }
// GetShows get some shows // 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) showIds, err := GetMediaIDs(env, "show", source, category, force)
if err != nil { if err != nil {
return nil, err return nil, err
@ -140,7 +140,7 @@ func GetShows(env *web.Env, source string, category string, force bool) ([]*show
showList := []*shows.Show{} showList := []*shows.Show{}
for _, id := range showIds { for _, id := range showIds {
show := shows.New(id) show := shows.New(id)
err := show.GetDetails(env, force) err := show.GetDetails(env, user, force)
if err != nil { if err != nil {
env.Log.Errorf("error while getting show details : %s", err) env.Log.Errorf("error while getting show details : %s", err)
continue continue
@ -208,8 +208,14 @@ func ExploreShows(env *web.Env, w http.ResponseWriter, r *http.Request) error {
category = "popular" 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 // 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 { if err != nil {
return env.RenderError(w, err) 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) v := auth.GetCurrentUser(r, env.Log)
user, ok := v.(*users.User) user, ok := v.(*users.User)
if !ok { 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 // 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" 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 // We'll refresh the medias for each sources
for _, source := range ShowMediaSources { for _, source := range ShowMediaSources {
env.Log.Debugf("refreshing %s", source) env.Log.Debugf("refreshing %s", source)
// GetMedias and refresh them // GetMedias and refresh them
_, err := GetShows(env, source, category, true) _, err := GetShows(env, user, source, category, true)
if err != nil { if err != nil {
return env.RenderError(w, err) return env.RenderError(w, err)
} }

View File

@ -106,7 +106,7 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
for _, m := range movies { for _, m := range movies {
m.Detailers = []polochon.Detailer{detailer} m.Detailers = []polochon.Detailer{detailer}
err := m.GetDetails(env, false) err := m.GetDetails(env, user, false)
if err != nil { if err != nil {
env.Log.Error(err) env.Log.Error(err)
} }
@ -120,8 +120,14 @@ func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) err
vars := mux.Vars(r) vars := mux.Vars(r)
id := vars["id"] 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) m := New(id)
if err := m.GetDetails(env, true); err != nil { if err := m.GetDetails(env, user, true); err != nil {
return err return err
} }
@ -170,7 +176,7 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error {
movieList := []*Movie{} movieList := []*Movie{}
for _, m := range movies { for _, m := range movies {
movie := New(m.ImdbID) movie := New(m.ImdbID)
err := movie.GetDetails(env, false) err := movie.GetDetails(env, user, false)
if err != nil { if err != nil {
env.Log.Errorf("error while getting movie details : %s", err) env.Log.Errorf("error while getting movie details : %s", err)
continue continue
@ -207,18 +213,18 @@ func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
v := auth.GetCurrentUser(r, env.Log) v := auth.GetCurrentUser(r, env.Log)
user, ok := v.(*users.User) user, ok := v.(*users.User)
if !ok { if !ok {
return fmt.Errorf("invalid user type") return env.RenderError(w, errors.New("invalid user type"))
} }
var polochonConfig config.UserPolochon var polochonConfig config.UserPolochon
err := user.GetConfig("polochon", &polochonConfig) err := user.GetConfig("polochon", &polochonConfig)
if err != nil { if err != nil {
return err return env.RenderError(w, err)
} }
client, err := papi.New(polochonConfig.URL) client, err := papi.New(polochonConfig.URL)
if err != nil { if err != nil {
return err return env.RenderError(w, err)
} }
if polochonConfig.Token != "" { 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}) 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)
}

View File

@ -14,6 +14,7 @@ import (
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly" "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/torrents"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
) )
@ -32,12 +33,48 @@ const (
RETURNING id;` RETURNING id;`
getMovieQueryByImdbID = ` getMovieQueryByImdbID = `
SELECT * SELECT
FROM movies WHERE imdb_id=$1;` 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 = ` getMovieQueryByID = `
SELECT * SELECT
FROM movies WHERE id=$1;` 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;` deleteMovieQuery = `DELETE FROM movies WHERE id=$1;`
) )
@ -47,6 +84,7 @@ type MovieDB struct {
ID string `db:"id"` ID string `db:"id"`
ImdbID string `db:"imdb_id"` ImdbID string `db:"imdb_id"`
TmdbID int `db:"tmdb_id"` TmdbID int `db:"tmdb_id"`
UserID *string `db:"user_id"`
Title string `db:"title"` Title string `db:"title"`
OriginalTitle string `db:"original_title"` OriginalTitle string `db:"original_title"`
SortTitle string `db:"sort_title"` SortTitle string `db:"sort_title"`
@ -104,12 +142,16 @@ func (m *Movie) FillFromDB(mDB *MovieDB) {
m.Genres = mDB.Genres m.Genres = mDB.Genres
m.SortTitle = mDB.SortTitle m.SortTitle = mDB.SortTitle
m.Tagline = mDB.Tagline m.Tagline = mDB.Tagline
if mDB.UserID != nil {
m.Wishlisted = true
}
} }
// Movie represents a movie // Movie represents a movie
type Movie struct { type Movie struct {
sqly.BaseModel sqly.BaseModel
polochon.Movie polochon.Movie
Wishlisted bool `json:"wishlisted"`
PolochonURL string `json:"polochon_url"` PolochonURL string `json:"polochon_url"`
PosterURL string `json:"poster_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 // 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 mDB MovieDB
var err error var err error
if m.ID != "" { 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 != "" { } 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 { } else {
err = fmt.Errorf("Can't get movie details, you have to specify an ID or ImdbID") 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 // If force is used, the detailer will be used even if the movie is found in
// database // 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{ log := env.Log.WithFields(logrus.Fields{
"imdb_id": m.ImdbID, "imdb_id": m.ImdbID,
"function": "movies.GetDetails", "function": "movies.GetDetails",
@ -163,7 +205,7 @@ func (m *Movie) GetDetails(env *web.Env, force bool) error {
} }
var err error var err error
err = m.Get(env) err = m.Get(env, user)
switch err { switch err {
case nil: case nil:
log.Debug("movie found in database") log.Debug("movie found in database")

View File

@ -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
}

View File

@ -7,20 +7,38 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
polochon "github.com/odwrtw/polochon/lib" 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" "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 { 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) vars := mux.Vars(r)
id := vars["id"] id := vars["id"]
s := New(id) v := auth.GetCurrentUser(r, env.Log)
if err := s.GetDetails(env, false); err != nil { user, ok := v.(*users.User)
return err 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) 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")) 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 var shows []*polochon.Show
searchers := env.Config.ShowSearchers searchers := env.Config.ShowSearchers
for _, searcher := range searchers { for _, searcher := range searchers {
@ -54,7 +78,7 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error {
showList := []*Show{} showList := []*Show{}
for _, s := range shows { for _, s := range shows {
show := New(s.ImdbID) show := New(s.ImdbID)
err := show.GetDetails(env, false) err := show.GetDetails(env, user, false)
if err != nil { if err != nil {
env.Log.Errorf("error while getting show details : %s", err) env.Log.Errorf("error while getting show details : %s", err)
continue continue
@ -64,3 +88,69 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error {
return env.RenderJSON(w, showList) 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)
}

View File

@ -46,11 +46,12 @@ const (
shows.tvdb_id, shows.tvdb_id,
shows.year, shows.year,
shows.first_aired, shows.first_aired,
shows.created_at, shows_tracked.season,
shows_tracked.episode,
shows.updated_at, shows.updated_at,
COALESCE(shows_tracked.season,0), shows.created_at
COALESCE(shows_tracked.episode,0) AS trackedepisode FROM shows LEFT JOIN shows_tracked
FROM shows LEFT JOIN shows_tracked ON shows.id=shows_tracked.show_imdb_id AND shows_tracked.user_id=$2 ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2
WHERE shows.imdb_id=$1;` WHERE shows.imdb_id=$1;`
getShowWithUserQueryByID = ` getShowWithUserQueryByID = `
@ -63,11 +64,11 @@ const (
shows.tvdb_id, shows.tvdb_id,
shows.year, shows.year,
shows.first_aired, shows.first_aired,
shows.created_at, shows_tracked.season,
shows_tracked.episode,
shows.updated_at, shows.updated_at,
COALESCE(shows_tracked.season,0), shows.created_at
COALESCE(shows_tracked.episode,0) FROM shows LEFT JOIN shows_tracked ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2
FROM shows LEFT JOIN shows_tracked ON shows.id=shows_tracked.show_imdb_id AND shows_tracked.user_id=$2
WHERE shows.id=$1;` WHERE shows.id=$1;`
) )
@ -81,8 +82,8 @@ type Show struct {
sqly.BaseModel sqly.BaseModel
polochon.Show polochon.Show
Episodes []*Episode `json:"episodes"` Episodes []*Episode `json:"episodes"`
TrackedSeason int `json:"tracked_season"` TrackedSeason *int `json:"tracked_season"`
TrackedEpisode int `json:"tracked_episode"` TrackedEpisode *int `json:"tracked_episode"`
BannerURL string `json:"banner_url"` BannerURL string `json:"banner_url"`
FanartURL string `json:"fanart_url"` FanartURL string `json:"fanart_url"`
PosterURL string `json:"poster_url"` PosterURL string `json:"poster_url"`
@ -93,6 +94,8 @@ type ShowDB struct {
ID string `db:"id"` ID string `db:"id"`
ImdbID string `db:"imdb_id"` ImdbID string `db:"imdb_id"`
TvdbID int `db:"tvdb_id"` TvdbID int `db:"tvdb_id"`
TrackedSeason *int `db:"season"`
TrackedEpisode *int `db:"episode"`
Title string `db:"title"` Title string `db:"title"`
Rating float32 `db:"rating"` Rating float32 `db:"rating"`
Plot string `db:"plot"` Plot string `db:"plot"`
@ -131,6 +134,8 @@ func (s *Show) FillFromDB(sDB *ShowDB) {
s.FirstAired = &sDB.FirstAired s.FirstAired = &sDB.FirstAired
s.Created = sDB.Created s.Created = sDB.Created
s.Updated = sDB.Updated s.Updated = sDB.Updated
s.TrackedSeason = sDB.TrackedSeason
s.TrackedEpisode = sDB.TrackedEpisode
} }
// New returns a new Show with a polochon ShowConfig // 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 // Get returns a show with user info like tracked
func (s *Show) Get(env *web.Env) error { func (s *Show) Get(env *web.Env, user *users.User) error {
var sDB ShowDB
var err error var err error
var sDB ShowDB
if s.ID != "" { 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 != "" { } 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 { } else {
err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID") err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID")
} }
if err != nil { if err != nil {
return err return err
} }
// Set the poster url // Set the poster url
s.PosterURL = s.GetPosterURL(env) s.PosterURL = s.GetPosterURL(env)
@ -164,29 +168,10 @@ func (s *Show) Get(env *web.Env) error {
return nil 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 // GetDetails retrieves details for the show, first try to
// get info from db, if not exists, use polochon.Detailer // get info from db, if not exists, use polochon.Detailer
// and save informations in the database for future use // 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{ log := env.Log.WithFields(logrus.Fields{
"imdb_id": s.ImdbID, "imdb_id": s.ImdbID,
"function": "shows.GetDetails", "function": "shows.GetDetails",
@ -198,7 +183,7 @@ func (s *Show) GetDetails(env *web.Env, force bool) error {
} }
var err error var err error
err = s.Get(env) err = s.Get(env, user)
switch err { switch err {
case nil: case nil:
log.Debug("show found in database") 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) 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 // Upsert a show in the database
func (s *Show) Upsert(db *sqlx.DB) error { func (s *Show) Upsert(db *sqlx.DB) error {
sDB := NewShowDB(s) sDB := NewShowDB(s)
@ -357,7 +304,7 @@ func (s *Show) Delete(db *sqlx.DB) error {
} }
// GetEpisodes from database // 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 // We retrive episode's info from database populate the s.Episodes member
var episodesDB = []*EpisodeDB{} var episodesDB = []*EpisodeDB{}
err := env.Database.Select(&episodesDB, getEpisodesQuery, s.ImdbID) err := env.Database.Select(&episodesDB, getEpisodesQuery, s.ImdbID)
@ -372,7 +319,7 @@ func (s *Show) GetEpisodes(env *web.Env) error {
episode := NewEpisode() episode := NewEpisode()
episode.FillFromDB(episodeDB) episode.FillFromDB(episodeDB)
s.Episodes = append(s.Episodes, episode) s.Episodes = append(s.Episodes, episode)
err = episode.GetTorrents(env, false) err = episode.GetTorrents(env, force)
if err != nil { if err != nil {
env.Log.Debugf("error while getting episode torrent: %q", err) env.Log.Debugf("error while getting episode torrent: %q", err)
} }

View File

@ -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
}

View File

@ -20,9 +20,9 @@ func init() {
// BaseModel have to be embeded in all your struct which reflect a table // BaseModel have to be embeded in all your struct which reflect a table
type BaseModel struct { type BaseModel struct {
ID string ID string `json:"id"`
Updated time.Time `db:"updated_at"` Updated time.Time `db:"updated_at" json:"updated_at"`
Created time.Time `db:"created_at"` Created time.Time `db:"created_at" json:"created_at"`
} }
// RunWithLastestMigration runs your test with database migration set to the lastest // RunWithLastestMigration runs your test with database migration set to the lastest

View File

@ -4,6 +4,7 @@ import "net/http"
// RenderError renders an error // RenderError renders an error
func (e *Env) RenderError(w http.ResponseWriter, err error) error { func (e *Env) RenderError(w http.ResponseWriter, err error) error {
e.Log.Warn(err)
return e.render(w, "error", err.Error()) return e.render(w, "error", err.Error())
} }

View File

@ -85,11 +85,21 @@ func main() {
// env.Handle("/shows/polochon", shows.FromPolochon).WithRole(users.UserRole) // 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]+}", 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/refresh", extmedias.RefreshShows).WithRole(users.UserRole).Methods("POST")
env.Handle("/shows/explore", extmedias.ExploreShows).WithRole(users.UserRole).Methods("GET") env.Handle("/shows/explore", extmedias.ExploreShows).WithRole(users.UserRole).Methods("GET")
env.Handle("/shows/search", shows.SearchShow).WithRole(users.UserRole).Methods("POST") env.Handle("/shows/search", shows.SearchShow).WithRole(users.UserRole).Methods("POST")
env.Handle("/torrents", torrents.DownloadHandler).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 := negroni.Classic()
n.Use(authMiddleware) n.Use(authMiddleware)
n.Use(negroni.NewStatic(http.Dir(cf.PublicDir))) n.Use(negroni.NewStatic(http.Dir(cf.PublicDir)))