parent
63aa470bf8
commit
8f9ec25808
64
INTERNAL.md
Normal file
64
INTERNAL.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Methods
|
||||
|
||||
* (1) Get from DB
|
||||
- GetDetails (Detailer : canape-backend)
|
||||
* (2) Get from DB and fetch if not found
|
||||
- GetDetails (Detailer : canape-backend)
|
||||
Stop if found
|
||||
- GetDetails (Detailer : all the others)
|
||||
Upsert if found
|
||||
* (3) Refresh
|
||||
- GetDetails (Detailer : all the others)
|
||||
Upsert if found
|
||||
|
||||
## Movies
|
||||
|
||||
### /movies/polochon
|
||||
|
||||
-Fetch polochon movies + Movies (2)-
|
||||
|
||||
### /movies/{id}/refresh
|
||||
|
||||
-Movies (3) + Torrents (3)-
|
||||
|
||||
### /movies/explore
|
||||
|
||||
-Explorer (1) + Movies (2) + Torrents (1)-
|
||||
|
||||
### /movies/search
|
||||
|
||||
-Search + Movies (2) + Torrents (1)-
|
||||
|
||||
### /movies/refresh
|
||||
|
||||
Explorer (3) + Movies (3) + Torrents (3)
|
||||
|
||||
## Shows
|
||||
|
||||
### /shows/polochon
|
||||
|
||||
-Fetch polochon shows + Shows (2)-
|
||||
|
||||
### /shows/explore
|
||||
|
||||
-Explorer (1) + Shows (2)-
|
||||
|
||||
### /shows/search
|
||||
|
||||
-Search + Shows (2)-
|
||||
|
||||
### /shows/{id}
|
||||
|
||||
-Shows (2) + Episodes (1) + Torrents (1)-
|
||||
|
||||
### /shows/{id}/refresh
|
||||
|
||||
-Shows (3) + Episodes (1) + Torrents (3)-
|
||||
|
||||
### /shows/{id}/seasons/{season}/episodes/{episode}
|
||||
|
||||
-Episode (3) + Torrents (3)-
|
||||
|
||||
### /shows/refresh
|
||||
|
||||
Explorer (3) + Shows (3) + Torrents (3)
|
3
sql/migration/0004_change_media_category.down.sql
Normal file
3
sql/migration/0004_change_media_category.down.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE external_medias ALTER COLUMN category TYPE media_category USING category::media_category;
|
||||
ALTER TABLE external_medias ALTER COLUMN source TYPE media_category USING source::media_source;
|
||||
ALTER TABLE external_medias ALTER COLUMN type TYPE media_category USING type::media_type;
|
3
sql/migration/0004_change_media_category.up.sql
Normal file
3
sql/migration/0004_change_media_category.up.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE external_medias ALTER COLUMN category TYPE text;
|
||||
ALTER TABLE external_medias ALTER COLUMN source TYPE text;
|
||||
ALTER TABLE external_medias ALTER COLUMN type TYPE text;
|
@ -13,6 +13,8 @@ type Middleware struct {
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
type authContextKey string
|
||||
|
||||
// NewMiddleware returns a new authentication middleware
|
||||
func NewMiddleware(authorizer *Authorizer, log *logrus.Entry) *Middleware {
|
||||
return &Middleware{
|
||||
@ -34,7 +36,8 @@ func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http
|
||||
m.log.Debugf("got a nil user in the context")
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "auth.user", user)
|
||||
ctxKey := authContextKey("auth.user")
|
||||
ctx := context.WithValue(r.Context(), ctxKey, user)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
next(w, r)
|
||||
@ -80,7 +83,8 @@ func (m *MiddlewareRole) ServeHTTP(w http.ResponseWriter, r *http.Request, next
|
||||
func GetCurrentUser(r *http.Request, log *logrus.Entry) User {
|
||||
log.Debug("getting user from context")
|
||||
|
||||
u := r.Context().Value("auth.user")
|
||||
ctxKey := authContextKey("auth.user")
|
||||
u := r.Context().Value(ctxKey)
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -1,18 +1,10 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/movies"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/shows"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/torrents"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
// Backend represents the data backend
|
||||
@ -20,127 +12,13 @@ type Backend struct {
|
||||
Database *sqlx.DB
|
||||
}
|
||||
|
||||
// GetUser gets the username from the UserBackend
|
||||
// Implements the UserBackend interface
|
||||
func (b *Backend) GetUser(username string) (auth.User, error) {
|
||||
return users.Get(b.Database, username)
|
||||
}
|
||||
|
||||
// Name implements the Module interface
|
||||
func (b *Backend) Name() string {
|
||||
return "canape-backend"
|
||||
}
|
||||
|
||||
// GetDetails implements the polochon Detailer interface
|
||||
func (b *Backend) GetDetails(media interface{}, log *logrus.Entry) error {
|
||||
switch t := media.(type) {
|
||||
case *polochon.Show:
|
||||
return b.GetShowDetails(t, log)
|
||||
case *polochon.ShowEpisode:
|
||||
return b.GetShowEpisodeDetails(t, log)
|
||||
case *polochon.Movie:
|
||||
return b.GetMovieDetails(t, log)
|
||||
default:
|
||||
return errors.New("invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
// GetMovieDetails gets details for movies
|
||||
func (b *Backend) GetMovieDetails(pmovie *polochon.Movie, log *logrus.Entry) error {
|
||||
m := movies.New(pmovie.ImdbID)
|
||||
if err := m.GetMovie(b.Database); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got movie %s from backend", pmovie.ImdbID)
|
||||
*pmovie = m.Movie
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetShowDetails gets details for shows
|
||||
func (b *Backend) GetShowDetails(pshow *polochon.Show, log *logrus.Entry) error {
|
||||
s := shows.New(pshow.ImdbID)
|
||||
if err := s.GetShow(b.Database); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got show %s from backend", pshow.ImdbID)
|
||||
*pshow = s.Show
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetShowEpisodeDetails gets details for episodes
|
||||
func (b *Backend) GetShowEpisodeDetails(pepisode *polochon.ShowEpisode, log *logrus.Entry) error {
|
||||
e := shows.NewEpisode()
|
||||
e.ShowImdbID = pepisode.ShowImdbID
|
||||
e.Season = pepisode.Season
|
||||
e.Episode = pepisode.Episode
|
||||
if err := e.GetEpisode(b.Database); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got episode S%02dE%02d %s from backend", pepisode.Season, pepisode.Episode, pepisode.ShowImdbID)
|
||||
*pepisode = e.ShowEpisode
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTorrents implements the polochon Torrenter interface
|
||||
func (b *Backend) GetTorrents(media interface{}, log *logrus.Entry) error {
|
||||
switch t := media.(type) {
|
||||
case *polochon.ShowEpisode:
|
||||
return b.GetEpisodeTorrents(t, log)
|
||||
case *polochon.Movie:
|
||||
return b.GetMovieTorrents(t, log)
|
||||
default:
|
||||
return errors.New("invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
// GetMovieTorrents fetch Torrents for movies
|
||||
func (b *Backend) GetMovieTorrents(pmovie *polochon.Movie, log *logrus.Entry) error {
|
||||
// m := movies.New(pmovie.ImdbID)
|
||||
movieTorrents, err := torrents.GetMovieTorrents(b.Database, pmovie.ImdbID)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("torrents found in backend")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("torrent not found in backend")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
log.Debugf("got torrents for movie %s from backend", pmovie.ImdbID)
|
||||
|
||||
for _, t := range movieTorrents {
|
||||
pmovie.Torrents = append(pmovie.Torrents, t.Torrent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEpisodeTorrents fetch Torrents for episodes
|
||||
func (b *Backend) GetEpisodeTorrents(pepisode *polochon.ShowEpisode, log *logrus.Entry) error {
|
||||
e := shows.NewEpisode()
|
||||
e.ShowImdbID = pepisode.ShowImdbID
|
||||
e.Season = pepisode.Season
|
||||
e.Episode = pepisode.Episode
|
||||
episodeTorrents, err := torrents.GetEpisodeTorrents(
|
||||
b.Database,
|
||||
pepisode.ShowImdbID,
|
||||
pepisode.Season,
|
||||
pepisode.Episode,
|
||||
)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("torrents found in backend")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("torrent not found in backend")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
log.Debugf("got torrents for episode S%02dE%02d %s from backend", pepisode.Season, pepisode.Episode, pepisode.ShowImdbID)
|
||||
// Add the torrents to the episode
|
||||
for _, t := range episodeTorrents {
|
||||
e.Torrents = append(e.Torrents, t.Torrent)
|
||||
}
|
||||
|
||||
return nil
|
||||
// GetUser gets the username from the UserBackend
|
||||
// Implements the UserBackend interface
|
||||
func (b *Backend) GetUser(username string) (auth.User, error) {
|
||||
return users.Get(b.Database, username)
|
||||
}
|
||||
|
68
src/internal/backend/detailer.go
Normal file
68
src/internal/backend/detailer.go
Normal file
@ -0,0 +1,68 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
// GetDetails implements the polochon Detailer interface
|
||||
func (b *Backend) GetDetails(media interface{}, log *logrus.Entry) error {
|
||||
switch t := media.(type) {
|
||||
case *polochon.Show:
|
||||
return b.GetShowDetails(t, log)
|
||||
case *polochon.ShowEpisode:
|
||||
return b.GetShowEpisodeDetails(t, log)
|
||||
case *polochon.Movie:
|
||||
return b.GetMovieDetails(t, log)
|
||||
default:
|
||||
return errors.New("invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
// GetMovieDetails gets details for movies
|
||||
func (b *Backend) GetMovieDetails(pMovie *polochon.Movie, log *logrus.Entry) error {
|
||||
// Get the movie
|
||||
if err := GetMovie(b.Database, pMovie); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got movie %s from backend", pMovie.ImdbID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetShowDetails gets details for shows
|
||||
func (b *Backend) GetShowDetails(pShow *polochon.Show, log *logrus.Entry) error {
|
||||
// Get the show
|
||||
if err := GetShow(b.Database, pShow); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got show %s from backend", pShow.ImdbID)
|
||||
// Get the show's episodes
|
||||
err := GetEpisodes(b.Database, pShow, log)
|
||||
if err != nil {
|
||||
log.Warnf("error while getting episodes: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetShowEpisodeDetails gets details for episodes
|
||||
func (b *Backend) GetShowEpisodeDetails(pEpisode *polochon.ShowEpisode, log *logrus.Entry) error {
|
||||
// Get the episode
|
||||
if err := GetEpisode(b.Database, pEpisode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf(
|
||||
"got episode S%02dE%02d %s from backend",
|
||||
pEpisode.Season,
|
||||
pEpisode.Episode,
|
||||
pEpisode.ShowImdbID,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
105
src/internal/backend/episode_torrents.go
Normal file
105
src/internal/backend/episode_torrents.go
Normal file
@ -0,0 +1,105 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertEpisodeTorrentQuery = `
|
||||
INSERT INTO episode_torrents (imdb_id, url, source, quality, upload_user,
|
||||
season, episode, seeders, leechers)
|
||||
VALUES (:imdb_id, :url, :source, :quality, :upload_user, :season, :episode,
|
||||
:seeders, :leechers)
|
||||
ON CONFLICT (imdb_id, season, episode, quality, source)
|
||||
DO UPDATE SET imdb_id=:imdb_id, url=:url, source=:source, quality=:quality,
|
||||
upload_user=:upload_user, season=:season, episode=:episode,
|
||||
seeders=:seeders, leechers=:leechers
|
||||
RETURNING id;`
|
||||
|
||||
getEpisodeTorrentQuery = `
|
||||
SELECT *
|
||||
FROM episode_torrents WHERE imdb_id=$1 AND season=$2 AND episode=$3;`
|
||||
)
|
||||
|
||||
// EpisodeTorrentDB represents the EpisodeTorrent in the DB
|
||||
type EpisodeTorrentDB struct {
|
||||
ID string `db:"id"`
|
||||
ImdbID string `db:"imdb_id"`
|
||||
URL string `db:"url"`
|
||||
Source string `db:"source"`
|
||||
Quality string `db:"quality"`
|
||||
UploadUser string `db:"upload_user"`
|
||||
Season int `db:"season"`
|
||||
Episode int `db:"episode"`
|
||||
Seeders int `db:"seeders"`
|
||||
Leechers int `db:"leechers"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// NewTorrentFromEpisodeTorrentDB returns a polochon.Torrent from an
|
||||
// episodeTorrentDB
|
||||
func NewTorrentFromEpisodeTorrentDB(eDB *EpisodeTorrentDB) *polochon.Torrent {
|
||||
q, _ := polochon.StringToQuality(eDB.Quality)
|
||||
return &polochon.Torrent{
|
||||
Quality: *q,
|
||||
URL: eDB.URL,
|
||||
Seeders: eDB.Seeders,
|
||||
Leechers: eDB.Leechers,
|
||||
Source: eDB.Source,
|
||||
UploadUser: eDB.UploadUser,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEpisodeTorrentDB returns an EpisodeTorrentDB ready to be put in DB from a
|
||||
// polochon.Torrent
|
||||
func NewEpisodeTorrentDB(t *polochon.Torrent, imdbID string, season, episode int) *EpisodeTorrentDB {
|
||||
return &EpisodeTorrentDB{
|
||||
ImdbID: imdbID,
|
||||
Season: season,
|
||||
Episode: episode,
|
||||
URL: t.URL,
|
||||
Source: t.Source,
|
||||
Quality: string(t.Quality),
|
||||
UploadUser: t.UploadUser,
|
||||
Seeders: t.Seeders,
|
||||
Leechers: t.Leechers,
|
||||
}
|
||||
}
|
||||
|
||||
// GetEpisodeTorrents returns show episodes torrents from database
|
||||
func GetEpisodeTorrents(db *sqlx.DB, imdbID string, season, episode int) ([]polochon.Torrent, error) {
|
||||
var torrentsDB = []*EpisodeTorrentDB{}
|
||||
err := db.Select(&torrentsDB, getEpisodeTorrentQuery, imdbID, season, episode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(torrentsDB) == 0 {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
var torrents []polochon.Torrent
|
||||
for _, torrentDB := range torrentsDB {
|
||||
torrent := NewTorrentFromEpisodeTorrentDB(torrentDB)
|
||||
torrents = append(torrents, *torrent)
|
||||
}
|
||||
|
||||
return torrents, nil
|
||||
}
|
||||
|
||||
// UpsertEpisodeTorrent upserts an episode torrent from a polochon torrent
|
||||
func UpsertEpisodeTorrent(db *sqlx.DB, torrent *polochon.Torrent, imdbID string, season, episode int) error {
|
||||
// Create the EpisodeTorrent ready to be put in db
|
||||
eDB := NewEpisodeTorrentDB(torrent, imdbID, season, episode)
|
||||
_, err := db.NamedQuery(upsertEpisodeTorrentQuery, eDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
141
src/internal/backend/episodes.go
Normal file
141
src/internal/backend/episodes.go
Normal file
@ -0,0 +1,141 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertEpisodeQuery = `
|
||||
INSERT INTO episodes (show_imdb_id, show_tvdb_id, title, season, episode, tvdb_id, aired, plot, runtime, rating, imdb_id)
|
||||
VALUES (:show_imdb_id, :show_tvdb_id, :title, :season, :episode, :tvdb_id, :aired, :plot, :runtime, :rating, :imdb_id)
|
||||
ON CONFLICT (show_imdb_id, season, episode)
|
||||
DO UPDATE
|
||||
SET show_imdb_id=:show_imdb_id, show_tvdb_id=:show_tvdb_id, title=:title,
|
||||
season=:season, episode=:episode, tvdb_id=:tvdb_id, aired=:aired,
|
||||
plot=:plot, runtime=:runtime, rating=:rating, imdb_id=:imdb_id
|
||||
RETURNING id;`
|
||||
|
||||
getEpisodesQuery = `
|
||||
SELECT *
|
||||
FROM episodes WHERE show_imdb_id=$1;`
|
||||
|
||||
getEpisodeQuery = `
|
||||
SELECT *
|
||||
FROM episodes WHERE show_imdb_id=$1 AND season=$2 AND episode=$3;`
|
||||
)
|
||||
|
||||
// EpisodeDB represents the Episode in the DB
|
||||
type EpisodeDB struct {
|
||||
ID string `db:"id"`
|
||||
TvdbID int `db:"tvdb_id"`
|
||||
ImdbID string `db:"imdb_id"`
|
||||
ShowImdbID string `db:"show_imdb_id"`
|
||||
ShowTvdbID int `db:"show_tvdb_id"`
|
||||
Season int `db:"season"`
|
||||
Episode int `db:"episode"`
|
||||
Title string `db:"title"`
|
||||
Rating float32 `db:"rating"`
|
||||
Plot string `db:"plot"`
|
||||
Thumb string `db:"thumb"`
|
||||
Runtime int `db:"runtime"`
|
||||
Aired string `db:"aired"`
|
||||
ReleaseGroup string `db:"release_group"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// NewEpisodeFromPolochon returns an EpisodeDB from a polochon ShowEpisode
|
||||
func NewEpisodeFromPolochon(e *polochon.ShowEpisode) *EpisodeDB {
|
||||
return &EpisodeDB{
|
||||
TvdbID: e.TvdbID,
|
||||
ImdbID: e.EpisodeImdbID,
|
||||
ShowImdbID: e.ShowImdbID,
|
||||
ShowTvdbID: e.ShowTvdbID,
|
||||
Season: e.Season,
|
||||
Episode: e.Episode,
|
||||
Title: e.Title,
|
||||
Rating: e.Rating,
|
||||
Plot: e.Plot,
|
||||
Thumb: e.Thumb,
|
||||
Runtime: e.Runtime,
|
||||
Aired: e.Aired,
|
||||
ReleaseGroup: e.ReleaseGroup,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEpisodeFromDB returns a new polochon ShowEpisode from an EpisodeDB
|
||||
func NewEpisodeFromDB(eDB *EpisodeDB) *polochon.ShowEpisode {
|
||||
pEpisode := polochon.ShowEpisode{}
|
||||
FillEpisodeFromDB(eDB, &pEpisode)
|
||||
return &pEpisode
|
||||
}
|
||||
|
||||
// FillEpisodeFromDB fills a ShowEpisode from an EpisodeDB
|
||||
func FillEpisodeFromDB(eDB *EpisodeDB, pEpisode *polochon.ShowEpisode) {
|
||||
pEpisode.TvdbID = eDB.TvdbID
|
||||
pEpisode.EpisodeImdbID = eDB.ImdbID
|
||||
pEpisode.ShowImdbID = eDB.ShowImdbID
|
||||
pEpisode.ShowTvdbID = eDB.ShowTvdbID
|
||||
pEpisode.Season = eDB.Season
|
||||
pEpisode.Episode = eDB.Episode
|
||||
pEpisode.Title = eDB.Title
|
||||
pEpisode.Rating = eDB.Rating
|
||||
pEpisode.Plot = eDB.Plot
|
||||
pEpisode.Thumb = eDB.Thumb
|
||||
pEpisode.Runtime = eDB.Runtime
|
||||
pEpisode.Aired = eDB.Aired
|
||||
}
|
||||
|
||||
// GetEpisode gets an episode and fills the polochon episode
|
||||
func GetEpisode(db *sqlx.DB, pEpisode *polochon.ShowEpisode) error {
|
||||
var episodeDB EpisodeDB
|
||||
err := db.QueryRowx(getEpisodeQuery, pEpisode.ShowImdbID, pEpisode.Season, pEpisode.Episode).StructScan(&episodeDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
FillEpisodeFromDB(&episodeDB, pEpisode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEpisodes gets show's episodes and fills the polochon show
|
||||
func GetEpisodes(db *sqlx.DB, pShow *polochon.Show, log *logrus.Entry) error {
|
||||
// Get the episodes
|
||||
var episodesDB = []*EpisodeDB{}
|
||||
err := db.Select(&episodesDB, getEpisodesQuery, pShow.ImdbID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(episodesDB) == 0 {
|
||||
log.Debug("got no episodes")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("got %d episodes", len(episodesDB))
|
||||
for _, episodeDB := range episodesDB {
|
||||
episode := NewEpisodeFromDB(episodeDB)
|
||||
pShow.Episodes = append(pShow.Episodes, episode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpsertEpisode upserts the episode
|
||||
func UpsertEpisode(db *sqlx.DB, showEpisode *polochon.ShowEpisode) error {
|
||||
e := NewEpisodeFromPolochon(showEpisode)
|
||||
var id string
|
||||
r, err := db.NamedQuery(upsertEpisodeQuery, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for r.Next() {
|
||||
r.Scan(&id)
|
||||
}
|
||||
e.ID = id
|
||||
return nil
|
||||
}
|
47
src/internal/backend/explorer.go
Normal file
47
src/internal/backend/explorer.go
Normal file
@ -0,0 +1,47 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertExternalMediaQuery = `
|
||||
INSERT INTO external_medias (type, source, category, ids)
|
||||
VALUES (:type, :source, :category, :ids)
|
||||
ON CONFLICT (type, source, category)
|
||||
DO UPDATE SET type=:type, source=:source, category=:category, ids=:ids
|
||||
RETURNING id;`
|
||||
|
||||
getExternalMediaQuery = `SELECT * FROM external_medias WHERE type=$1 AND source=$2 AND category=$3 LIMIT 1;`
|
||||
)
|
||||
|
||||
// Media represents an external media
|
||||
type Media struct {
|
||||
sqly.BaseModel
|
||||
Type string `db:"type"`
|
||||
Source string `db:"source"`
|
||||
Category string `db:"category"`
|
||||
IDs pq.StringArray `db:"ids"`
|
||||
}
|
||||
|
||||
// Explore will return an array of Medias from the DB
|
||||
func Explore(db *sqlx.DB, mtype, msrc, mcat string) (*Media, error) {
|
||||
m := &Media{}
|
||||
if err := db.QueryRowx(getExternalMediaQuery, mtype, msrc, mcat).StructScan(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Upsert adds or updates the Media in the database
|
||||
func (m *Media) Upsert(db *sqlx.DB) error {
|
||||
_, err := db.NamedQuery(upsertExternalMediaQuery, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
101
src/internal/backend/movie_torrents.go
Normal file
101
src/internal/backend/movie_torrents.go
Normal file
@ -0,0 +1,101 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertMovieTorrentQuery = `
|
||||
INSERT INTO movie_torrents (imdb_id, url, source, quality, upload_user,
|
||||
seeders, leechers)
|
||||
VALUES (:imdb_id, :url, :source, :quality, :upload_user, :seeders,
|
||||
:leechers)
|
||||
ON CONFLICT (imdb_id, quality, source)
|
||||
DO UPDATE SET imdb_id=:imdb_id, url=:url, source=:source, quality=:quality,
|
||||
upload_user=:upload_user, seeders=:seeders, leechers=:leechers
|
||||
RETURNING id;`
|
||||
|
||||
getMovieTorrentQueryByImdbID = `
|
||||
SELECT *
|
||||
FROM movie_torrents WHERE imdb_id=$1;`
|
||||
)
|
||||
|
||||
// MovieTorrentDB represents the MovieTorrent in the DB
|
||||
type MovieTorrentDB struct {
|
||||
ID string `db:"id"`
|
||||
ImdbID string `db:"imdb_id"`
|
||||
URL string `db:"url"`
|
||||
Source string `db:"source"`
|
||||
Quality string `db:"quality"`
|
||||
UploadUser string `db:"upload_user"`
|
||||
Seeders int `db:"seeders"`
|
||||
Leechers int `db:"leechers"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// NewTorrentFromMovieTorrentDB creates a new polochon.Torrent from a
|
||||
// MovieTorrentDB
|
||||
func NewTorrentFromMovieTorrentDB(mDB *MovieTorrentDB) *polochon.Torrent {
|
||||
q, _ := polochon.StringToQuality(mDB.Quality)
|
||||
return &polochon.Torrent{
|
||||
Quality: *q,
|
||||
URL: mDB.URL,
|
||||
Seeders: mDB.Seeders,
|
||||
Leechers: mDB.Leechers,
|
||||
Source: mDB.Source,
|
||||
UploadUser: mDB.UploadUser,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMovieTorrentDB returns a MovieTorrent ready to be put in DB from a
|
||||
// Torrent
|
||||
func NewMovieTorrentDB(t *polochon.Torrent, imdbID string) MovieTorrentDB {
|
||||
return MovieTorrentDB{
|
||||
ImdbID: imdbID,
|
||||
URL: t.URL,
|
||||
Source: t.Source,
|
||||
Quality: string(t.Quality),
|
||||
UploadUser: t.UploadUser,
|
||||
Seeders: t.Seeders,
|
||||
Leechers: t.Leechers,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMovieTorrents returns polochon.Torrents from the database
|
||||
func GetMovieTorrents(db *sqlx.DB, imdbID string) ([]polochon.Torrent, error) {
|
||||
var torrentsDB = []*MovieTorrentDB{}
|
||||
// Get the torrents from the DB
|
||||
err := db.Select(&torrentsDB, getMovieTorrentQueryByImdbID, imdbID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(torrentsDB) == 0 {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
// Create polochon Torrents from the MovieTorrentDB
|
||||
var torrents []polochon.Torrent
|
||||
for _, torrentDB := range torrentsDB {
|
||||
torrent := NewTorrentFromMovieTorrentDB(torrentDB)
|
||||
torrents = append(torrents, *torrent)
|
||||
}
|
||||
|
||||
return torrents, nil
|
||||
}
|
||||
|
||||
// UpsertMovieTorrent adds or updates MovieTorrent in db
|
||||
func UpsertMovieTorrent(db *sqlx.DB, t *polochon.Torrent, imdbID string) error {
|
||||
mDB := NewMovieTorrentDB(t, imdbID)
|
||||
_, err := db.NamedQuery(upsertMovieTorrentQuery, mDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
125
src/internal/backend/movie_wishlist.go
Normal file
125
src/internal/backend/movie_wishlist.go
Normal file
@ -0,0 +1,125 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertMovieWishlistQuery = `
|
||||
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;`
|
||||
|
||||
isMovieWishlistedQueryByUserID = `
|
||||
SELECT
|
||||
movies.imdb_id
|
||||
FROM movies INNER JOIN movies_tracked
|
||||
ON
|
||||
movies.imdb_id=movies_tracked.imdb_id
|
||||
AND
|
||||
movies_tracked.user_id=$2
|
||||
WHERE movies.imdb_id=$1;`
|
||||
|
||||
getMovieWishlistQueryByUserID = `
|
||||
SELECT
|
||||
movies.imdb_id
|
||||
FROM movies INNER JOIN movies_tracked
|
||||
ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$1;`
|
||||
|
||||
deleteMovieWishlistedQueryByID = `DELETE FROM movies_tracked WHERE imdb_id=$1 AND user_id=$2;`
|
||||
)
|
||||
|
||||
// MovieWishlist represents the list of movies wishlisted by a user
|
||||
type MovieWishlist struct {
|
||||
movies map[string]struct{}
|
||||
}
|
||||
|
||||
// NewMovieWishlist returns a new MovieWishlist
|
||||
func NewMovieWishlist() *MovieWishlist {
|
||||
return &MovieWishlist{
|
||||
movies: map[string]struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// add adds movie to the MovieWishlist
|
||||
func (w *MovieWishlist) add(imdbID string) {
|
||||
w.movies[imdbID] = struct{}{}
|
||||
}
|
||||
|
||||
// List return a slice of imdbID wishlisted
|
||||
func (w *MovieWishlist) List() []string {
|
||||
movies := make([]string, len(w.movies))
|
||||
|
||||
i := 0
|
||||
for imdbID := range w.movies {
|
||||
movies[i] = imdbID
|
||||
i++
|
||||
}
|
||||
|
||||
return movies
|
||||
}
|
||||
|
||||
// GetMovieWishlist returns a MovieWishlist obejct for a user
|
||||
func GetMovieWishlist(db *sqlx.DB, userID string) (*MovieWishlist, error) {
|
||||
var movies = []string{}
|
||||
// Get the list of movies
|
||||
err := db.Select(&movies, getMovieWishlistQueryByUserID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new MovieWishlist
|
||||
wishlist := NewMovieWishlist()
|
||||
for _, imdbID := range movies {
|
||||
// add the movie to the wishlist object
|
||||
wishlist.add(imdbID)
|
||||
}
|
||||
|
||||
return wishlist, nil
|
||||
}
|
||||
|
||||
// IsMovieInWishlist returns true if the movie is in the wishlist
|
||||
func (w *MovieWishlist) IsMovieInWishlist(imdbID string) bool {
|
||||
_, ok := w.movies[imdbID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsMovieWishlisted returns true if the movie is wishlisted
|
||||
func IsMovieWishlisted(db *sqlx.DB, userID, imdbID string) (bool, error) {
|
||||
var movies = []string{}
|
||||
// Check if the movie is wishlisted by the user
|
||||
err := db.Select(&movies, isMovieWishlistedQueryByUserID, imdbID, userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(movies) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// AddMovieToWishlist Adds a movie to a user's wishlist in DB
|
||||
func AddMovieToWishlist(db *sqlx.DB, userID, imdbID string) error {
|
||||
_, err := db.Exec(upsertMovieWishlistQuery, imdbID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMovieFromWishlist deletes a movie from a user's wishlist in DB
|
||||
func DeleteMovieFromWishlist(db *sqlx.DB, userID, imdbID string) error {
|
||||
r, err := db.Exec(deleteMovieWishlistedQueryByID, imdbID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count, _ := r.RowsAffected()
|
||||
if count != 1 {
|
||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||
}
|
||||
return nil
|
||||
}
|
119
src/internal/backend/movies.go
Normal file
119
src/internal/backend/movies.go
Normal file
@ -0,0 +1,119 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertMovieQuery = `
|
||||
INSERT INTO movies (imdb_id, title, rating, votes, plot, tmdb_id, year,
|
||||
genres, original_title, runtime, sort_title, tagline)
|
||||
VALUES (:imdb_id, :title, :rating, :votes, :plot, :tmdb_id, :year, :genres,
|
||||
:original_title, :runtime, :sort_title, :tagline)
|
||||
ON CONFLICT (imdb_id)
|
||||
DO UPDATE
|
||||
SET imdb_id=:imdb_id, title=:title, rating=:rating, votes=:votes,
|
||||
plot=:plot, tmdb_id=:tmdb_id, year=:year, genres=:genres,
|
||||
original_title=:original_title, runtime=:runtime, sort_title=:sort_title,
|
||||
tagline=:tagline
|
||||
RETURNING id;`
|
||||
|
||||
getMovieQueryByImdbID = `
|
||||
SELECT *
|
||||
FROM movies
|
||||
WHERE imdb_id=$1;`
|
||||
)
|
||||
|
||||
// MovieDB represents the Movie in the DB
|
||||
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"`
|
||||
Rating float32 `db:"rating"`
|
||||
Votes int `db:"votes"`
|
||||
Plot string `db:"plot"`
|
||||
Year int `db:"year"`
|
||||
Runtime int `db:"runtime"`
|
||||
Tagline string `db:"tagline"`
|
||||
Genres pq.StringArray `db:"genres"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// GetMovie fills show details of a polochon.Movie
|
||||
func GetMovie(db *sqlx.DB, pMovie *polochon.Movie) error {
|
||||
var mDB MovieDB
|
||||
var err error
|
||||
if pMovie.ImdbID != "" {
|
||||
// Get the data from the DB
|
||||
err = db.QueryRowx(getMovieQueryByImdbID, pMovie.ImdbID).StructScan(&mDB)
|
||||
} else {
|
||||
err = fmt.Errorf("Can't get movie details, you have to specify an ImdbID")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fills the polochon.Movie from the MovieDB
|
||||
FillFromDB(&mDB, pMovie)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillFromDB fills a Movie from a MovieDB extracted from the DB
|
||||
func FillFromDB(mDB *MovieDB, pMovie *polochon.Movie) {
|
||||
pMovie.ImdbID = mDB.ImdbID
|
||||
pMovie.Title = mDB.Title
|
||||
pMovie.Rating = mDB.Rating
|
||||
pMovie.Votes = mDB.Votes
|
||||
pMovie.Plot = mDB.Plot
|
||||
pMovie.TmdbID = mDB.TmdbID
|
||||
pMovie.Year = mDB.Year
|
||||
pMovie.OriginalTitle = mDB.OriginalTitle
|
||||
pMovie.Runtime = mDB.Runtime
|
||||
pMovie.Genres = mDB.Genres
|
||||
pMovie.SortTitle = mDB.SortTitle
|
||||
pMovie.Tagline = mDB.Tagline
|
||||
}
|
||||
|
||||
// UpsertMovie upsert a polochon Movie in the database
|
||||
func UpsertMovie(db *sqlx.DB, pMovie *polochon.Movie) error {
|
||||
mDB := NewMovieDB(pMovie)
|
||||
_, err := db.NamedQuery(upsertMovieQuery, mDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMovieDB returns a Movie ready to be put in DB from a
|
||||
// polochon Movie
|
||||
func NewMovieDB(m *polochon.Movie) MovieDB {
|
||||
genres := []string{}
|
||||
if m.Genres != nil {
|
||||
genres = m.Genres
|
||||
}
|
||||
return MovieDB{
|
||||
ImdbID: m.ImdbID,
|
||||
Title: m.Title,
|
||||
Rating: m.Rating,
|
||||
Votes: m.Votes,
|
||||
Plot: m.Plot,
|
||||
TmdbID: m.TmdbID,
|
||||
Year: m.Year,
|
||||
OriginalTitle: m.OriginalTitle,
|
||||
Runtime: m.Runtime,
|
||||
SortTitle: m.SortTitle,
|
||||
Tagline: m.Tagline,
|
||||
Genres: genres,
|
||||
}
|
||||
}
|
140
src/internal/backend/show_wishlist.go
Normal file
140
src/internal/backend/show_wishlist.go
Normal file
@ -0,0 +1,140 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertShowWishlistQuery = `
|
||||
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;`
|
||||
|
||||
isShowWishlistedQueryByUserID = `
|
||||
SELECT
|
||||
shows.imdb_id
|
||||
FROM shows INNER JOIN shows_tracked
|
||||
ON
|
||||
shows.imdb_id=shows_tracked.imdb_id
|
||||
AND
|
||||
shows_tracked.user_id=$2
|
||||
WHERE shows.imdb_id=$1;`
|
||||
|
||||
getShowWishlistQueryByUserID = `
|
||||
SELECT
|
||||
shows.imdb_id
|
||||
FROM shows INNER JOIN shows_tracked
|
||||
ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$1;`
|
||||
|
||||
deleteShowWishlistedQueryByID = `DELETE FROM shows_tracked WHERE imdb_id=$1 AND user_id=$2;`
|
||||
)
|
||||
|
||||
// ShowWishlist represents the show wishlist of a user
|
||||
type ShowWishlist struct {
|
||||
shows map[string]*WishedShow
|
||||
}
|
||||
|
||||
// WishedShow represents a wished show of a user
|
||||
type WishedShow struct {
|
||||
ImdbID string `db:"imdb_id"`
|
||||
Season int
|
||||
Episode int
|
||||
}
|
||||
|
||||
// NewShowWishlist returns a new ShowWishlist
|
||||
func NewShowWishlist() *ShowWishlist {
|
||||
return &ShowWishlist{
|
||||
shows: map[string]*WishedShow{},
|
||||
}
|
||||
}
|
||||
|
||||
// add adds a show to the ShowWishlist object
|
||||
func (w *ShowWishlist) add(imdbID string, season, episode int) {
|
||||
w.shows[imdbID] = &WishedShow{
|
||||
ImdbID: imdbID,
|
||||
Season: season,
|
||||
Episode: episode,
|
||||
}
|
||||
}
|
||||
|
||||
// List return a slice of WishedShow
|
||||
func (w *ShowWishlist) List() []*WishedShow {
|
||||
shows := make([]*WishedShow, len(w.shows))
|
||||
|
||||
i := 0
|
||||
for _, s := range w.shows {
|
||||
shows[i] = &WishedShow{
|
||||
ImdbID: s.ImdbID,
|
||||
Season: s.Season,
|
||||
Episode: s.Episode,
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return shows
|
||||
}
|
||||
|
||||
// GetShowWishlist returns a ShowWishlist for a user
|
||||
func GetShowWishlist(db *sqlx.DB, userID string) (*ShowWishlist, error) {
|
||||
var wishedShows = []*WishedShow{}
|
||||
// Get the wishlisted shows
|
||||
err := db.Select(&wishedShows, getShowWishlistQueryByUserID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create the new object
|
||||
wishlist := NewShowWishlist()
|
||||
for _, wishedShow := range wishedShows {
|
||||
// Add the show to the wishlist
|
||||
wishlist.add(wishedShow.ImdbID, wishedShow.Season, wishedShow.Episode)
|
||||
}
|
||||
|
||||
return wishlist, nil
|
||||
}
|
||||
|
||||
// IsShowInWishlist returns true and the WishedShow if the show is in the
|
||||
// wishlist
|
||||
func (w *ShowWishlist) IsShowInWishlist(imdbID string) (*WishedShow, bool) {
|
||||
show, ok := w.shows[imdbID]
|
||||
return show, ok
|
||||
}
|
||||
|
||||
// IsShowWishlisted returns true and the WishedShow if the show is wishlisted
|
||||
func IsShowWishlisted(db *sqlx.DB, userID, imdbID string) (*WishedShow, error) {
|
||||
var wishedShows = []*WishedShow{}
|
||||
err := db.Select(&wishedShows, isShowWishlistedQueryByUserID, imdbID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(wishedShows) != 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return wishedShows[0], nil
|
||||
}
|
||||
|
||||
// AddShowToWishlist Adds a show to a user's wishlist
|
||||
func AddShowToWishlist(db *sqlx.DB, userID, imdbID string, season, episode int) error {
|
||||
_, err := db.Exec(upsertShowWishlistQuery, imdbID, userID, season, episode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteShowFromWishlist deletes a show from a user's wishlist
|
||||
func DeleteShowFromWishlist(db *sqlx.DB, userID, imdbID string) error {
|
||||
r, err := db.Exec(deleteShowWishlistedQueryByID, imdbID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count, _ := r.RowsAffected()
|
||||
if count != 1 {
|
||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||
}
|
||||
return nil
|
||||
}
|
104
src/internal/backend/shows.go
Normal file
104
src/internal/backend/shows.go
Normal file
@ -0,0 +1,104 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertShowQuery = `
|
||||
INSERT INTO shows (imdb_id, title, rating, plot, tvdb_id, year, first_aired)
|
||||
VALUES (:imdb_id, :title, :rating, :plot, :tvdb_id, :year, :first_aired)
|
||||
ON CONFLICT (imdb_id)
|
||||
DO UPDATE
|
||||
SET imdb_id=:imdb_id, title=:title, rating=:rating, plot=:plot,
|
||||
tvdb_id=:tvdb_id, year=:year, first_aired=:first_aired
|
||||
RETURNING id;`
|
||||
|
||||
getShowQueryByImdbID = `
|
||||
SELECT *
|
||||
FROM shows WHERE imdb_id=$1;`
|
||||
)
|
||||
|
||||
// ShowDB represents the Show in the DB
|
||||
type ShowDB struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
// UpsertShow a show in the database
|
||||
func UpsertShow(db *sqlx.DB, s *polochon.Show) error {
|
||||
sDB := NewShowFromPolochon(s)
|
||||
// Upsert the show
|
||||
_, err := db.NamedQuery(upsertShowQuery, sDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, e := range s.Episodes {
|
||||
// Upsert its episodes
|
||||
err = UpsertEpisode(db, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewShowFromPolochon returns an ShowDB from a polochon Show
|
||||
func NewShowFromPolochon(s *polochon.Show) *ShowDB {
|
||||
sDB := ShowDB{
|
||||
ImdbID: s.ImdbID,
|
||||
TvdbID: s.TvdbID,
|
||||
Title: s.Title,
|
||||
Rating: s.Rating,
|
||||
Plot: s.Plot,
|
||||
Year: s.Year,
|
||||
}
|
||||
if s.FirstAired != nil {
|
||||
sDB.FirstAired = *s.FirstAired
|
||||
}
|
||||
return &sDB
|
||||
}
|
||||
|
||||
// GetShow fills a show from the DB
|
||||
func GetShow(db *sqlx.DB, pShow *polochon.Show) error {
|
||||
var err error
|
||||
var sDB ShowDB
|
||||
if pShow.ImdbID != "" {
|
||||
err = db.QueryRowx(getShowQueryByImdbID, pShow.ImdbID).StructScan(&sDB)
|
||||
} else {
|
||||
err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fill the show from the ShowDB
|
||||
FillShowFromDB(&sDB, pShow)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillShowFromDB returns a Show from a ShowDB extracted from the DB
|
||||
func FillShowFromDB(sDB *ShowDB, pShow *polochon.Show) {
|
||||
pShow.ImdbID = sDB.ImdbID
|
||||
pShow.Title = sDB.Title
|
||||
pShow.Rating = sDB.Rating
|
||||
pShow.Plot = sDB.Plot
|
||||
pShow.TvdbID = sDB.TvdbID
|
||||
pShow.Year = sDB.Year
|
||||
pShow.FirstAired = &sDB.FirstAired
|
||||
}
|
76
src/internal/backend/torrenter.go
Normal file
76
src/internal/backend/torrenter.go
Normal file
@ -0,0 +1,76 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
// GetTorrents implements the polochon Torrenter interface
|
||||
func (b *Backend) GetTorrents(media interface{}, log *logrus.Entry) error {
|
||||
switch t := media.(type) {
|
||||
case *polochon.ShowEpisode:
|
||||
return b.GetEpisodeTorrents(t, log)
|
||||
case *polochon.Movie:
|
||||
return b.GetMovieTorrents(t, log)
|
||||
default:
|
||||
return errors.New("invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
// GetMovieTorrents fetch Torrents for movies
|
||||
func (b *Backend) GetMovieTorrents(pmovie *polochon.Movie, log *logrus.Entry) error {
|
||||
movieTorrents, err := GetMovieTorrents(b.Database, pmovie.ImdbID)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("torrents found in backend")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("torrent not found in backend")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
log.Debugf(
|
||||
"got %d torrents for movie %s from backend",
|
||||
len(movieTorrents),
|
||||
pmovie.ImdbID,
|
||||
)
|
||||
|
||||
// Add the torrents to the movie
|
||||
pmovie.Torrents = movieTorrents
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEpisodeTorrents fetch Torrents for episodes
|
||||
func (b *Backend) GetEpisodeTorrents(pepisode *polochon.ShowEpisode, log *logrus.Entry) error {
|
||||
episodeTorrents, err := GetEpisodeTorrents(
|
||||
b.Database,
|
||||
pepisode.ShowImdbID,
|
||||
pepisode.Season,
|
||||
pepisode.Episode,
|
||||
)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("torrents found in backend")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("torrent not found in backend")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
log.Debugf(
|
||||
"got %d torrents for episode S%02dE%02d %s from backend",
|
||||
len(episodeTorrents),
|
||||
pepisode.Season,
|
||||
pepisode.Episode,
|
||||
pepisode.ShowImdbID,
|
||||
)
|
||||
|
||||
// Add the torrents to the episode
|
||||
pepisode.Torrents = episodeTorrents
|
||||
|
||||
return nil
|
||||
}
|
@ -7,12 +7,14 @@ import (
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
"github.com/odwrtw/polochon/modules/eztv"
|
||||
"github.com/odwrtw/polochon/modules/tmdb"
|
||||
"github.com/odwrtw/polochon/modules/trakttv"
|
||||
"github.com/odwrtw/polochon/modules/tvdb"
|
||||
"github.com/odwrtw/polochon/modules/yts"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Config represents the Config of the canape app
|
||||
type Config struct {
|
||||
Authorizer AuthorizerConfig `yaml:"authorizer"`
|
||||
PGDSN string `yaml:"pgdsn"`
|
||||
@ -22,21 +24,26 @@ type Config struct {
|
||||
|
||||
// TODO improve the detailers and torrenters configurations
|
||||
TmdbAPIKey string `yaml:"tmdb_api_key"`
|
||||
MovieExplorers []polochon.Explorer
|
||||
MovieDetailers []polochon.Detailer
|
||||
MovieTorrenters []polochon.Torrenter
|
||||
MovieSearchers []polochon.Searcher
|
||||
ShowExplorers []polochon.Explorer
|
||||
ShowDetailers []polochon.Detailer
|
||||
ShowTorrenters []polochon.Torrenter
|
||||
ShowSearchers []polochon.Searcher
|
||||
}
|
||||
|
||||
// AuthorizerConfig is the config for the authentication
|
||||
type AuthorizerConfig struct {
|
||||
Pepper string `yaml:"pepper"`
|
||||
Cost int `yaml:"cost"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
|
||||
// Load loads a config file from a path and return a Config object
|
||||
func Load(path string) (*Config, error) {
|
||||
// Open the file
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -45,26 +52,51 @@ func Load(path string) (*Config, error) {
|
||||
|
||||
cf := &Config{}
|
||||
|
||||
// Read it
|
||||
b, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal its content
|
||||
err = yaml.Unmarshal(b, cf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the default values
|
||||
|
||||
// Default movie detailers
|
||||
cf.MovieDetailers = []polochon.Detailer{}
|
||||
if cf.TmdbAPIKey != "" {
|
||||
d, err := tmdb.New(&tmdb.Params{cf.TmdbAPIKey})
|
||||
d, err := tmdb.New(
|
||||
&tmdb.Params{
|
||||
ApiKey: cf.TmdbAPIKey,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf.MovieDetailers = append(cf.MovieDetailers, d)
|
||||
}
|
||||
|
||||
// Default movie explorers
|
||||
cf.MovieExplorers = []polochon.Explorer{}
|
||||
ytsExp, err := yts.NewExplorer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf.MovieExplorers = append(cf.MovieExplorers, ytsExp)
|
||||
if cf.TraktTVClientID != "" {
|
||||
traktExp, err := trakttv.NewExplorer(&trakttv.Params{
|
||||
ClientID: cf.TraktTVClientID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf.MovieExplorers = append(cf.MovieExplorers, traktExp)
|
||||
}
|
||||
|
||||
// Default movie torrenters
|
||||
cf.MovieTorrenters = []polochon.Torrenter{}
|
||||
d, err := yts.New()
|
||||
@ -89,6 +121,23 @@ func Load(path string) (*Config, error) {
|
||||
}
|
||||
cf.ShowSearchers = append(cf.ShowSearchers, showSearcher)
|
||||
|
||||
// Default show explorers
|
||||
cf.ShowExplorers = []polochon.Explorer{}
|
||||
eztvExp, err := eztv.NewExplorer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf.ShowExplorers = append(cf.ShowExplorers, eztvExp)
|
||||
if cf.TraktTVClientID != "" {
|
||||
traktExp, err := trakttv.NewExplorer(&trakttv.Params{
|
||||
ClientID: cf.TraktTVClientID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf.ShowExplorers = append(cf.ShowExplorers, traktExp)
|
||||
}
|
||||
|
||||
// Default show detailers
|
||||
cf.ShowDetailers = []polochon.Detailer{}
|
||||
showDetailer, err := tvdb.NewDetailer()
|
||||
|
@ -1,58 +1,189 @@
|
||||
package extmedias
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
"fmt"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/movies"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/shows"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertExternalMediaQuery = `
|
||||
INSERT INTO external_medias (type, source, category, ids)
|
||||
VALUES (:type, :source, :category, :ids)
|
||||
ON CONFLICT (type, source, category)
|
||||
DO UPDATE SET type=:type, source=:source, category=:category, ids=:ids
|
||||
RETURNING id;`
|
||||
// NewExplorer returns a polochon.Explorer from the list of Explorers in the config
|
||||
func NewExplorer(env *web.Env, mediaType, name string) (polochon.Explorer, error) {
|
||||
var explorers []polochon.Explorer
|
||||
if mediaType == "movie" {
|
||||
explorers = env.Config.MovieExplorers
|
||||
} else {
|
||||
explorers = env.Config.ShowExplorers
|
||||
}
|
||||
|
||||
deleteExternalMediaQuery = `DELETE FROM external_medias WHERE id=:id;`
|
||||
getExternalMediaQuery = `SELECT * FROM external_medias WHERE type=$1 AND source=$2 AND category=$3 LIMIT 1;`
|
||||
)
|
||||
|
||||
// Media represents an external media
|
||||
type Media struct {
|
||||
sqly.BaseModel
|
||||
Type string `db:"type"`
|
||||
Source string `db:"source"`
|
||||
Category string `db:"category"`
|
||||
IDs pq.StringArray `db:"ids"`
|
||||
for _, e := range explorers {
|
||||
// Check the name of the explorer
|
||||
if e.Name() == name {
|
||||
return e, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no such explorer %s", name)
|
||||
}
|
||||
|
||||
// Upsert adds or updates or adds the Media in the database
|
||||
func (m *Media) Upsert(db *sqlx.DB) error {
|
||||
var id string
|
||||
r, err := db.NamedQuery(upsertExternalMediaQuery, m)
|
||||
// GetMediaIDs will get some media IDs
|
||||
func GetMediaIDs(env *web.Env, mediaType string, source string, category string) ([]string, error) {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"mediaType": mediaType,
|
||||
"source": source,
|
||||
"category": category,
|
||||
"function": "extmedias.GetMediaIds",
|
||||
})
|
||||
|
||||
// Get the explorer that we need
|
||||
explorer, err := NewExplorer(env, mediaType, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for r.Next() {
|
||||
r.Scan(&id)
|
||||
}
|
||||
m.ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the media from database or raise an error
|
||||
func (m *Media) Delete(db *sqlx.DB) error {
|
||||
_, err := db.NamedExec(deleteExternalMediaQuery, m)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get gets a media
|
||||
func Get(db *sqlx.DB, mtype, msrc, mcat string) (*Media, error) {
|
||||
m := &Media{}
|
||||
if err := db.QueryRowx(getExternalMediaQuery, mtype, msrc, mcat).StructScan(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
var ids []string
|
||||
if mediaType == "movie" {
|
||||
// Explore new movies
|
||||
medias, err := explorer.GetMovieList(category, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add them in the list of ids
|
||||
for _, media := range medias {
|
||||
ids = append(ids, media.ImdbID)
|
||||
}
|
||||
} else {
|
||||
// Explore new shows
|
||||
medias, err := explorer.GetShowList(category, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add them in the list of ids
|
||||
for _, media := range medias {
|
||||
ids = append(ids, media.ImdbID)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("got %d medias from %s", len(ids), source)
|
||||
|
||||
// Upserts the new Media entry
|
||||
media := &backend.Media{
|
||||
Type: mediaType,
|
||||
Source: source,
|
||||
Category: category,
|
||||
IDs: ids,
|
||||
}
|
||||
|
||||
err = media.Upsert(env.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("medias updated in database")
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// RefreshShows refresh explored shows
|
||||
// Call all explorers and Refresh
|
||||
// Retrieve a list of all the ids to be refreshed
|
||||
// Refresh all the shows + Torrents for all episodes
|
||||
func RefreshShows(env *web.Env) {
|
||||
showMap := make(map[string]struct{})
|
||||
// Iterate over all show explorers
|
||||
for _, showExplorer := range env.Config.ShowExplorers {
|
||||
availableOptions := showExplorer.AvailableShowOptions()
|
||||
// Iterate over all show explorer options
|
||||
for _, option := range availableOptions {
|
||||
env.Log.Infof("refreshing shows %s %s", showExplorer.Name(), option)
|
||||
|
||||
// Get the new explored shows
|
||||
showIds, err := GetMediaIDs(env, "show", showExplorer.Name(), option)
|
||||
if err != nil {
|
||||
env.Log.Warnf("error while refreshing %s/%s : %s", showExplorer.Name(), option, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add them in a map ( to get every shows only once )
|
||||
for _, id := range showIds {
|
||||
showMap[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
env.Log.Info("========================================================")
|
||||
// Iterate over the map of shows to refresh them
|
||||
for id := range showMap {
|
||||
show := shows.New(id)
|
||||
// Refresh the shows
|
||||
err := show.Refresh(env, env.Config.ShowDetailers)
|
||||
if err != nil {
|
||||
env.Log.Warnf("error while refreshing show %s : %s", id, err)
|
||||
}
|
||||
// Iterate over all episodes to refresh their torrents
|
||||
for _, e := range show.Episodes {
|
||||
// Refresh the torrents
|
||||
err := shows.RefreshTorrents(env, e, env.Config.ShowTorrenters)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RefreshMovies refresh explored Movies
|
||||
// Call all explorers and Refresh
|
||||
// Retrieve a list of all the ids to be refreshed
|
||||
// Refresh all the movies + Torrents
|
||||
func RefreshMovies(env *web.Env) {
|
||||
movieMap := make(map[string]struct{})
|
||||
// Iterate over all movie explorers
|
||||
for _, movieExplorer := range env.Config.MovieExplorers {
|
||||
availableOptions := movieExplorer.AvailableMovieOptions()
|
||||
// Iterate over all movie explorer options
|
||||
for _, option := range availableOptions {
|
||||
env.Log.Infof("refreshing movies %s %s", movieExplorer.Name(), option)
|
||||
|
||||
// Get the new explored movies
|
||||
movieIds, err := GetMediaIDs(env, "movie", movieExplorer.Name(), option)
|
||||
if err != nil {
|
||||
env.Log.Warnf("error while refreshing %s/%s : %s", movieExplorer.Name(), option, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add them in a map ( to get every movies only once )
|
||||
for _, id := range movieIds {
|
||||
movieMap[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
env.Log.Info("========================================================")
|
||||
// Iterate over the map of movies to refresh them
|
||||
for id := range movieMap {
|
||||
movie := movies.New(id, nil, nil, false, env.Config.PublicDir)
|
||||
// Refresh the movie
|
||||
err := movie.Refresh(env, env.Config.MovieDetailers)
|
||||
if err != nil {
|
||||
env.Log.Warnf("error while refreshing movie %s : %s", id, err)
|
||||
}
|
||||
// Refresh its torrents
|
||||
err = movie.RefreshTorrents(env, env.Config.MovieTorrenters)
|
||||
if err != nil {
|
||||
env.Log.Warnf("error while refreshing movie torrents %s : %s", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh refreshes explore new shows and refresh them
|
||||
func Refresh(env *web.Env) {
|
||||
env.Log.Debugf("refreshing shows")
|
||||
RefreshShows(env)
|
||||
env.Log.Debugf("done refreshing shows")
|
||||
|
||||
env.Log.Debugf("refreshing movies")
|
||||
RefreshMovies(env)
|
||||
env.Log.Debugf("done refreshing movies")
|
||||
}
|
||||
|
@ -1,75 +0,0 @@
|
||||
package extmedias
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
_ "github.com/mattes/migrate/driver/postgres"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var db *sqlx.DB
|
||||
var pgdsn string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
pgdsn = os.Getenv("POSTGRES_DSN")
|
||||
db, err = sqlx.Connect("postgres", pgdsn)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddExternalMedias(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
media := &Media{
|
||||
Type: "movie",
|
||||
Source: "trakttv",
|
||||
Category: "trending",
|
||||
IDs: []string{"1", "2", "3"},
|
||||
}
|
||||
|
||||
// Add it
|
||||
if err := media.Upsert(db); err != nil {
|
||||
t.Fatalf("failed to add external media: %q", err)
|
||||
}
|
||||
|
||||
// Update the IDs
|
||||
media.IDs = []string{"1", "2", "3", "4"}
|
||||
if err := media.Upsert(db); err != nil {
|
||||
t.Fatalf("failed to update the external media: %q", err)
|
||||
}
|
||||
|
||||
// Find it
|
||||
m, err := Get(db, media.Type, media.Source, media.Category)
|
||||
if err != nil {
|
||||
t.Fatalf("failed get the external media: %q", err)
|
||||
}
|
||||
|
||||
// Small, almost useless check
|
||||
if len(m.IDs) != 4 {
|
||||
t.Fatalf("the media should have 4 ids, only %d found", len(m.IDs))
|
||||
}
|
||||
|
||||
// Delete it
|
||||
if err := media.Delete(db); err != nil {
|
||||
t.Fatalf("failed to delete the external media: %q", err)
|
||||
}
|
||||
|
||||
// Search it and expect that it's not found
|
||||
m, err = Get(db, media.Type, media.Source, media.Category)
|
||||
if err == nil {
|
||||
t.Fatal("there should be an error, was the external media deleted ?")
|
||||
}
|
||||
if err != sql.ErrNoRows {
|
||||
t.Fatalf("unexpected error: %q", err)
|
||||
}
|
||||
})
|
||||
}
|
@ -1,130 +1,107 @@
|
||||
package extmedias
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/movies"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/shows"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
eztvExplorer "github.com/odwrtw/polochon/modules/eztv"
|
||||
traktExplorer "github.com/odwrtw/polochon/modules/trakttv"
|
||||
ytsExplorer "github.com/odwrtw/polochon/modules/yts"
|
||||
)
|
||||
|
||||
// GetMediaIDs will get some media IDs
|
||||
func GetMediaIDs(env *web.Env, mediaType string, source string, category string, force bool) ([]string, error) {
|
||||
// RefreshHandler refresh the explored movies
|
||||
func RefreshHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"mediaType": mediaType,
|
||||
"source": source,
|
||||
"category": category,
|
||||
"function": "extmedias.GetMediaIds",
|
||||
"function": "extmedias.RefreshHandler",
|
||||
})
|
||||
log.Debugf("refreshing shows and movies")
|
||||
Refresh(env)
|
||||
log.Debugf("done refreshing shows and movies")
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
media, err := Get(env.Database, mediaType, source, category)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debugf("%s medias found in database", mediaType)
|
||||
if !force {
|
||||
log.Debug("returning medias from db")
|
||||
return media.IDs, nil
|
||||
}
|
||||
case sql.ErrNoRows:
|
||||
log.Debugf("%s medias not found in database", mediaType)
|
||||
default:
|
||||
// Unexpected error
|
||||
return nil, err
|
||||
}
|
||||
// RefreshMoviesHandler refresh the explored movies
|
||||
func RefreshMoviesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"function": "extmedias.RefreshMoviesHandler",
|
||||
})
|
||||
log.Debugf("refreshing movies")
|
||||
RefreshMovies(env)
|
||||
log.Debugf("done refreshing movies")
|
||||
return nil
|
||||
}
|
||||
|
||||
explorer, err := NewExplorer(env, source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ids []string
|
||||
if mediaType == "movie" {
|
||||
medias, err := explorer.GetMovieList(polochon.ExploreByRate, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, media := range medias {
|
||||
ids = append(ids, media.ImdbID)
|
||||
}
|
||||
} else {
|
||||
medias, err := explorer.GetShowList(polochon.ExploreByRate, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, media := range medias {
|
||||
if i > 5 {
|
||||
break
|
||||
}
|
||||
ids = append(ids, media.ImdbID)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("got %d medias from %s", len(ids), source)
|
||||
|
||||
media = &Media{
|
||||
Type: mediaType,
|
||||
Source: source,
|
||||
Category: category,
|
||||
IDs: ids,
|
||||
}
|
||||
|
||||
err = media.Upsert(env.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("medias updated in database")
|
||||
|
||||
return ids, nil
|
||||
// RefreshShowsHandler refresh the explored shows
|
||||
func RefreshShowsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"function": "extmedias.RefreshShowsHandler",
|
||||
})
|
||||
log.Debugf("refreshing shows")
|
||||
RefreshShows(env)
|
||||
log.Debugf("done refreshing shows")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMovies get some movies
|
||||
func GetMovies(env *web.Env, user *users.User, source string, category string, force bool) ([]*movies.Movie, error) {
|
||||
movieIds, err := GetMediaIDs(env, "movie", source, category, force)
|
||||
func GetMovies(env *web.Env, user *users.User, source string, category string) ([]*movies.Movie, error) {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"source": source,
|
||||
"category": category,
|
||||
"function": "extmedias.GetMovies",
|
||||
})
|
||||
log.Debugf("getting movies")
|
||||
media, err := backend.Explore(env.Database, "movie", source, category)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the URLs from polochon, we don't really care if it fails for now
|
||||
var urls map[string]string
|
||||
urls, err = movies.GetPolochonMoviesURLs(user)
|
||||
// Create a papi client
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting polochon movies url: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the user's polochon movies
|
||||
pMovies, err := client.GetMovies()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the user's wishlisted movies
|
||||
moviesWishlist, err := backend.GetMovieWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
movieList := []*movies.Movie{}
|
||||
for _, id := range movieIds {
|
||||
movie := movies.New(id)
|
||||
err := movie.GetDetails(env, user, force)
|
||||
// Fill all the movies infos from the list of IDs
|
||||
for _, id := range media.IDs {
|
||||
pMovie, _ := pMovies.Has(id)
|
||||
movie := movies.New(id, client, pMovie, moviesWishlist.IsMovieInWishlist(id), env.Config.PublicDir)
|
||||
// First check in the DB
|
||||
before := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then with the default detailers
|
||||
after := env.Config.MovieDetailers
|
||||
err := movie.GetAndFetch(env, before, after)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting movie details : %s", err)
|
||||
continue
|
||||
}
|
||||
err = movie.GetTorrents(env, force)
|
||||
|
||||
// Look only for torrents in db
|
||||
torrenters := []polochon.Torrenter{env.Backend.Torrenter}
|
||||
err = movie.GetTorrents(env, torrenters)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting movie torrents : %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if urls != nil {
|
||||
if url, ok := urls[id]; ok {
|
||||
movie.PolochonURL = url
|
||||
}
|
||||
}
|
||||
|
||||
movieList = append(movieList, movie)
|
||||
}
|
||||
return movieList, nil
|
||||
@ -132,31 +109,59 @@ func GetMovies(env *web.Env, user *users.User, source string, category string, f
|
||||
|
||||
// GetShows get some shows
|
||||
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)
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"source": source,
|
||||
"category": category,
|
||||
"function": "extmedias.GetShows",
|
||||
})
|
||||
log.Debugf("getting shows")
|
||||
// Get the user's polochon config
|
||||
media, err := backend.Explore(env.Database, "show", source, category)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a papi client
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the polochon's shows
|
||||
pShows, err := client.GetShows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the user's wishlisted shows
|
||||
wShows, err := backend.GetShowWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
showList := []*shows.Show{}
|
||||
for _, id := range showIds {
|
||||
show := shows.New(id)
|
||||
err := show.GetDetails(env, user, force)
|
||||
// Fill all the shows infos from the list of IDs
|
||||
for _, id := range media.IDs {
|
||||
pShow, _ := pShows.Has(id)
|
||||
wShow, _ := wShows.IsShowInWishlist(id)
|
||||
show := shows.NewWithClient(id, client, pShow, wShow, env.Config.PublicDir)
|
||||
|
||||
// First check in the DB
|
||||
before := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then with the default detailers
|
||||
after := env.Config.ShowDetailers
|
||||
err := show.GetAndFetch(env, before, after)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting show details : %s", err)
|
||||
continue
|
||||
}
|
||||
err = show.GetTorrents(env, force)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting show torrents : %s", err)
|
||||
continue
|
||||
}
|
||||
showList = append(showList, show)
|
||||
}
|
||||
return showList, nil
|
||||
}
|
||||
|
||||
// Explore will explore some movies
|
||||
func Explore(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
// ExploreMovies will explore some movies from the db
|
||||
func ExploreMovies(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -181,7 +186,7 @@ func Explore(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
|
||||
// Get the medias without trying to refresh them
|
||||
movies, err := GetMovies(env, user, source, category, false)
|
||||
movies, err := GetMovies(env, user, source, category)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
@ -222,94 +227,3 @@ func ExploreShows(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
return env.RenderJSON(w, shows)
|
||||
}
|
||||
|
||||
// MediaSources represents the implemented media sources
|
||||
var MediaSources = []string{
|
||||
"trakttv",
|
||||
"yts",
|
||||
}
|
||||
|
||||
// ShowMediaSources represents the implemented media sources for shows
|
||||
var ShowMediaSources = []string{
|
||||
"eztv",
|
||||
}
|
||||
|
||||
// Refresh will refresh the movie list
|
||||
func Refresh(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
env.Log.Debugf("refreshing infos ...")
|
||||
source := r.FormValue("source")
|
||||
if source == "" {
|
||||
source = "yts"
|
||||
}
|
||||
|
||||
category := r.FormValue("category")
|
||||
if category == "" {
|
||||
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 MediaSources {
|
||||
env.Log.Debugf("refreshing %s", source)
|
||||
// GetMedias and refresh them
|
||||
_, err := GetMovies(env, user, source, category, true)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, map[string]string{"message": "Refresh is done"})
|
||||
}
|
||||
|
||||
// RefreshShows will refresh the movie list
|
||||
func RefreshShows(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
env.Log.Debugf("refreshing shows ...")
|
||||
source := r.FormValue("source")
|
||||
if source == "" {
|
||||
source = "eztv"
|
||||
}
|
||||
|
||||
category := r.FormValue("category")
|
||||
if category == "" {
|
||||
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, user, source, category, true)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, map[string]string{"message": "Refresh is done"})
|
||||
}
|
||||
|
||||
// NewExplorer returns a polochon.Explorer
|
||||
func NewExplorer(env *web.Env, source string) (polochon.Explorer, error) {
|
||||
switch source {
|
||||
case "trakttv":
|
||||
return traktExplorer.NewExplorer(&traktExplorer.Params{
|
||||
ClientID: env.Config.TraktTVClientID,
|
||||
})
|
||||
case "yts":
|
||||
return ytsExplorer.NewExplorer()
|
||||
case "eztv":
|
||||
return eztvExplorer.NewExplorer()
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown explorer")
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
@ -15,84 +14,28 @@ import (
|
||||
"github.com/odwrtw/polochon/modules/pam"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
)
|
||||
|
||||
// ErrPolochonUnavailable is an error returned if the polochon server is not available
|
||||
var ErrPolochonUnavailable = fmt.Errorf("Invalid polochon address")
|
||||
|
||||
func getPolochonMovies(user *users.User) ([]*Movie, error) {
|
||||
movies := []*Movie{}
|
||||
|
||||
var polochonConfig config.UserPolochon
|
||||
err := user.GetConfig("polochon", &polochonConfig)
|
||||
if err != nil {
|
||||
return movies, err
|
||||
}
|
||||
|
||||
client, err := papi.New(polochonConfig.URL)
|
||||
if err != nil {
|
||||
return movies, err
|
||||
}
|
||||
|
||||
if polochonConfig.Token != "" {
|
||||
client.SetToken(polochonConfig.Token)
|
||||
}
|
||||
|
||||
pmovies, err := client.GetMovies()
|
||||
if err != nil {
|
||||
// Catch network error for accessing specified polochon address
|
||||
if uerr, ok := err.(*url.Error); ok {
|
||||
if nerr, ok := uerr.Err.(*net.OpError); ok {
|
||||
if nerr.Op == "dial" {
|
||||
return movies, ErrPolochonUnavailable
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return movies, err
|
||||
}
|
||||
for _, pmovie := range pmovies {
|
||||
movie := New(pmovie.ImdbID)
|
||||
movie.PolochonURL, err = client.DownloadURL(&papi.Movie{ImdbID: movie.ImdbID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
movies = append(movies, movie)
|
||||
}
|
||||
return movies, nil
|
||||
}
|
||||
|
||||
// GetPolochonMoviesURLs returns the polochon urls associated with an imdb id
|
||||
func GetPolochonMoviesURLs(user *users.User) (map[string]string, error) {
|
||||
movies, err := getPolochonMovies(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urls := make(map[string]string, len(movies))
|
||||
for _, movie := range movies {
|
||||
urls[movie.ImdbID] = movie.PolochonURL
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
// FromPolochon will returns movies from Polochon
|
||||
func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
// PolochonMoviesHandler will returns movies from Polochon
|
||||
func PolochonMoviesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
// Get the user from the request
|
||||
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"))
|
||||
}
|
||||
|
||||
movies, err := getPolochonMovies(user)
|
||||
// Get the polochon movies of the user
|
||||
movies, err := getPolochonMovies(user, env)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Create a new polochon Detailer to get infos about the movies we just got
|
||||
var polochonConfig config.UserPolochon
|
||||
err = user.GetConfig("polochon", &polochonConfig)
|
||||
if err != nil {
|
||||
@ -104,9 +47,13 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
Token: polochonConfig.Token,
|
||||
})
|
||||
|
||||
// Get details with the polochon Detailer for each polochon.Movies we have
|
||||
for _, m := range movies {
|
||||
m.Detailers = []polochon.Detailer{detailer}
|
||||
err := m.GetDetails(env, user, false)
|
||||
// First try from the db
|
||||
first := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then try from the polochon detailer
|
||||
detailers := []polochon.Detailer{detailer}
|
||||
err := m.GetAndFetch(env, first, detailers)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
}
|
||||
@ -115,20 +62,47 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
return env.RenderJSON(w, movies)
|
||||
}
|
||||
|
||||
// GetDetailsHandler retrieves details for a movie
|
||||
func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
// RefreshMovieHandler refreshes details for a movie
|
||||
func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
// Get the user
|
||||
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, user, true); err != nil {
|
||||
return err
|
||||
// Create a new papi client
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Get the polochon movie
|
||||
pMovie, err := client.GetMovie(id)
|
||||
if err != nil && err != papi.ErrResourceNotFound {
|
||||
log.Println("Error getting movie ", err)
|
||||
}
|
||||
|
||||
// Check if the movie is wishlisted
|
||||
isWishlisted, err := backend.IsMovieWishlisted(env.Database, user.ID, id)
|
||||
if err != nil {
|
||||
log.Println("Error getting wishlisted movie ", err)
|
||||
}
|
||||
|
||||
// Create a new movie
|
||||
m := New(id, client, pMovie, isWishlisted, env.Config.PublicDir)
|
||||
|
||||
// Refresh the movie's infos
|
||||
if err := m.Refresh(env, env.Config.MovieDetailers); err != nil {
|
||||
env.Log.Error(err)
|
||||
}
|
||||
|
||||
// Refresh the movie's torrents
|
||||
if err := m.RefreshTorrents(env, env.Config.MovieTorrenters); err != nil {
|
||||
env.Log.Error(err)
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, m)
|
||||
@ -154,8 +128,27 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
return env.RenderError(w, errors.New("invalid user"))
|
||||
}
|
||||
|
||||
// Create a new papi client
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Get the user's polochon movies
|
||||
pMovies, err := client.GetMovies()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Get the user's wishlisted movies
|
||||
moviesWishlist, err := backend.GetMovieWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
var movies []*polochon.Movie
|
||||
searchers := env.Config.MovieSearchers
|
||||
// Search for the movie with all the Searchers
|
||||
for _, searcher := range searchers {
|
||||
result, err := searcher.SearchMovie(data.Key, env.Log)
|
||||
if err != nil {
|
||||
@ -165,42 +158,45 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
movies = append(movies, result...)
|
||||
}
|
||||
|
||||
// Get the URLs from polochon, we don't really care if it fails for now
|
||||
var urls map[string]string
|
||||
urls, err = GetPolochonMoviesURLs(user)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting polochon movies url: %s", err)
|
||||
}
|
||||
|
||||
env.Log.Debugf("got %d movies doing search %q", len(movies), data.Key)
|
||||
movieList := []*Movie{}
|
||||
// For each movie found, fill the details
|
||||
for _, m := range movies {
|
||||
movie := New(m.ImdbID)
|
||||
err := movie.GetDetails(env, user, false)
|
||||
pMovie, _ := pMovies.Has(m.ImdbID)
|
||||
movie := New(
|
||||
m.ImdbID,
|
||||
client,
|
||||
pMovie,
|
||||
moviesWishlist.IsMovieInWishlist(m.ImdbID),
|
||||
env.Config.PublicDir,
|
||||
)
|
||||
|
||||
// First check in the DB
|
||||
before := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then with the default detailers
|
||||
after := env.Config.MovieDetailers
|
||||
err := movie.GetAndFetch(env, before, after)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting movie details : %s", err)
|
||||
continue
|
||||
}
|
||||
err = movie.GetTorrents(env, false)
|
||||
|
||||
// Look only for torrents in db
|
||||
torrenters := []polochon.Torrenter{env.Backend.Torrenter}
|
||||
err = movie.GetTorrents(env, torrenters)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting movie torrents : %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if urls != nil {
|
||||
if url, ok := urls[m.ImdbID]; ok {
|
||||
movie.PolochonURL = url
|
||||
}
|
||||
}
|
||||
|
||||
movieList = append(movieList, movie)
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, movieList)
|
||||
}
|
||||
|
||||
// DeleteHandler deletes the movie from polochon
|
||||
func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
// PolochonDeleteHandler deletes the movie from polochon
|
||||
func PolochonDeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
@ -210,27 +206,20 @@ func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
})
|
||||
log.Debugf("deleting movie")
|
||||
|
||||
// Get the user
|
||||
v := auth.GetCurrentUser(r, env.Log)
|
||||
user, ok := v.(*users.User)
|
||||
if !ok {
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
var polochonConfig config.UserPolochon
|
||||
err := user.GetConfig("polochon", &polochonConfig)
|
||||
// Create a new papi client
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
client, err := papi.New(polochonConfig.URL)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
if polochonConfig.Token != "" {
|
||||
client.SetToken(polochonConfig.Token)
|
||||
}
|
||||
|
||||
// Delete the movie
|
||||
return client.Delete(&papi.Movie{ImdbID: id})
|
||||
}
|
||||
|
||||
@ -245,8 +234,7 @@ func AddToWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
m := New(id)
|
||||
if err := m.AddToWishlist(env, user); err != nil {
|
||||
if err := backend.AddMovieToWishlist(env.Database, user.ID, id); err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
@ -264,8 +252,7 @@ func DeleteFromWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
m := New(id)
|
||||
if err := m.DeleteFromWishlist(env, user); err != nil {
|
||||
if err := backend.DeleteMovieFromWishlist(env.Database, user.ID, id); err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
@ -280,10 +267,55 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
movies, err := GetWishlist(env, user)
|
||||
// Create a new papi client
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, movies)
|
||||
// Get the user's polochon movies
|
||||
pMovies, err := client.GetMovies()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Get the user's wishlisted movies
|
||||
moviesWishlist, err := backend.GetMovieWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
movieList := []*Movie{}
|
||||
// For each movie found, fill the details
|
||||
for _, imdbID := range moviesWishlist.List() {
|
||||
pMovie, _ := pMovies.Has(imdbID)
|
||||
movie := New(
|
||||
imdbID,
|
||||
client,
|
||||
pMovie,
|
||||
moviesWishlist.IsMovieInWishlist(imdbID),
|
||||
env.Config.PublicDir,
|
||||
)
|
||||
// First check in the DB
|
||||
before := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then with the default detailers
|
||||
after := env.Config.MovieDetailers
|
||||
err := movie.GetAndFetch(env, before, after)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting movie details : %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Look only for torrents in db
|
||||
torrenters := []polochon.Torrenter{env.Backend.Torrenter}
|
||||
err = movie.GetTorrents(env, torrenters)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting movie torrents : %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
movieList = append(movieList, movie)
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, movieList)
|
||||
}
|
||||
|
@ -2,331 +2,198 @@ package movies
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
"github.com/odwrtw/papi"
|
||||
"github.com/odwrtw/polochon/lib"
|
||||
|
||||
"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/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertMovieQuery = `
|
||||
INSERT INTO movies (imdb_id, title, rating, votes, plot, tmdb_id, year,
|
||||
genres, original_title, runtime, sort_title, tagline)
|
||||
VALUES (:imdb_id, :title, :rating, :votes, :plot, :tmdb_id, :year, :genres,
|
||||
:original_title, :runtime, :sort_title, :tagline)
|
||||
ON CONFLICT (imdb_id)
|
||||
DO UPDATE
|
||||
SET imdb_id=:imdb_id, title=:title, rating=:rating, votes=:votes,
|
||||
plot=:plot, tmdb_id=:tmdb_id, year=:year, genres=:genres,
|
||||
original_title=:original_title, runtime=:runtime, sort_title=:sort_title,
|
||||
tagline=:tagline
|
||||
RETURNING id;`
|
||||
|
||||
getUserMovieQueryByImdbID = `
|
||||
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;`
|
||||
|
||||
getUserMovieQueryByID = `
|
||||
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;`
|
||||
|
||||
getMovieQueryByImdbID = `
|
||||
SELECT *
|
||||
FROM movies
|
||||
WHERE imdb_id=$1;`
|
||||
|
||||
getMovieQueryByID = `
|
||||
SELECT *
|
||||
FROM movies
|
||||
WHERE movies.id=$1;`
|
||||
|
||||
deleteMovieQuery = `DELETE FROM movies WHERE id=$1;`
|
||||
)
|
||||
|
||||
// MovieDB represents the Movie in the DB
|
||||
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"`
|
||||
Rating float32 `db:"rating"`
|
||||
Votes int `db:"votes"`
|
||||
Plot string `db:"plot"`
|
||||
Year int `db:"year"`
|
||||
Runtime int `db:"runtime"`
|
||||
Tagline string `db:"tagline"`
|
||||
Genres pq.StringArray `db:"genres"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// NewMovieDB returns a Movie ready to be put in DB from a
|
||||
// Movie
|
||||
func NewMovieDB(m *Movie) MovieDB {
|
||||
genres := []string{}
|
||||
if m.Genres != nil {
|
||||
genres = m.Genres
|
||||
}
|
||||
return MovieDB{
|
||||
ID: m.ID,
|
||||
ImdbID: m.ImdbID,
|
||||
Title: m.Title,
|
||||
Rating: m.Rating,
|
||||
Votes: m.Votes,
|
||||
Plot: m.Plot,
|
||||
TmdbID: m.TmdbID,
|
||||
Year: m.Year,
|
||||
OriginalTitle: m.OriginalTitle,
|
||||
Runtime: m.Runtime,
|
||||
SortTitle: m.SortTitle,
|
||||
Tagline: m.Tagline,
|
||||
Genres: genres,
|
||||
Created: m.Created,
|
||||
Updated: m.Updated,
|
||||
}
|
||||
}
|
||||
|
||||
// FillFromDB returns a Movie from a MovieDB extracted from the DB
|
||||
func (m *Movie) FillFromDB(mDB *MovieDB) {
|
||||
m.Created = mDB.Created
|
||||
m.Updated = mDB.Updated
|
||||
m.ID = mDB.ID
|
||||
m.ImdbID = mDB.ImdbID
|
||||
m.Title = mDB.Title
|
||||
m.Rating = mDB.Rating
|
||||
m.Votes = mDB.Votes
|
||||
m.Plot = mDB.Plot
|
||||
m.TmdbID = mDB.TmdbID
|
||||
m.Year = mDB.Year
|
||||
m.OriginalTitle = mDB.OriginalTitle
|
||||
m.Runtime = mDB.Runtime
|
||||
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"`
|
||||
*polochon.Movie
|
||||
client *papi.Client
|
||||
pMovie *papi.Movie
|
||||
publicDir string
|
||||
Wishlisted bool `json:"wishlisted"`
|
||||
}
|
||||
|
||||
// New returns a new Movie with an ImDB id
|
||||
func New(imdbID string) *Movie {
|
||||
// MarshalJSON implements the Marshal interface
|
||||
func (m *Movie) MarshalJSON() ([]byte, error) {
|
||||
type Alias Movie
|
||||
|
||||
var downloadURL string
|
||||
// If the episode is present, fill the downloadURL
|
||||
if m.pMovie != nil {
|
||||
downloadURL, _ = m.client.DownloadURL(m.pMovie)
|
||||
}
|
||||
|
||||
// Marshal the movie with its polochon_url
|
||||
movieToMarshal := &struct {
|
||||
*Alias
|
||||
PolochonURL string `json:"polochon_url"`
|
||||
PosterURL string `json:"poster_url"`
|
||||
}{
|
||||
Alias: (*Alias)(m),
|
||||
PolochonURL: downloadURL,
|
||||
PosterURL: m.PosterURL(),
|
||||
}
|
||||
|
||||
return json.Marshal(movieToMarshal)
|
||||
}
|
||||
|
||||
// New returns a new Movie with all the needed infos
|
||||
func New(imdbID string, client *papi.Client, pMovie *papi.Movie, isWishlisted bool, publicDir string) *Movie {
|
||||
return &Movie{
|
||||
Movie: polochon.Movie{
|
||||
client: client,
|
||||
pMovie: pMovie,
|
||||
publicDir: publicDir,
|
||||
Wishlisted: isWishlisted,
|
||||
Movie: &polochon.Movie{
|
||||
ImdbID: imdbID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns show details in database from id or imdbid or an 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(getUserMovieQueryByID, m.ID, user.ID).StructScan(&mDB)
|
||||
} else if m.ImdbID != "" {
|
||||
err = env.Database.QueryRowx(getUserMovieQueryByImdbID, m.ImdbID, user.ID).StructScan(&mDB)
|
||||
} else {
|
||||
err = fmt.Errorf("Can't get movie details, you have to specify an ID or ImdbID")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the poster url
|
||||
m.PosterURL = m.GetPosterURL(env)
|
||||
|
||||
m.FillFromDB(&mDB)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMovie returns show details in database from id or imdbid or an error
|
||||
func (m *Movie) GetMovie(db *sqlx.DB) error {
|
||||
var mDB MovieDB
|
||||
var err error
|
||||
if m.ID != "" {
|
||||
err = db.QueryRowx(getMovieQueryByID, m.ID).StructScan(&mDB)
|
||||
} else if m.ImdbID != "" {
|
||||
err = db.QueryRowx(getMovieQueryByImdbID, m.ImdbID).StructScan(&mDB)
|
||||
} else {
|
||||
err = fmt.Errorf("Can't get movie details, you have to specify an ID or ImdbID")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the poster url
|
||||
// m.PosterURL = m.GetPosterURL(env)
|
||||
|
||||
m.FillFromDB(&mDB)
|
||||
return nil
|
||||
}
|
||||
// if not exists, use polochon.Detailer and save informations in the database
|
||||
// for future use
|
||||
//
|
||||
// If force is used, the detailer will be used even if the movie is found in
|
||||
// database
|
||||
func (m *Movie) GetDetails(env *web.Env, user *users.User, force bool) error {
|
||||
// GetDetails retrieves details for the movie with the given detailers
|
||||
func (m *Movie) GetDetails(env *web.Env, detailers []polochon.Detailer) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": m.ImdbID,
|
||||
"function": "movies.GetDetails",
|
||||
})
|
||||
log.Debugf("getting details")
|
||||
|
||||
if len(m.Detailers) == 0 {
|
||||
m.Detailers = env.Config.MovieDetailers
|
||||
}
|
||||
|
||||
var err error
|
||||
err = m.Get(env, user)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("movie found in database")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("movie not found in database")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
// If force is not specified, don't go further
|
||||
if !force {
|
||||
// Will return ErrNoRows if the movie wasn't found
|
||||
return err
|
||||
}
|
||||
m.Detailers = detailers
|
||||
|
||||
// GetDetail
|
||||
err = m.Movie.GetDetails(env.Log)
|
||||
err := m.Movie.GetDetails(env.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("got details from detailers")
|
||||
|
||||
err = m.Upsert(env.Database)
|
||||
if err != nil {
|
||||
log.Debug("error while doing db func")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("movie added in database")
|
||||
|
||||
// Download poster
|
||||
err = web.Download(m.Thumb, m.imgFile(env))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("poster downloaded")
|
||||
|
||||
// Set the poster url
|
||||
m.PosterURL = m.GetPosterURL(env)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTorrents retrieves torrents for the movie, first try to get info from db,
|
||||
// if not exists, use polochon.Torrenter and save informations in the database
|
||||
// for future use
|
||||
//
|
||||
// If force is used, the torrenter will be used even if the torrent is found in
|
||||
// GetAndFetch retrieves details for the movie with the given
|
||||
// detailers 'before'
|
||||
// If found, return
|
||||
// If not, retrives details with the detailers 'after' and update them in
|
||||
// database
|
||||
func (m *Movie) GetTorrents(env *web.Env, force bool) error {
|
||||
func (m *Movie) GetAndFetch(env *web.Env, before []polochon.Detailer, after []polochon.Detailer) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": m.ImdbID,
|
||||
"function": "movies.GetAndFetch",
|
||||
})
|
||||
|
||||
// Try to get details with the first batch of Detailers
|
||||
err := m.GetDetails(env, before)
|
||||
if err == nil {
|
||||
log.Debug("movie found in first try")
|
||||
return nil
|
||||
}
|
||||
log.Debugf("movie not found in database: %s", err)
|
||||
|
||||
// If not found, try the second batch and upsert
|
||||
return m.Refresh(env, after)
|
||||
}
|
||||
|
||||
// Refresh retrieves details for the movie with the given detailers
|
||||
// and update them in database
|
||||
func (m *Movie) Refresh(env *web.Env, detailers []polochon.Detailer) error {
|
||||
// Refresh
|
||||
err := m.GetDetails(env, detailers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Download poster
|
||||
err = web.Download(m.Thumb, m.imgFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env.Log.Debug("poster downloaded")
|
||||
|
||||
// If found, update in database
|
||||
return backend.UpsertMovie(env.Database, m.Movie)
|
||||
}
|
||||
|
||||
// GetTorrents retrieves torrents for the movie with the given torrenters
|
||||
func (m *Movie) GetTorrents(env *web.Env, torrenters []polochon.Torrenter) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": m.ImdbID,
|
||||
"function": "movies.GetTorrents",
|
||||
})
|
||||
log.Debugf("getting torrents")
|
||||
|
||||
if len(m.Torrenters) == 0 {
|
||||
m.Torrenters = env.Config.MovieTorrenters
|
||||
}
|
||||
m.Torrenters = torrenters
|
||||
|
||||
movieTorrents, err := torrents.GetMovieTorrents(env.Database, m.ImdbID)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("torrents found in database")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("torrent not found in database")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
if !force {
|
||||
log.Debugf("returning %d torrents from db", len(movieTorrents))
|
||||
// Add the torrents to the movie
|
||||
for _, t := range movieTorrents {
|
||||
m.Torrents = append(m.Torrents, t.Torrent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = m.Movie.GetTorrents(env.Log)
|
||||
err := m.Movie.GetTorrents(env.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got %d torrents from torrenters", len(m.Movie.Torrents))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAndFetchTorrents retrieves torrents for the movie with the given
|
||||
// torrenters 'before'
|
||||
// If found, return
|
||||
// If not, retrives torrents with the torrenters 'after' and update them in
|
||||
// database
|
||||
func (m *Movie) GetAndFetchTorrents(env *web.Env, before []polochon.Torrenter, after []polochon.Torrenter) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": m.ImdbID,
|
||||
"function": "movies.GetAndFetchTorrents",
|
||||
})
|
||||
|
||||
// Try to get torrents with the first batch of Torrenters
|
||||
err := m.GetTorrents(env, before)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("movie torrent's found in first try")
|
||||
return nil
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("movie's torrents not found in database")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
|
||||
// If not found, try the second batch and upsert
|
||||
return m.RefreshTorrents(env, after)
|
||||
}
|
||||
|
||||
// RefreshTorrents retrieves torrents for the movie with the given torrenters
|
||||
// and update them in database
|
||||
func (m *Movie) RefreshTorrents(env *web.Env, torrenters []polochon.Torrenter) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": m.ImdbID,
|
||||
"function": "movies.RefreshTorrents",
|
||||
})
|
||||
|
||||
// Get torrents with de torrenters
|
||||
err := m.GetTorrents(env, torrenters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got %d torrents from torrenters", len(m.Movie.Torrents))
|
||||
|
||||
// Update them in database
|
||||
for _, t := range m.Movie.Torrents {
|
||||
torrent := torrents.NewMovie(m.ImdbID, t)
|
||||
err = torrent.Upsert(env.Database)
|
||||
err = backend.UpsertMovieTorrent(env.Database, &t, m.ImdbID)
|
||||
if err != nil {
|
||||
log.Error("error while adding torrent", err)
|
||||
continue
|
||||
@ -336,51 +203,59 @@ func (m *Movie) GetTorrents(env *web.Env, force bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert a movie in the database
|
||||
func (m *Movie) Upsert(db *sqlx.DB) error {
|
||||
mDB := NewMovieDB(m)
|
||||
var id string
|
||||
r, err := db.NamedQuery(upsertMovieQuery, mDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for r.Next() {
|
||||
r.Scan(&id)
|
||||
}
|
||||
m.ID = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete movie from database
|
||||
func (m *Movie) Delete(db *sqlx.DB) error {
|
||||
r, err := db.Exec(deleteMovieQuery, m.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count, _ := r.RowsAffected()
|
||||
if count != 1 {
|
||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// imgURL returns the default image url
|
||||
func (m *Movie) imgURL(env *web.Env) string {
|
||||
func (m *Movie) imgURL() string {
|
||||
return fmt.Sprintf("img/movies/%s.jpg", m.ImdbID)
|
||||
}
|
||||
|
||||
// imgFile returns the image location on disk
|
||||
func (m *Movie) imgFile(env *web.Env) string {
|
||||
return filepath.Join(env.Config.PublicDir, m.imgURL(env))
|
||||
func (m *Movie) imgFile() string {
|
||||
return filepath.Join(m.publicDir, m.imgURL())
|
||||
}
|
||||
|
||||
// GetPosterURL returns the image URL or the default image if the poster is not yet downloaded
|
||||
func (m *Movie) GetPosterURL(env *web.Env) string {
|
||||
// PosterURL returns the image URL or the default image if the poster is not yet downloaded
|
||||
func (m *Movie) PosterURL() string {
|
||||
// Check if the movie image exists
|
||||
if _, err := os.Stat(m.imgFile(env)); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(m.imgFile()); os.IsNotExist(err) {
|
||||
// TODO image in the config ?
|
||||
return "img/noimage.png"
|
||||
}
|
||||
return m.imgURL(env)
|
||||
return m.imgURL()
|
||||
}
|
||||
|
||||
// getPolochonMovies returns an array of the user's polochon movies
|
||||
func getPolochonMovies(user *users.User, env *web.Env) ([]*Movie, error) {
|
||||
movies := []*Movie{}
|
||||
|
||||
// Create a papi client
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return movies, err
|
||||
}
|
||||
|
||||
// Retrieve the user's polochon movies
|
||||
pmovies, err := client.GetMovies()
|
||||
if err != nil {
|
||||
return movies, err
|
||||
}
|
||||
|
||||
// Get the user's wishlisted movies
|
||||
moviesWishlist, err := backend.GetMovieWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return movies, err
|
||||
}
|
||||
|
||||
// Create Movies objects from the movies retrieved
|
||||
for _, pmovie := range pmovies.List() {
|
||||
movie := New(
|
||||
pmovie.ImdbID,
|
||||
client,
|
||||
pmovie,
|
||||
moviesWishlist.IsMovieInWishlist(pmovie.ImdbID),
|
||||
env.Config.PublicDir,
|
||||
)
|
||||
movies = append(movies, movie)
|
||||
}
|
||||
|
||||
return movies, nil
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
package movies
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattes/migrate/driver/postgres"
|
||||
"github.com/odwrtw/polochon/lib"
|
||||
"github.com/odwrtw/polochon/modules/mock"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
)
|
||||
|
||||
var db *sqlx.DB
|
||||
var pgdsn string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
pgdsn = os.Getenv("POSTGRES_DSN")
|
||||
db, err = sqlx.Connect("postgres", pgdsn)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrate(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
env := web.NewEnv(web.EnvParams{
|
||||
Database: db,
|
||||
// Auth: authorizer,
|
||||
Log: log,
|
||||
Config: &config.Config{
|
||||
PublicDir: "/tmp",
|
||||
},
|
||||
})
|
||||
detailer, _ := mock.NewDetailer(nil)
|
||||
polochonConfig := polochon.MovieConfig{
|
||||
Detailers: []polochon.Detailer{
|
||||
detailer,
|
||||
},
|
||||
}
|
||||
movie := Movie{
|
||||
Movie: polochon.Movie{
|
||||
MovieConfig: polochonConfig,
|
||||
ImdbID: "tt12345",
|
||||
Genres: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
err := movie.GetDetails(env, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get it
|
||||
movie = Movie{
|
||||
Movie: polochon.Movie{
|
||||
MovieConfig: polochonConfig,
|
||||
ImdbID: "tt12345",
|
||||
},
|
||||
}
|
||||
err = movie.Get(env)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if movie.Title != fmt.Sprintf("Movie %s", movie.ImdbID) {
|
||||
t.Fatalf("Unexpected movie's title: %s", movie.Title)
|
||||
}
|
||||
|
||||
// Delete it
|
||||
err = movie.Delete(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get it again
|
||||
err = movie.Get(env)
|
||||
if err != sql.ErrNoRows {
|
||||
t.Fatalf("Unexpected error: %q", err)
|
||||
}
|
||||
})
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
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
|
||||
}
|
@ -18,6 +18,7 @@ const (
|
||||
|
||||
var src = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
// String returns n random strings
|
||||
func String(n int) string {
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
|
@ -1,181 +1,140 @@
|
||||
package shows
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/odwrtw/papi"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
"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/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertEpisodeQuery = `
|
||||
INSERT INTO episodes (show_imdb_id, show_tvdb_id, title, season, episode, tvdb_id, aired, plot, runtime, rating, imdb_id)
|
||||
VALUES (:show_imdb_id, :show_tvdb_id, :title, :season, :episode, :tvdb_id, :aired, :plot, :runtime, :rating, :imdb_id)
|
||||
ON CONFLICT (show_imdb_id, season, episode)
|
||||
DO UPDATE
|
||||
SET show_imdb_id=:show_imdb_id, show_tvdb_id=:show_tvdb_id, title=:title,
|
||||
season=:season, episode=:episode, tvdb_id=:tvdb_id, aired=:aired,
|
||||
plot=:plot, runtime=:runtime, rating=:rating, imdb_id=:imdb_id
|
||||
RETURNING id;`
|
||||
|
||||
getEpisodesQuery = `
|
||||
SELECT *
|
||||
FROM episodes WHERE show_imdb_id=$1;`
|
||||
|
||||
getEpisodeQuery = `
|
||||
SELECT *
|
||||
FROM episodes WHERE show_imdb_id=$1 AND season=$2 AND episode=$3;`
|
||||
)
|
||||
|
||||
// Episode represents an episode
|
||||
type Episode struct {
|
||||
sqly.BaseModel
|
||||
polochon.ShowEpisode
|
||||
PolochonURL string `json:"polochon_url"`
|
||||
client *papi.Client
|
||||
pShow *papi.Show
|
||||
*polochon.ShowEpisode
|
||||
}
|
||||
|
||||
// EpisodeDB represents the Episode in the DB
|
||||
type EpisodeDB struct {
|
||||
ID string `db:"id"`
|
||||
TvdbID int `db:"tvdb_id"`
|
||||
ImdbID string `db:"imdb_id"`
|
||||
ShowImdbID string `db:"show_imdb_id"`
|
||||
ShowTvdbID int `db:"show_tvdb_id"`
|
||||
Season int `db:"season"`
|
||||
Episode int `db:"episode"`
|
||||
Title string `db:"title"`
|
||||
Rating float32 `db:"rating"`
|
||||
Plot string `db:"plot"`
|
||||
Thumb string `db:"thumb"`
|
||||
Runtime int `db:"runtime"`
|
||||
Aired string `db:"aired"`
|
||||
ReleaseGroup string `db:"release_group"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
// MarshalJSON implements the Marshal interface
|
||||
func (e *Episode) MarshalJSON() ([]byte, error) {
|
||||
type alias Episode
|
||||
|
||||
var downloadURL string
|
||||
// If the episode is present, fill the downloadURL
|
||||
if e.pShow != nil && e.pShow.HasEpisode(e.Season, e.Episode) {
|
||||
downloadURL, _ = e.client.DownloadURL(
|
||||
&papi.Episode{
|
||||
ShowImdbID: e.ShowImdbID,
|
||||
Episode: e.Episode,
|
||||
Season: e.Season,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Marshal the episode with its polochon_url
|
||||
episodeToMarshal := &struct {
|
||||
*alias
|
||||
PolochonURL string `json:"polochon_url"`
|
||||
}{
|
||||
alias: (*alias)(e),
|
||||
PolochonURL: downloadURL,
|
||||
}
|
||||
|
||||
return json.Marshal(episodeToMarshal)
|
||||
}
|
||||
|
||||
// NewEpisode returns an Episode
|
||||
func NewEpisode() *Episode {
|
||||
return &Episode{}
|
||||
}
|
||||
|
||||
// NewEpisodeDB returns an Episode ready to be put in DB from an
|
||||
// Episode
|
||||
func NewEpisodeDB(e *Episode) EpisodeDB {
|
||||
return EpisodeDB{
|
||||
ID: e.ID,
|
||||
TvdbID: e.TvdbID,
|
||||
ImdbID: e.EpisodeImdbID,
|
||||
ShowImdbID: e.ShowImdbID,
|
||||
ShowTvdbID: e.ShowTvdbID,
|
||||
Season: e.Season,
|
||||
Episode: e.Episode,
|
||||
Title: e.Title,
|
||||
Rating: e.Rating,
|
||||
Plot: e.Plot,
|
||||
Thumb: e.Thumb,
|
||||
Runtime: e.Runtime,
|
||||
Aired: e.Aired,
|
||||
ReleaseGroup: e.ReleaseGroup,
|
||||
Created: e.Created,
|
||||
Updated: e.Updated,
|
||||
func NewEpisode(client *papi.Client, pShow *papi.Show, imdbID string, season, episode int) *Episode {
|
||||
return &Episode{
|
||||
client: client,
|
||||
pShow: pShow,
|
||||
ShowEpisode: &polochon.ShowEpisode{
|
||||
ShowImdbID: imdbID,
|
||||
Season: season,
|
||||
Episode: episode,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FillFromDB returns a Show from a ShowDB extracted from the DB
|
||||
func (e *Episode) FillFromDB(eDB *EpisodeDB) {
|
||||
e.ID = eDB.ID
|
||||
e.TvdbID = eDB.TvdbID
|
||||
e.EpisodeImdbID = eDB.ImdbID
|
||||
e.ShowImdbID = eDB.ShowImdbID
|
||||
e.ShowTvdbID = eDB.ShowTvdbID
|
||||
e.Season = eDB.Season
|
||||
e.Episode = eDB.Episode
|
||||
e.Title = eDB.Title
|
||||
e.Rating = eDB.Rating
|
||||
e.Plot = eDB.Plot
|
||||
e.Thumb = eDB.Thumb
|
||||
e.Runtime = eDB.Runtime
|
||||
e.Aired = eDB.Aired
|
||||
e.Created = eDB.Created
|
||||
e.Updated = eDB.Updated
|
||||
}
|
||||
|
||||
// Upsert episode to the database
|
||||
func (e *Episode) Upsert(db *sqlx.DB) error {
|
||||
eDB := NewEpisodeDB(e)
|
||||
var id string
|
||||
r, err := db.NamedQuery(upsertEpisodeQuery, eDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for r.Next() {
|
||||
r.Scan(&id)
|
||||
}
|
||||
e.ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTorrents retrieves torrents for the show, first try to get info from db,
|
||||
// if not exists, use polochon.Torrenter and save informations in the database
|
||||
// for future use
|
||||
//
|
||||
// If force is used, the torrenter will be used even if the torrent is found in
|
||||
// GetAndFetchTorrents retrieves torrents for the episode with the given
|
||||
// torrenters 'before'
|
||||
// If found, return
|
||||
// If not, retrives torrents with the torrenters 'after' and update them in
|
||||
// database
|
||||
func (e *Episode) GetTorrents(env *web.Env, force bool) error {
|
||||
func (e *Episode) GetAndFetchTorrents(env *web.Env, before []polochon.Torrenter, after []polochon.Torrenter) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": e.ShowEpisode.ShowImdbID,
|
||||
"season": e.ShowEpisode.Season,
|
||||
"episode": e.ShowEpisode.Episode,
|
||||
"function": "shows.GetTorrents",
|
||||
"imdb_id": e.ShowImdbID,
|
||||
"season": e.Season,
|
||||
"episode": e.Episode,
|
||||
"function": "episodes.GetAndFetchTorrents",
|
||||
})
|
||||
|
||||
// Try to get details with the first batch of Detailers
|
||||
err := e.GetTorrents(env, before)
|
||||
if err == nil {
|
||||
log.Debug("episode torrents found in first try")
|
||||
return nil
|
||||
}
|
||||
log.Debugf("episode torrent not found in database: %s", err)
|
||||
|
||||
// If not found, try the second batch and upsert
|
||||
return e.RefreshTorrents(env, after)
|
||||
}
|
||||
|
||||
// GetTorrents retrieves torrents for the episode with the given torrenters
|
||||
func (e *Episode) GetTorrents(env *web.Env, torrenters []polochon.Torrenter) error {
|
||||
return GetTorrents(env, e.ShowEpisode, torrenters)
|
||||
}
|
||||
|
||||
// GetTorrents retrieves torrents for the episode with the given torrenters
|
||||
func GetTorrents(env *web.Env, showEpisode *polochon.ShowEpisode, torrenters []polochon.Torrenter) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": showEpisode.ShowImdbID,
|
||||
"season": showEpisode.Season,
|
||||
"episode": showEpisode.Episode,
|
||||
"function": "episodes.GetTorrents",
|
||||
})
|
||||
log.Debugf("getting torrents")
|
||||
|
||||
if len(e.Torrenters) == 0 {
|
||||
e.Torrenters = env.Config.ShowTorrenters
|
||||
}
|
||||
showEpisode.Torrenters = torrenters
|
||||
|
||||
episodeTorrents, err := torrents.GetEpisodeTorrents(
|
||||
env.Database,
|
||||
e.ShowEpisode.ShowImdbID,
|
||||
e.ShowEpisode.Season,
|
||||
e.ShowEpisode.Episode,
|
||||
)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("torrents found in database")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("torrent not found in database")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
// If force is not specified, don't go further
|
||||
if !force {
|
||||
log.Debugf("returning %d torrents from db", len(episodeTorrents))
|
||||
// Add the torrents to the episode
|
||||
for _, t := range episodeTorrents {
|
||||
e.Torrents = append(e.Torrents, t.Torrent)
|
||||
}
|
||||
// Will return ErrNoRows if the torrent wasn't found
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.ShowEpisode.GetTorrents(env.Log)
|
||||
err := showEpisode.GetTorrents(env.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got %d torrents from torrenters", len(e.ShowEpisode.Torrents))
|
||||
log.Debugf("got %d torrents from torrenters", len(showEpisode.Torrents))
|
||||
|
||||
for _, t := range e.ShowEpisode.Torrents {
|
||||
torrent := torrents.NewEpisodeFromPolochon(e.ShowEpisode, t)
|
||||
err = torrent.Upsert(env.Database)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RefreshTorrents refresh the episode torrents
|
||||
func (e *Episode) RefreshTorrents(env *web.Env, torrenters []polochon.Torrenter) error {
|
||||
return RefreshTorrents(env, e.ShowEpisode, torrenters)
|
||||
}
|
||||
|
||||
// RefreshTorrents refresh the episode torrents
|
||||
func RefreshTorrents(env *web.Env, showEpisode *polochon.ShowEpisode, torrenters []polochon.Torrenter) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": showEpisode.ShowImdbID,
|
||||
"season": showEpisode.Season,
|
||||
"episode": showEpisode.Episode,
|
||||
"function": "episodes.RefreshTorrents",
|
||||
})
|
||||
log.Debugf("refreshing torrents")
|
||||
|
||||
// Refresh
|
||||
err := GetTorrents(env, showEpisode, torrenters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upsert all the torrents we got
|
||||
for _, t := range showEpisode.Torrents {
|
||||
err = backend.UpsertEpisodeTorrent(env.Database, &t, showEpisode.ShowImdbID, showEpisode.Season, showEpisode.Episode)
|
||||
if err != nil {
|
||||
log.Errorf("error while adding torrent : %s", err)
|
||||
continue
|
||||
@ -185,20 +144,35 @@ func (e *Episode) GetTorrents(env *web.Env, force bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns an episode
|
||||
func (e *Episode) Get(env *web.Env) error {
|
||||
return e.GetEpisode(env.Database)
|
||||
}
|
||||
|
||||
// GetEpisode returns an episode
|
||||
func (e *Episode) GetEpisode(db *sqlx.DB) error {
|
||||
var episodeDB EpisodeDB
|
||||
err := db.QueryRowx(getEpisodeQuery, e.ShowImdbID, e.Season, e.Episode).StructScan(&episodeDB)
|
||||
// Refresh retrieves details for the episode with the given detailers
|
||||
// and update them in database
|
||||
func (e *Episode) Refresh(env *web.Env, detailers []polochon.Detailer) error {
|
||||
// Refresh
|
||||
err := e.GetEpisodeDetails(env, detailers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.FillFromDB(&episodeDB)
|
||||
return backend.UpsertEpisode(env.Database, e.ShowEpisode)
|
||||
}
|
||||
|
||||
// GetEpisodeDetails retrieves details for the episode with the given detailers
|
||||
func (e *Episode) GetEpisodeDetails(env *web.Env, detailers []polochon.Detailer) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": e.ShowImdbID,
|
||||
"function": "episodes.GetDetails",
|
||||
})
|
||||
log.Debugf("getting details")
|
||||
|
||||
e.ShowEpisode.Detailers = detailers
|
||||
|
||||
// Get the details
|
||||
err := e.ShowEpisode.GetDetails(env.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("got details from detailers")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -4,37 +4,26 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
customError "github.com/odwrtw/errors"
|
||||
"github.com/odwrtw/papi"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
"github.com/odwrtw/polochon/modules/pam"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/odwrtw/papi"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
// ErrPolochonUnavailable is an error returned if the polochon server is not available
|
||||
var ErrPolochonUnavailable = fmt.Errorf("Invalid polochon address")
|
||||
|
||||
// GetDetailsHandler retrieves details of a show
|
||||
// GetDetailsHandler handles 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 an episode
|
||||
func DetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request, force bool) error {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
@ -44,31 +33,94 @@ func DetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request, force
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
// Get the show from the polochon of the user
|
||||
pShow, err := getPolochonShow(user, s.ImdbID)
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
env.Log.Warnf("error while getting polochon show %s : %s", s.ImdbID, err)
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
// For each of the user's polochon episodes, add a direct link to it
|
||||
for _, pEpisode := range pShow.Episodes {
|
||||
for _, e := range s.Episodes {
|
||||
if e.Season != pEpisode.Season || e.Episode != pEpisode.Episode {
|
||||
continue
|
||||
}
|
||||
e.PolochonURL = pEpisode.PolochonURL
|
||||
|
||||
pShow, err := client.GetShow(id)
|
||||
if err != nil && err != papi.ErrResourceNotFound {
|
||||
log.Println("Got error getting show ", err)
|
||||
}
|
||||
|
||||
wShow, err := backend.IsShowWishlisted(env.Database, user.ID, id)
|
||||
if err != nil && err != papi.ErrResourceNotFound {
|
||||
log.Println("Got error getting wishlisted show ", err)
|
||||
}
|
||||
|
||||
s := NewWithClient(id, client, pShow, wShow, env.Config.PublicDir)
|
||||
// First try from the db
|
||||
first := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then try from the polochon detailers
|
||||
detailers := env.Config.ShowDetailers
|
||||
err = s.GetAndFetch(env, first, detailers)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
env.Log.Debug("getting episodes torrents")
|
||||
for _, e := range s.Show.Episodes {
|
||||
// Get torrents from the db
|
||||
backend := []polochon.Torrenter{env.Backend.Torrenter}
|
||||
err := GetTorrents(env, e, backend)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, s)
|
||||
}
|
||||
|
||||
// RefreshShowHandler refreshes details of a show + torrents
|
||||
func RefreshShowHandler(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"))
|
||||
}
|
||||
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
pShow, err := client.GetShow(id)
|
||||
if err != nil && err != papi.ErrResourceNotFound {
|
||||
log.Println("Got error getting show ", err)
|
||||
}
|
||||
|
||||
wShow, err := backend.IsShowWishlisted(env.Database, user.ID, id)
|
||||
if err != nil && err != papi.ErrResourceNotFound {
|
||||
log.Println("Got error getting wishlisted show ", err)
|
||||
}
|
||||
|
||||
s := NewWithClient(id, client, pShow, wShow, env.Config.PublicDir)
|
||||
// Refresh the polochon detailers
|
||||
detailers := env.Config.ShowDetailers
|
||||
err = s.Refresh(env, detailers)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
env.Log.Debug("getting episodes torrents")
|
||||
for _, e := range s.Episodes {
|
||||
// Get torrents from the db
|
||||
err := RefreshTorrents(env, e, env.Config.ShowTorrenters)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
env.Log.Debug("getting polochon show")
|
||||
|
||||
return env.RenderJSON(w, s)
|
||||
}
|
||||
|
||||
// SearchShow will search a show
|
||||
func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
var data struct {
|
||||
@ -90,23 +142,50 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
var shows []*polochon.Show
|
||||
searchers := env.Config.ShowSearchers
|
||||
// Iterate on all the searchers to search for the show
|
||||
for _, searcher := range searchers {
|
||||
result, err := searcher.SearchShow(data.Key, env.Log)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while searching show : %s", err)
|
||||
continue
|
||||
}
|
||||
// Add the results to the list of results
|
||||
shows = append(shows, result...)
|
||||
}
|
||||
|
||||
env.Log.Debugf("got %d shows doing search %q", len(shows), data.Key)
|
||||
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Get the polochon's shows
|
||||
pShows, err := client.GetShows()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Get the user's wishlisted shows
|
||||
wShows, err := backend.GetShowWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
showList := []*Show{}
|
||||
// Now iterate over all the shows to get details
|
||||
for _, s := range shows {
|
||||
show := New(s.ImdbID)
|
||||
err := show.GetDetails(env, user, false)
|
||||
pShow, _ := pShows.Has(s.ImdbID)
|
||||
wShow, _ := wShows.IsShowInWishlist(s.ImdbID)
|
||||
show := NewWithClient(s.ImdbID, client, pShow, wShow, env.Config.PublicDir)
|
||||
|
||||
// First try from the db
|
||||
first := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then try from the polochon detailers
|
||||
detailers := env.Config.ShowDetailers
|
||||
err := show.GetAndFetch(env, first, detailers)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting show details : %s", err)
|
||||
continue
|
||||
env.Log.Error(err)
|
||||
}
|
||||
showList = append(showList, show)
|
||||
}
|
||||
@ -135,8 +214,7 @@ func AddToWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
s := New(id)
|
||||
if err := s.AddToWishlist(env, user, data.Season, data.Episode); err != nil {
|
||||
if err := backend.AddShowToWishlist(env.Database, user.ID, id, data.Season, data.Episode); err != nil {
|
||||
env.Log.Warnf("Error while adding to db : %s", err)
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
@ -155,8 +233,7 @@ func DeleteFromWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
s := New(id)
|
||||
if err := s.DeleteFromWishlist(env, user); err != nil {
|
||||
if err := backend.DeleteShowFromWishlist(env.Database, user.ID, id); err != nil {
|
||||
env.Log.Warnf("Error while deleting to db : %s", err)
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
@ -172,111 +249,64 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
shows, err := GetWishlist(env, user)
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, shows)
|
||||
// Get the polochon's shows
|
||||
pShows, err := client.GetShows()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
wShows, err := backend.GetShowWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
showList := []*Show{}
|
||||
for _, wishedShow := range wShows.List() {
|
||||
pShow, _ := pShows.Has(wishedShow.ImdbID)
|
||||
show := NewWithClient(wishedShow.ImdbID, client, pShow, wishedShow, env.Config.PublicDir)
|
||||
|
||||
// First check in the DB
|
||||
before := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then with the default detailers
|
||||
after := env.Config.ShowDetailers
|
||||
err := show.GetAndFetch(env, before, after)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting show details : %s", err)
|
||||
continue
|
||||
}
|
||||
showList = append(showList, show)
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, showList)
|
||||
}
|
||||
|
||||
// getPolochonShows returns all the Shows from the polochon of a user
|
||||
func getPolochonShows(user *users.User) ([]*Show, error) {
|
||||
shows := []*Show{}
|
||||
|
||||
var polochonConfig config.UserPolochon
|
||||
err := user.GetConfig("polochon", &polochonConfig)
|
||||
if err != nil {
|
||||
return shows, err
|
||||
}
|
||||
|
||||
client, err := papi.New(polochonConfig.URL)
|
||||
if err != nil {
|
||||
return shows, err
|
||||
}
|
||||
|
||||
if polochonConfig.Token != "" {
|
||||
client.SetToken(polochonConfig.Token)
|
||||
}
|
||||
|
||||
pshows, err := client.GetShows()
|
||||
if err != nil {
|
||||
// Catch network error for accessing specified polochon address
|
||||
if uerr, ok := err.(*url.Error); ok {
|
||||
if nerr, ok := uerr.Err.(*net.OpError); ok {
|
||||
if nerr.Op == "dial" {
|
||||
return shows, ErrPolochonUnavailable
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return shows, err
|
||||
}
|
||||
for _, pshow := range pshows {
|
||||
show := New(pshow.ImdbID)
|
||||
for _, season := range pshow.Seasons {
|
||||
for _, episode := range season.Episodes {
|
||||
e := NewEpisode()
|
||||
e.Season = episode.Season
|
||||
e.Episode = episode.Episode
|
||||
e.ShowImdbID = episode.ShowImdbID
|
||||
e.PolochonURL, _ = client.DownloadURL(
|
||||
&papi.Episode{
|
||||
ShowImdbID: show.ImdbID,
|
||||
Episode: e.Episode,
|
||||
Season: e.Season,
|
||||
},
|
||||
)
|
||||
|
||||
show.Episodes = append(show.Episodes, e)
|
||||
}
|
||||
}
|
||||
shows = append(shows, show)
|
||||
}
|
||||
return shows, nil
|
||||
}
|
||||
|
||||
// getPolochonShow returns a Show with its epidodes from the polochon of a user
|
||||
func getPolochonShow(user *users.User, imdbID string) (Show, error) {
|
||||
shows, err := getPolochonShows(user)
|
||||
if err != nil {
|
||||
return Show{}, err
|
||||
}
|
||||
for _, s := range shows {
|
||||
if s.ImdbID == imdbID {
|
||||
return *s, nil
|
||||
}
|
||||
}
|
||||
return Show{}, nil
|
||||
}
|
||||
|
||||
// FromPolochon will returns shows from Polochon
|
||||
func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
// PolochonShowsHandler will returns shows from Polochon
|
||||
func PolochonShowsHandler(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 := getPolochonShows(user)
|
||||
// Get the polochon's shows
|
||||
shows, err := getPolochonShows(env, user)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
var polochonConfig config.UserPolochon
|
||||
err = user.GetConfig("polochon", &polochonConfig)
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
detailer, err := pam.New(&pam.Params{
|
||||
Endpoint: polochonConfig.URL,
|
||||
Token: polochonConfig.Token,
|
||||
})
|
||||
|
||||
// Get details in DB for each shows
|
||||
// Fetch the details if not found
|
||||
for _, s := range shows {
|
||||
s.Detailers = []polochon.Detailer{detailer}
|
||||
err := s.GetDetails(env, user, false)
|
||||
// First try from the db
|
||||
first := []polochon.Detailer{env.Backend.Detailer}
|
||||
// Then try from the polochon detailer
|
||||
detailers := env.Config.ShowDetailers
|
||||
err := s.GetAndFetch(env, first, detailers)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
}
|
||||
@ -287,11 +317,6 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
// RefreshEpisodeHandler refresh details of an episode
|
||||
func RefreshEpisodeHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
return EpisodeDetailsHandler(env, w, r, true)
|
||||
}
|
||||
|
||||
// EpisodeDetailsHandler handles details of a show
|
||||
func EpisodeDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request, force bool) error {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
@ -306,29 +331,28 @@ func EpisodeDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request,
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
s := New(id)
|
||||
e, err := s.GetEpisodeDetails(env, season, episode, force)
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
err = e.GetTorrents(env, force)
|
||||
if err != nil && customError.IsFatal(err) {
|
||||
pShow, err := client.GetShow(id)
|
||||
if err != nil && err != papi.ErrResourceNotFound {
|
||||
env.Log.Warnf("Error getting show ", err)
|
||||
}
|
||||
|
||||
e := NewEpisode(client, pShow, id, season, episode)
|
||||
// Refresh the episode
|
||||
err = e.Refresh(env, env.Config.ShowDetailers)
|
||||
if err != nil {
|
||||
env.Log.Error(err)
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
// Get the show from the polochon of the user
|
||||
pShow, err := getPolochonShow(user, id)
|
||||
// Refresh the torrents
|
||||
err = e.RefreshTorrents(env, env.Config.ShowTorrenters)
|
||||
if err != nil {
|
||||
env.Log.Warnf("error while getting polochon episode %s S%02dE%02d : %s", id, season, episode, err)
|
||||
}
|
||||
// Find if the user has a the episode in its polochon to add the
|
||||
// DownloadURL
|
||||
for _, pEpisode := range pShow.Episodes {
|
||||
if e.Season == pEpisode.Season && e.Episode == pEpisode.Episode {
|
||||
e.PolochonURL = pEpisode.PolochonURL
|
||||
break
|
||||
}
|
||||
env.Log.Error(err)
|
||||
}
|
||||
|
||||
return env.RenderJSON(w, e)
|
||||
|
@ -1,419 +1,215 @@
|
||||
package shows
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/odwrtw/papi"
|
||||
"github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertShowQuery = `
|
||||
INSERT INTO shows (imdb_id, title, rating, plot, tvdb_id, year, first_aired)
|
||||
VALUES (:imdb_id, :title, :rating, :plot, :tvdb_id, :year, :first_aired)
|
||||
ON CONFLICT (imdb_id)
|
||||
DO UPDATE
|
||||
SET imdb_id=:imdb_id, title=:title, rating=:rating, plot=:plot,
|
||||
tvdb_id=:tvdb_id, year=:year, first_aired=:first_aired
|
||||
RETURNING id;`
|
||||
|
||||
getShowQueryByImdbID = `
|
||||
SELECT *
|
||||
FROM shows WHERE imdb_id=$1;`
|
||||
|
||||
getShowQueryByID = `
|
||||
SELECT *
|
||||
FROM shows WHERE id=$1;`
|
||||
|
||||
deleteShowQueryByID = `DELETE FROM shows WHERE id=$1;`
|
||||
|
||||
getShowWithUserQueryByImdbID = `
|
||||
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 LEFT JOIN shows_tracked
|
||||
ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2
|
||||
WHERE shows.imdb_id=$1;`
|
||||
|
||||
getShowWithUserQueryByID = `
|
||||
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 LEFT JOIN shows_tracked ON shows.imdb_id=shows_tracked.imdb_id AND shows_tracked.user_id=$2
|
||||
WHERE shows.id=$1;`
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound error returned when show not found in database
|
||||
ErrNotFound = fmt.Errorf("Not found")
|
||||
)
|
||||
|
||||
// Show represents a show
|
||||
type Show struct {
|
||||
sqly.BaseModel
|
||||
polochon.Show
|
||||
Episodes []*Episode `json:"episodes"`
|
||||
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"`
|
||||
client *papi.Client
|
||||
pShow *papi.Show
|
||||
*polochon.Show
|
||||
TrackedSeason *int `json:"tracked_season"`
|
||||
TrackedEpisode *int `json:"tracked_episode"`
|
||||
publicDir string
|
||||
}
|
||||
|
||||
// ShowDB represents the Show in the DB
|
||||
type ShowDB struct {
|
||||
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
|
||||
// Show
|
||||
func NewShowDB(s *Show) ShowDB {
|
||||
sDB := ShowDB{
|
||||
ID: s.ID,
|
||||
ImdbID: s.ImdbID,
|
||||
Title: s.Title,
|
||||
Rating: s.Rating,
|
||||
Plot: s.Plot,
|
||||
TvdbID: s.TvdbID,
|
||||
Year: s.Year,
|
||||
Created: s.Created,
|
||||
Updated: s.Updated,
|
||||
// MarshalJSON implements the Marshal interface
|
||||
func (s *Show) MarshalJSON() ([]byte, error) {
|
||||
type alias Show
|
||||
// Create the structure that we want to marshal
|
||||
showToMarshal := &struct {
|
||||
*alias
|
||||
Episodes []Episode `json:"episodes"`
|
||||
BannerURL string `json:"banner_url"`
|
||||
FanartURL string `json:"fanart_url"`
|
||||
PosterURL string `json:"poster_url"`
|
||||
}{
|
||||
alias: (*alias)(s),
|
||||
BannerURL: s.GetImageURL("banner"),
|
||||
FanartURL: s.GetImageURL("fanart"),
|
||||
PosterURL: s.GetImageURL("poster"),
|
||||
}
|
||||
if s.FirstAired != nil {
|
||||
sDB.FirstAired = *s.FirstAired
|
||||
}
|
||||
return sDB
|
||||
}
|
||||
|
||||
// FillFromDB returns a Show from a ShowDB extracted from the DB
|
||||
func (s *Show) FillFromDB(sDB *ShowDB) {
|
||||
s.ID = sDB.ID
|
||||
s.ImdbID = sDB.ImdbID
|
||||
s.Title = sDB.Title
|
||||
s.Rating = sDB.Rating
|
||||
s.Plot = sDB.Plot
|
||||
s.TvdbID = sDB.TvdbID
|
||||
s.Year = sDB.Year
|
||||
s.FirstAired = &sDB.FirstAired
|
||||
s.Created = sDB.Created
|
||||
s.Updated = sDB.Updated
|
||||
s.TrackedSeason = sDB.TrackedSeason
|
||||
s.TrackedEpisode = sDB.TrackedEpisode
|
||||
// Create Episode obj from polochon.Episodes and add them to the object to
|
||||
// marshal
|
||||
for _, e := range s.Show.Episodes {
|
||||
showToMarshal.Episodes = append(showToMarshal.Episodes, Episode{
|
||||
ShowEpisode: e,
|
||||
client: s.client,
|
||||
pShow: s.pShow,
|
||||
})
|
||||
}
|
||||
return json.Marshal(showToMarshal)
|
||||
}
|
||||
|
||||
// New returns a new Show with a polochon ShowConfig
|
||||
func New(imdbID string) *Show {
|
||||
return &Show{
|
||||
Show: polochon.Show{
|
||||
Show: &polochon.Show{
|
||||
ImdbID: imdbID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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(getShowWithUserQueryByID, s.ID, user.ID).StructScan(&sDB)
|
||||
} else if s.ImdbID != "" {
|
||||
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")
|
||||
// NewWithClient returns a new Show with a polochon ShowConfig
|
||||
func NewWithClient(imdbID string, client *papi.Client, pShow *papi.Show, wShow *backend.WishedShow, publicDir string) *Show {
|
||||
s := &Show{
|
||||
Show: &polochon.Show{
|
||||
ImdbID: imdbID,
|
||||
},
|
||||
client: client,
|
||||
pShow: pShow,
|
||||
publicDir: publicDir,
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
if wShow != nil {
|
||||
s.TrackedSeason = &wShow.Season
|
||||
s.TrackedEpisode = &wShow.Episode
|
||||
}
|
||||
// Set the poster url
|
||||
s.PosterURL = s.GetPosterURL(env)
|
||||
|
||||
s.FillFromDB(&sDB)
|
||||
return nil
|
||||
return s
|
||||
}
|
||||
|
||||
// GetShow returns a show with user info like tracked
|
||||
func (s *Show) GetShow(db *sqlx.DB) error {
|
||||
var err error
|
||||
var sDB ShowDB
|
||||
if s.ID != "" {
|
||||
err = db.QueryRowx(getShowQueryByID, s.ID).StructScan(&sDB)
|
||||
} else if s.ImdbID != "" {
|
||||
err = db.QueryRowx(getShowQueryByImdbID, s.ImdbID).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)
|
||||
|
||||
s.FillFromDB(&sDB)
|
||||
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, user *users.User, force bool) error {
|
||||
// GetDetails retrieves details for the show with the given detailers
|
||||
func (s *Show) GetDetails(env *web.Env, detailers []polochon.Detailer) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": s.ImdbID,
|
||||
"function": "shows.GetDetails",
|
||||
})
|
||||
log.Debugf("getting details")
|
||||
|
||||
if len(s.Detailers) == 0 {
|
||||
s.Detailers = env.Config.ShowDetailers
|
||||
var detailersName []string
|
||||
for _, d := range detailers {
|
||||
detailersName = append(detailersName, d.Name())
|
||||
}
|
||||
log.Debugf("getting details with %s", strings.Join(detailersName, ", "))
|
||||
|
||||
var err error
|
||||
err = s.Get(env, user)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("show found in database")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("show not found in database")
|
||||
default:
|
||||
// Unexpected error
|
||||
return err
|
||||
}
|
||||
// If force is not specified, don't go further
|
||||
if !force {
|
||||
// Will return ErrNoRows if the show wasn't found
|
||||
return err
|
||||
}
|
||||
s.Detailers = detailers
|
||||
|
||||
// GetDetail
|
||||
err = s.Show.GetDetails(env.Log)
|
||||
// Get the details
|
||||
err := s.Show.GetDetails(env.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("got details from detailers")
|
||||
s.Episodes = []*Episode{}
|
||||
for _, pe := range s.Show.Episodes {
|
||||
s.Episodes = append(s.Episodes, &Episode{ShowEpisode: *pe})
|
||||
}
|
||||
|
||||
err = s.Upsert(env.Database)
|
||||
if err != nil {
|
||||
log.Debug("error while doing show upsert func", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("show added in database")
|
||||
|
||||
// Download show images
|
||||
s.downloadImages(env)
|
||||
|
||||
log.Debug("images downloaded")
|
||||
|
||||
// Set the poster url
|
||||
s.PosterURL = s.GetPosterURL(env)
|
||||
log.Debugf("got details from detailers ")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPosterURL returns the image URL or the default image if the poster is not yet downloaded
|
||||
func (s *Show) GetPosterURL(env *web.Env) string {
|
||||
// GetAndFetch retrieves details for the show with the given
|
||||
// detailers 'before'
|
||||
// If found, return
|
||||
// If not, retrives details with the detailers 'after' and update them in
|
||||
// database
|
||||
func (s *Show) GetAndFetch(env *web.Env, before []polochon.Detailer, after []polochon.Detailer) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": s.ImdbID,
|
||||
"function": "shows.GetAndFetch",
|
||||
})
|
||||
|
||||
// Try to get details with the first batch of Detailers
|
||||
err := s.GetDetails(env, before)
|
||||
if err == nil {
|
||||
log.Debug("show found in first try")
|
||||
return nil
|
||||
}
|
||||
log.Debugf("show not found in database: %s", err)
|
||||
|
||||
// If not found, try the second batch and upsert
|
||||
return s.Refresh(env, after)
|
||||
}
|
||||
|
||||
// Refresh retrieves details for the show with the given detailers
|
||||
// and update them in database
|
||||
func (s *Show) Refresh(env *web.Env, detailers []polochon.Detailer) error {
|
||||
// Refresh
|
||||
err := s.GetDetails(env, detailers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Download show images
|
||||
s.downloadImages(env)
|
||||
|
||||
env.Log.Debug("images downloaded")
|
||||
|
||||
// If found, update in database
|
||||
return backend.UpsertShow(env.Database, s.Show)
|
||||
}
|
||||
|
||||
// GetImageURL returns the image URL or the default image if the poster is not yet downloaded
|
||||
func (s *Show) GetImageURL(imgType string) string {
|
||||
// Check if the show image exists
|
||||
if _, err := os.Stat(s.imgFile(env, "poster")); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(s.imgFile(imgType)); os.IsNotExist(err) {
|
||||
// TODO image in the config ?
|
||||
return "img/noimage.png"
|
||||
}
|
||||
return s.imgURL(env, "poster")
|
||||
return s.imgURL(imgType)
|
||||
}
|
||||
|
||||
// downloadImages will download the show images
|
||||
func (s *Show) downloadImages(env *web.Env) {
|
||||
// Download the banner
|
||||
err := web.Download(s.Show.Banner, s.imgFile(env, "banner"))
|
||||
err := web.Download(s.Show.Banner, s.imgFile("banner"))
|
||||
if err != nil {
|
||||
env.Log.Errorf("failed to dowload banner: %s", err)
|
||||
}
|
||||
err = web.Download(s.Show.Fanart, s.imgFile(env, "fanart"))
|
||||
err = web.Download(s.Show.Fanart, s.imgFile("fanart"))
|
||||
if err != nil {
|
||||
env.Log.Errorf("failed to dowload fanart: %s", err)
|
||||
}
|
||||
err = web.Download(s.Show.Poster, s.imgFile(env, "poster"))
|
||||
err = web.Download(s.Show.Poster, s.imgFile("poster"))
|
||||
if err != nil {
|
||||
env.Log.Errorf("failed to dowload poster: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// imgFile returns the image location on disk
|
||||
func (s *Show) imgFile(env *web.Env, imgType string) string {
|
||||
func (s *Show) imgFile(imgType string) string {
|
||||
fileURL := fmt.Sprintf("img/shows/%s-%s.jpg", s.ImdbID, imgType)
|
||||
return filepath.Join(env.Config.PublicDir, fileURL)
|
||||
return filepath.Join(s.publicDir, fileURL)
|
||||
}
|
||||
|
||||
// imgURL returns the default image url
|
||||
func (s *Show) imgURL(env *web.Env, imgType string) string {
|
||||
func (s *Show) imgURL(imgType string) string {
|
||||
return fmt.Sprintf("img/shows/%s-%s.jpg", s.ImdbID, imgType)
|
||||
}
|
||||
|
||||
// Upsert a show in the database
|
||||
func (s *Show) Upsert(db *sqlx.DB) error {
|
||||
sDB := NewShowDB(s)
|
||||
var id string
|
||||
r, err := db.NamedQuery(upsertShowQuery, sDB)
|
||||
// getPolochonShows returns all the Shows from the polochon of a user
|
||||
func getPolochonShows(env *web.Env, user *users.User) ([]*Show, error) {
|
||||
shows := []*Show{}
|
||||
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return err
|
||||
return shows, err
|
||||
}
|
||||
for r.Next() {
|
||||
r.Scan(&id)
|
||||
}
|
||||
s.ID = id
|
||||
|
||||
for _, e := range s.Episodes {
|
||||
e.ShowImdbID = s.ImdbID
|
||||
err = e.Upsert(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete show from database
|
||||
func (s *Show) Delete(db *sqlx.DB) error {
|
||||
r, err := db.Exec(deleteShowQueryByID, s.ID)
|
||||
// Get the polochon's shows
|
||||
pshows, err := client.GetShows()
|
||||
if err != nil {
|
||||
return err
|
||||
return shows, err
|
||||
}
|
||||
count, _ := r.RowsAffected()
|
||||
if count != 1 {
|
||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEpisodes from database
|
||||
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)
|
||||
wShows, err := backend.GetShowWishlist(env.Database, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(episodesDB) == 0 {
|
||||
return nil
|
||||
return shows, err
|
||||
}
|
||||
|
||||
for _, episodeDB := range episodesDB {
|
||||
episode := NewEpisode()
|
||||
episode.FillFromDB(episodeDB)
|
||||
s.Episodes = append(s.Episodes, episode)
|
||||
err = episode.GetTorrents(env, force)
|
||||
if err != nil {
|
||||
env.Log.Debugf("error while getting episode torrent: %q", err)
|
||||
}
|
||||
// Create Shows objects from the shows retrieved
|
||||
for _, pShow := range pshows.List() {
|
||||
wShow, _ := wShows.IsShowInWishlist(pShow.ImdbID)
|
||||
show := NewWithClient(pShow.ImdbID, client, pShow, wShow, env.Config.PublicDir)
|
||||
shows = append(shows, show)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEpisode from database
|
||||
func (s *Show) GetEpisodeDetails(env *web.Env, seasonNb, episodeNb int, force bool) (*Episode, error) {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"imdb_id": s.ImdbID,
|
||||
"season": seasonNb,
|
||||
"episode": episodeNb,
|
||||
"function": "show.GetEpisodeDetails",
|
||||
})
|
||||
log.Debugf("getting episode details")
|
||||
|
||||
e := NewEpisode()
|
||||
e.ShowImdbID = s.ImdbID
|
||||
e.Season = seasonNb
|
||||
e.Episode = episodeNb
|
||||
|
||||
if len(e.Detailers) == 0 {
|
||||
e.Detailers = env.Config.ShowDetailers
|
||||
}
|
||||
|
||||
var err error
|
||||
err = e.Get(env)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Debug("episode found in database")
|
||||
case sql.ErrNoRows:
|
||||
log.Debug("episode not found in database")
|
||||
default:
|
||||
// Unexpected error
|
||||
return nil, err
|
||||
}
|
||||
// If force is not specified, don't go further
|
||||
if !force {
|
||||
// Will return ErrNoRows if the episode wasn't found
|
||||
return e, err
|
||||
}
|
||||
|
||||
// GetDetail of the episode
|
||||
err = e.ShowEpisode.GetDetails(env.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Upsert the episode
|
||||
err = e.Upsert(env.Database)
|
||||
if err != nil {
|
||||
log.Debug("error while doing episode upsert func", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("episode inserted/updated in database")
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// GetTorrents from the database or fetch them if needed
|
||||
func (s *Show) GetTorrents(env *web.Env, force bool) error {
|
||||
for _, e := range s.Episodes {
|
||||
err := e.GetTorrents(env, force)
|
||||
if err != nil {
|
||||
env.Log.Errorf("error while getting episode torrent: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return shows, nil
|
||||
}
|
||||
|
@ -1,187 +0,0 @@
|
||||
package shows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattes/migrate/driver/postgres"
|
||||
"github.com/odwrtw/polochon/lib"
|
||||
"github.com/odwrtw/polochon/modules/mock"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
)
|
||||
|
||||
var db *sqlx.DB
|
||||
var pgdsn string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
pgdsn = os.Getenv("POSTGRES_DSN")
|
||||
db, err = sqlx.Connect("postgres", pgdsn)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrate(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
detailer, _ := mock.NewDetailer(nil)
|
||||
polochonConfig := polochon.ShowConfig{
|
||||
Detailers: []polochon.Detailer{
|
||||
detailer,
|
||||
},
|
||||
}
|
||||
|
||||
show := Show{
|
||||
Show: polochon.Show{
|
||||
ShowConfig: polochonConfig,
|
||||
ImdbID: "tt12345",
|
||||
},
|
||||
}
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
err := show.GetDetails(db, log)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(show.Episodes) != 50 {
|
||||
t.Fatalf("Unexpected number of episodes: %d", len(show.Episodes))
|
||||
}
|
||||
|
||||
// Get from db
|
||||
show2 := Show{
|
||||
Show: polochon.Show{
|
||||
ShowConfig: polochonConfig,
|
||||
ImdbID: "tt12345",
|
||||
},
|
||||
}
|
||||
|
||||
err = show2.Get(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = show2.GetEpisodes(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(show2.Episodes) != 50 {
|
||||
t.Fatalf("Unexpected number of episodes: %d", len(show2.Episodes))
|
||||
}
|
||||
|
||||
err = show.Delete(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = show.Get(db)
|
||||
if err != ErrNotFound {
|
||||
t.Fatalf("Unexpected error: %q", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UserBackend represents the data backend to get the user
|
||||
type UserBackend struct {
|
||||
Database *sqlx.DB
|
||||
}
|
||||
|
||||
// Get gets the username from the UserBackend
|
||||
func (b *UserBackend) Get(username string) (auth.User, error) {
|
||||
return users.Get(b.Database, username)
|
||||
}
|
||||
|
||||
func getEnv(db *sqlx.DB) *web.Env {
|
||||
uBackend := &UserBackend{Database: db}
|
||||
|
||||
authParams := auth.Params{
|
||||
Backend: uBackend,
|
||||
Pepper: "pepper",
|
||||
Cost: 10,
|
||||
Secret: "secret",
|
||||
}
|
||||
authorizer := auth.New(authParams)
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
env := web.NewEnv(web.EnvParams{
|
||||
Database: db,
|
||||
Auth: authorizer,
|
||||
Log: log,
|
||||
// Config: cf,
|
||||
})
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func TestTrackedShow(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
detailer, _ := mock.NewDetailer(nil)
|
||||
polochonConfig := polochon.ShowConfig{
|
||||
Detailers: []polochon.Detailer{
|
||||
detailer,
|
||||
},
|
||||
}
|
||||
|
||||
show := Show{
|
||||
Show: polochon.Show{
|
||||
ShowConfig: polochonConfig,
|
||||
ImdbID: "tt12345",
|
||||
},
|
||||
}
|
||||
|
||||
err := show.Upsert(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
u := &users.User{Name: "plop"}
|
||||
env := getEnv(db)
|
||||
|
||||
u.Hash, err = env.Auth.GenHash("pass")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.NewConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.Add(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = show.GetAsUser(db, u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if show.IsTracked() {
|
||||
t.Fatal("Tracked must be false here")
|
||||
}
|
||||
|
||||
q := `INSERT INTO shows_tracked (show_id, user_id, season, episode) VALUES ($1, $2, $3, $4);`
|
||||
_, err = db.Exec(q, show.ID, u.ID, 1, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = show.GetAsUser(db, u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !show.IsTracked() {
|
||||
t.Fatal("Tracked must be true here")
|
||||
}
|
||||
})
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
package torrents
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertEpisodeTorrentQuery = `
|
||||
INSERT INTO episode_torrents (imdb_id, url, source, quality, upload_user,
|
||||
season, episode, seeders, leechers)
|
||||
VALUES (:imdb_id, :url, :source, :quality, :upload_user, :season, :episode,
|
||||
:seeders, :leechers)
|
||||
ON CONFLICT (imdb_id, season, episode, quality, source)
|
||||
DO UPDATE SET imdb_id=:imdb_id, url=:url, source=:source, quality=:quality,
|
||||
upload_user=:upload_user, season=:season, episode=:episode,
|
||||
seeders=:seeders, leechers=:leechers
|
||||
RETURNING id;`
|
||||
|
||||
getEpisodeTorrentQuery = `
|
||||
SELECT *
|
||||
FROM episode_torrents WHERE imdb_id=$1 AND season=$2 AND episode=$3;`
|
||||
|
||||
getEpisodeTorrentQueryByID = `
|
||||
SELECT *
|
||||
FROM episode_torrents WHERE id=$1;`
|
||||
|
||||
deleteEpisodeTorrentQuery = `DELETE FROM movie_torrents WHERE id=$1;`
|
||||
)
|
||||
|
||||
// EpisodeTorrent represents an episode torrent
|
||||
type EpisodeTorrent struct {
|
||||
sqly.BaseModel
|
||||
polochon.Torrent
|
||||
ShowImdbID string `json:"show_imdb_id"`
|
||||
Season int `json:"season"`
|
||||
Episode int `json:"episode"`
|
||||
}
|
||||
|
||||
// EpisodeTorrentDB represents the EpisodeTorrent in the DB
|
||||
type EpisodeTorrentDB struct {
|
||||
ID string `db:"id"`
|
||||
ImdbID string `db:"imdb_id"`
|
||||
URL string `db:"url"`
|
||||
Source string `db:"source"`
|
||||
Quality string `db:"quality"`
|
||||
UploadUser string `db:"upload_user"`
|
||||
Season int `db:"season"`
|
||||
Episode int `db:"episode"`
|
||||
Seeders int `db:"seeders"`
|
||||
Leechers int `db:"leechers"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// NewEpisodeFromPolochon returns a new EpisodeTorrent from a polochon.ShowEpisode
|
||||
func NewEpisodeFromPolochon(se polochon.ShowEpisode, poTo polochon.Torrent) *EpisodeTorrent {
|
||||
return &EpisodeTorrent{
|
||||
ShowImdbID: se.ShowImdbID,
|
||||
Season: se.Season,
|
||||
Episode: se.Episode,
|
||||
Torrent: poTo,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEpisode returns a new EpisodeTorrent
|
||||
func NewEpisode() *EpisodeTorrent {
|
||||
return &EpisodeTorrent{}
|
||||
}
|
||||
|
||||
// GetEpisodeTorrents returns show details in database from id or imdbid or an error
|
||||
func GetEpisodeTorrents(db *sqlx.DB, imdbID string, season, episode int) ([]*EpisodeTorrent, error) {
|
||||
var torrentsDB = []*EpisodeTorrentDB{}
|
||||
err := db.Select(&torrentsDB, getEpisodeTorrentQuery, imdbID, season, episode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(torrentsDB) == 0 {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
var torrents []*EpisodeTorrent
|
||||
for _, torrentDB := range torrentsDB {
|
||||
episode := NewEpisode()
|
||||
episode.FillFromDB(torrentDB)
|
||||
torrents = append(torrents, episode)
|
||||
}
|
||||
|
||||
return torrents, nil
|
||||
}
|
||||
|
||||
// Delete episode from database
|
||||
func (e *EpisodeTorrent) Delete(db *sqlx.DB) error {
|
||||
r, err := db.Exec(deleteEpisodeTorrentQuery, e.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count, _ := r.RowsAffected()
|
||||
if count != 1 {
|
||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillFromDB will fill a MovieTorrent from a MovieTorrentDB extracted from the DB
|
||||
func (e *EpisodeTorrent) FillFromDB(eDB *EpisodeTorrentDB) {
|
||||
q, _ := polochon.StringToQuality(eDB.Quality)
|
||||
e.ShowImdbID = eDB.ImdbID
|
||||
e.Created = eDB.Created
|
||||
e.Updated = eDB.Updated
|
||||
e.ID = eDB.ID
|
||||
e.URL = eDB.URL
|
||||
e.Source = eDB.Source
|
||||
e.Quality = *q
|
||||
e.UploadUser = eDB.UploadUser
|
||||
e.Seeders = eDB.Seeders
|
||||
e.Leechers = eDB.Leechers
|
||||
}
|
||||
|
||||
// Upsert an episode torrent in the database
|
||||
func (e *EpisodeTorrent) Upsert(db *sqlx.DB) error {
|
||||
eDB := NewEpisodeTorrentDB(e)
|
||||
var id string
|
||||
r, err := db.NamedQuery(upsertEpisodeTorrentQuery, eDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for r.Next() {
|
||||
r.Scan(&id)
|
||||
}
|
||||
e.ID = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewEpisodeTorrentDB returns an EpisodeTorrent ready to be put in DB from a
|
||||
// EspisodeTorrent
|
||||
func NewEpisodeTorrentDB(e *EpisodeTorrent) EpisodeTorrentDB {
|
||||
return EpisodeTorrentDB{
|
||||
ID: e.ID,
|
||||
ImdbID: e.ShowImdbID,
|
||||
Season: e.Season,
|
||||
Episode: e.Episode,
|
||||
URL: e.URL,
|
||||
Source: e.Source,
|
||||
Quality: string(e.Quality),
|
||||
UploadUser: e.UploadUser,
|
||||
Seeders: e.Seeders,
|
||||
Leechers: e.Leechers,
|
||||
Created: e.Created,
|
||||
Updated: e.Updated,
|
||||
}
|
||||
}
|
@ -6,11 +6,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
|
||||
"github.com/odwrtw/papi"
|
||||
)
|
||||
|
||||
// DownloadHandler downloads a movie via polochon
|
||||
@ -33,19 +30,9 @@ func DownloadHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error
|
||||
return env.RenderError(w, errors.New("invalid user type"))
|
||||
}
|
||||
|
||||
var polochonConfig config.UserPolochon
|
||||
err = user.GetConfig("polochon", &polochonConfig)
|
||||
client, err := user.NewPapiClient()
|
||||
if err != nil {
|
||||
return env.RenderError(w, errors.New("problem when getting user config"))
|
||||
}
|
||||
|
||||
client, err := papi.New(polochonConfig.URL)
|
||||
if err != nil {
|
||||
return env.RenderError(w, errors.New("problem when getting papi client"))
|
||||
}
|
||||
|
||||
if polochonConfig.Token != "" {
|
||||
client.SetToken(polochonConfig.Token)
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
err = client.AddTorrent(data.URL)
|
||||
|
@ -1,171 +0,0 @@
|
||||
package torrents
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
upsertMovieTorrentQuery = `
|
||||
INSERT INTO movie_torrents (imdb_id, url, source, quality, upload_user,
|
||||
seeders, leechers)
|
||||
VALUES (:imdb_id, :url, :source, :quality, :upload_user, :seeders,
|
||||
:leechers)
|
||||
ON CONFLICT (imdb_id, quality, source)
|
||||
DO UPDATE SET imdb_id=:imdb_id, url=:url, source=:source, quality=:quality,
|
||||
upload_user=:upload_user, seeders=:seeders, leechers=:leechers
|
||||
RETURNING id;`
|
||||
|
||||
getMovieTorrentQueryByImdbID = `
|
||||
SELECT *
|
||||
FROM movie_torrents WHERE imdb_id=$1;`
|
||||
|
||||
getMovieTorrentQueryByID = `
|
||||
SELECT *
|
||||
FROM movie_torrents WHERE id=$1;`
|
||||
|
||||
deleteMovieTorrentQuery = `DELETE FROM movie_torrents WHERE id=$1;`
|
||||
)
|
||||
|
||||
// MovieTorrent represents a movie torrent
|
||||
type MovieTorrent struct {
|
||||
sqly.BaseModel
|
||||
polochon.Torrent
|
||||
ImdbID string `json:"imdb_id"`
|
||||
}
|
||||
|
||||
// MovieTorrentDB represents the MovieTorrent in the DB
|
||||
type MovieTorrentDB struct {
|
||||
ID string `db:"id"`
|
||||
ImdbID string `db:"imdb_id"`
|
||||
URL string `db:"url"`
|
||||
Source string `db:"source"`
|
||||
Quality string `db:"quality"`
|
||||
UploadUser string `db:"upload_user"`
|
||||
Seeders int `db:"seeders"`
|
||||
Leechers int `db:"leechers"`
|
||||
Created time.Time `db:"created_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// NewMovieFromImDB returns a new MovieTorrent with an ImDB id
|
||||
func NewMovieFromImDB(imdbID string) *MovieTorrent {
|
||||
return &MovieTorrent{
|
||||
ImdbID: imdbID,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMovie returns a new MovieTorrent with an ImDB id
|
||||
func NewMovie(imdbID string, poTo polochon.Torrent) *MovieTorrent {
|
||||
return &MovieTorrent{
|
||||
ImdbID: imdbID,
|
||||
Torrent: poTo,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns show details in database from id or imdbid or an error
|
||||
func (m *MovieTorrent) Get(db *sqlx.DB) error {
|
||||
var mDB MovieTorrentDB
|
||||
var err error
|
||||
if m.ID != "" {
|
||||
err = db.QueryRowx(getMovieTorrentQueryByID, m.ID).StructScan(&mDB)
|
||||
} else if m.ImdbID != "" {
|
||||
err = db.QueryRowx(getMovieTorrentQueryByImdbID, m.ImdbID).StructScan(&mDB)
|
||||
} else {
|
||||
err = fmt.Errorf("can't get movie torrent details, you have to specify an ID or ImdbID")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.FillFromDB(&mDB)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMovieTorrents returns show details in database from id or imdbid or an error
|
||||
func GetMovieTorrents(db *sqlx.DB, imdbID string) ([]*MovieTorrent, error) {
|
||||
var torrentsDB = []*MovieTorrentDB{}
|
||||
err := db.Select(&torrentsDB, getMovieTorrentQueryByImdbID, imdbID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(torrentsDB) == 0 {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
var torrents []*MovieTorrent
|
||||
for _, torrentDB := range torrentsDB {
|
||||
movie := NewMovieFromImDB(imdbID)
|
||||
movie.FillFromDB(torrentDB)
|
||||
torrents = append(torrents, movie)
|
||||
}
|
||||
|
||||
return torrents, nil
|
||||
}
|
||||
|
||||
// Upsert a movie torrent in the database
|
||||
func (m *MovieTorrent) Upsert(db *sqlx.DB) error {
|
||||
mDB := NewMovieTorrentDB(m)
|
||||
var id string
|
||||
r, err := db.NamedQuery(upsertMovieTorrentQuery, mDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for r.Next() {
|
||||
r.Scan(&id)
|
||||
}
|
||||
m.ID = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete movie from database
|
||||
func (m *MovieTorrent) Delete(db *sqlx.DB) error {
|
||||
r, err := db.Exec(deleteMovieTorrentQuery, m.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count, _ := r.RowsAffected()
|
||||
if count != 1 {
|
||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMovieTorrentDB returns a MovieTorrent ready to be put in DB from a
|
||||
// MovieTorrent
|
||||
func NewMovieTorrentDB(m *MovieTorrent) MovieTorrentDB {
|
||||
return MovieTorrentDB{
|
||||
ID: m.ID,
|
||||
ImdbID: m.ImdbID,
|
||||
URL: m.URL,
|
||||
Source: m.Source,
|
||||
Quality: string(m.Quality),
|
||||
UploadUser: m.UploadUser,
|
||||
Seeders: m.Seeders,
|
||||
Leechers: m.Leechers,
|
||||
Created: m.Created,
|
||||
Updated: m.Updated,
|
||||
}
|
||||
}
|
||||
|
||||
// FillFromDB will fill a MovieTorrent from a MovieTorrentDB extracted from the DB
|
||||
func (m *MovieTorrent) FillFromDB(mDB *MovieTorrentDB) {
|
||||
q, _ := polochon.StringToQuality(mDB.Quality)
|
||||
m.ImdbID = mDB.ImdbID
|
||||
m.Created = mDB.Created
|
||||
m.Updated = mDB.Updated
|
||||
m.ID = mDB.ID
|
||||
m.URL = mDB.URL
|
||||
m.Source = mDB.Source
|
||||
m.Quality = *q
|
||||
m.UploadUser = mDB.UploadUser
|
||||
m.Seeders = mDB.Seeders
|
||||
m.Leechers = mDB.Leechers
|
||||
}
|
@ -13,6 +13,7 @@ import (
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
)
|
||||
|
||||
// SignupPOSTHandler handles the user's Signup
|
||||
func SignupPOSTHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
var data struct {
|
||||
Username string `json:"username"`
|
||||
|
@ -2,10 +2,14 @@ package users
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
"github.com/odwrtw/papi"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/random"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
)
|
||||
@ -23,7 +27,9 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
UserRole = "user"
|
||||
// UserRole represents the user's role
|
||||
UserRole = "user"
|
||||
// AdminRole represents the admin's role
|
||||
AdminRole = "admin"
|
||||
)
|
||||
|
||||
@ -89,6 +95,25 @@ func (u *User) NewConfig() error {
|
||||
return u.RawConfig.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// NewPapiClient creates a new papi client for the given user
|
||||
func (u *User) NewPapiClient() (*papi.Client, error) {
|
||||
var polochonConfig config.UserPolochon
|
||||
err := u.GetConfig("polochon", &polochonConfig)
|
||||
if err != nil {
|
||||
return nil, errors.New("missing polochon config")
|
||||
}
|
||||
|
||||
client, err := papi.New(polochonConfig.URL)
|
||||
if err != nil {
|
||||
return nil, errors.New("error getting papi client")
|
||||
}
|
||||
|
||||
if polochonConfig.Token != "" {
|
||||
client.SetToken(polochonConfig.Token)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Token represents a token
|
||||
type Token struct {
|
||||
sqly.BaseModel
|
||||
@ -201,6 +226,7 @@ func (u *User) GetName() string {
|
||||
return u.Name
|
||||
}
|
||||
|
||||
// HasRole checks if a user as a role
|
||||
func (u *User) HasRole(role string) bool {
|
||||
if role == AdminRole && !u.Admin {
|
||||
return false
|
||||
@ -208,6 +234,7 @@ func (u *User) HasRole(role string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsAdmin checks if a user is admin
|
||||
func (u *User) IsAdmin() bool {
|
||||
return u.HasRole(AdminRole)
|
||||
}
|
||||
|
@ -1,337 +0,0 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
_ "github.com/mattes/migrate/driver/postgres"
|
||||
)
|
||||
|
||||
var db *sqlx.DB
|
||||
var pgdsn string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
pgdsn = os.Getenv("POSTGRES_DSN")
|
||||
db, err = sqlx.Connect("postgres", pgdsn)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Unavailable PG tests:\n %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
|
||||
// Add a new user
|
||||
u := &User{Name: "plop", Hash: "plop"}
|
||||
var err error
|
||||
env := getEnv(db)
|
||||
|
||||
u.Hash, err = env.Auth.GenHash("test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.NewConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = u.Add(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Try add it twice
|
||||
err = u.Add(db)
|
||||
if err != nil {
|
||||
if err, ok := err.(*pq.Error); ok {
|
||||
if err.Code.Name() != "unique_violation" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get it
|
||||
u2, err := Get(db, "plop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if u2.ID != u.ID {
|
||||
t.Fatal("ID are different")
|
||||
}
|
||||
|
||||
// Update it
|
||||
u2.Name = "toto"
|
||||
err = u2.Update(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
u3, err := Get(db, "toto")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if u3.ID != u2.ID {
|
||||
t.Fatal("ID are different")
|
||||
}
|
||||
if u3.Name != "toto" {
|
||||
t.Fatalf("Unexpected name %q", u3.Name)
|
||||
}
|
||||
|
||||
// Delete it
|
||||
err = u3.Delete(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
u, err = Get(db, "toto")
|
||||
if err == nil {
|
||||
t.Fatal("We expect an error here, the user didn't exist anymore")
|
||||
}
|
||||
if err != ErrUnknownUser {
|
||||
t.Fatalf("Unexpected error: %q", err)
|
||||
}
|
||||
if u != nil {
|
||||
t.Fatal("User have to be nil here")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTokenAddDelete(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
// Add a new user
|
||||
u := &User{Name: "plop", Hash: "plop"}
|
||||
var err error
|
||||
env := getEnv(db)
|
||||
|
||||
u.Hash, err = env.Auth.GenHash("test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.NewConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = u.Add(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add many token
|
||||
_, err = u.NewToken(db)
|
||||
_, err = u.NewToken(db)
|
||||
token, err := u.NewToken(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get token
|
||||
tokens, err := u.GetTokens(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tokens) != 3 {
|
||||
t.Fatalf("Unexpected number of token: %q", len(tokens))
|
||||
}
|
||||
|
||||
// Delete token
|
||||
err = u.DeleteToken(db, token.Value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tokens, _ = u.GetTokens(db)
|
||||
if len(tokens) != 2 {
|
||||
t.Fatalf("Unexpected number of token: %q", len(tokens))
|
||||
}
|
||||
|
||||
// Delete a fake token
|
||||
err = u.DeleteToken(db, "plop")
|
||||
if err == nil {
|
||||
t.Fatalf("We expect an error when try to delete a fake token")
|
||||
}
|
||||
if err.Error() != "Unexpected number of row deleted: 0" {
|
||||
t.Fatalf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
// Remove user and test auto delete token
|
||||
u.Delete(db)
|
||||
q := `SELECT count(*) FROM tokens WHERE user_id=$1;`
|
||||
var count int
|
||||
err = db.QueryRowx(q, u.ID).Scan(&count)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("Unexpected number of token: %d", count)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// UserBackend represents the data backend to get the user
|
||||
type UserBackend struct {
|
||||
Database *sqlx.DB
|
||||
}
|
||||
|
||||
// Get gets the username from the UserBackend
|
||||
func (b *UserBackend) Get(username string) (auth.User, error) {
|
||||
return Get(b.Database, username)
|
||||
}
|
||||
|
||||
func getEnv(db *sqlx.DB) *web.Env {
|
||||
uBackend := &UserBackend{Database: db}
|
||||
|
||||
authParams := auth.Params{
|
||||
Backend: uBackend,
|
||||
Pepper: "pepper",
|
||||
Cost: 10,
|
||||
Secret: "secret",
|
||||
}
|
||||
authorizer := auth.New(authParams)
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
env := web.NewEnv(web.EnvParams{
|
||||
Database: db,
|
||||
Auth: authorizer,
|
||||
Log: log,
|
||||
// Config: cf,
|
||||
})
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func TestTokenCheck(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
u := &User{Name: "plop", Hash: "plop"}
|
||||
var err error
|
||||
env := getEnv(db)
|
||||
|
||||
u.Hash, err = env.Auth.GenHash("test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.NewConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = u.Add(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token, err := u.NewToken(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := u.CheckToken(db, token.Value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("Token not found")
|
||||
}
|
||||
|
||||
// test fake token
|
||||
ok, err = u.CheckToken(db, "plop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("Token found otherwise we don't expect")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestAutoUpdateCols(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
u := &User{Name: "plop"}
|
||||
var err error
|
||||
env := getEnv(db)
|
||||
|
||||
u.Hash, err = env.Auth.GenHash("pass")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.NewConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.Add(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u.Name = "toto"
|
||||
err = u.Update(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !u.Created.Before(u.Updated) {
|
||||
t.Fatalf("colum updated not auto updated on table users")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
sqly.RunWithLastestMigration(db, pgdsn, t, func(db *sqlx.DB, t *testing.T) {
|
||||
u := &User{Name: "plop", Hash: "plop"}
|
||||
env := getEnv(db)
|
||||
|
||||
var err error
|
||||
u.Hash, err = env.Auth.GenHash("pass")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.NewConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = u.Add(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
u, err = Get(db, "plop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
type test struct {
|
||||
Test string
|
||||
}
|
||||
te := test{"coucou"}
|
||||
err = u.SetConfig("test1", te)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var te2 test
|
||||
err = u.GetConfig("test1", &te2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if te != te2 {
|
||||
t.Fatal("unexpected config")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
@ -7,12 +7,14 @@ import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jmoiron/sqlx"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
"github.com/unrolled/render"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
// Env describes an environement object passed to all handlers
|
||||
type Env struct {
|
||||
Backend DetailerTorrenter
|
||||
Database *sqlx.DB
|
||||
Log *logrus.Entry
|
||||
Router *mux.Router
|
||||
@ -28,6 +30,14 @@ type EnvParams struct {
|
||||
Auth *auth.Authorizer
|
||||
Log *logrus.Entry
|
||||
Config *config.Config
|
||||
Backend DetailerTorrenter
|
||||
}
|
||||
|
||||
// DetailerTorrenter represents an object implementing polochon.Detailer and
|
||||
// polochon.Torrenter
|
||||
type DetailerTorrenter struct {
|
||||
Detailer polochon.Detailer
|
||||
Torrenter polochon.Torrenter
|
||||
}
|
||||
|
||||
// NewEnv returns a new *Env
|
||||
@ -39,24 +49,29 @@ func NewEnv(p EnvParams) *Env {
|
||||
Auth: p.Auth,
|
||||
Config: p.Config,
|
||||
Render: render.New(),
|
||||
Backend: p.Backend,
|
||||
}
|
||||
}
|
||||
|
||||
// Route represents a route
|
||||
type Route struct {
|
||||
env *Env
|
||||
mRoute *mux.Route
|
||||
}
|
||||
|
||||
// Name returns the name of a route
|
||||
func (r *Route) Name(name string) *Route {
|
||||
r.mRoute.Name(name)
|
||||
return r
|
||||
}
|
||||
|
||||
// Methods returns a list of methods for a route
|
||||
func (r *Route) Methods(methods ...string) *Route {
|
||||
r.mRoute.Methods(methods...)
|
||||
return r
|
||||
}
|
||||
|
||||
// WithRole sets a given role for a route
|
||||
func (r *Route) WithRole(role string) *Route {
|
||||
handler := r.mRoute.GetHandler()
|
||||
newHandler := negroni.New(
|
||||
|
52
src/main.go
52
src/main.go
@ -7,17 +7,14 @@ import (
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/external_medias"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/movies"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/shows"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/torrents"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
extmedias "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/external_medias"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/phyber/negroni-gzip/gzip"
|
||||
"github.com/robfig/cron"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
@ -61,43 +58,38 @@ func main() {
|
||||
Auth: authorizer,
|
||||
Log: log,
|
||||
Config: cf,
|
||||
Backend: web.DetailerTorrenter{
|
||||
Torrenter: backend,
|
||||
Detailer: backend,
|
||||
},
|
||||
})
|
||||
|
||||
authMiddleware := auth.NewMiddleware(env.Auth, log)
|
||||
|
||||
env.Handle("/users/login", users.LoginPOSTHandler).Methods("POST")
|
||||
env.Handle("/users/signup", users.SignupPOSTHandler).Methods("POST")
|
||||
env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole).Methods("POST")
|
||||
// Setup the routes
|
||||
setupRoutes(env)
|
||||
|
||||
env.Handle("/movies/polochon", movies.FromPolochon).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.GetDetailsHandler).WithRole(users.UserRole).Methods("POST")
|
||||
env.Handle("/movies/{id:tt[0-9]+}", movies.DeleteHandler).WithRole(users.AdminRole).Methods("DELETE")
|
||||
env.Handle("/movies/explore", extmedias.Explore).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/movies/refresh", extmedias.Refresh).WithRole(users.UserRole).Methods("POST")
|
||||
env.Handle("/movies/search", movies.SearchMovie).WithRole(users.UserRole).Methods("POST")
|
||||
// Create the cron object
|
||||
c := cron.New()
|
||||
|
||||
env.Handle("/shows/polochon", shows.FromPolochon).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]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}", shows.RefreshEpisodeHandler).WithRole(users.UserRole).Methods("POST")
|
||||
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")
|
||||
// Refresh the library every 6h
|
||||
c.AddFunc("@every 6h", func() {
|
||||
env.Log.Infof("Running refresh cron!")
|
||||
extmedias.Refresh(env)
|
||||
})
|
||||
|
||||
env.Handle("/torrents", torrents.DownloadHandler).WithRole(users.UserRole).Methods("POST")
|
||||
// Start the cron
|
||||
c.Start()
|
||||
|
||||
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")
|
||||
// Stop the cron
|
||||
defer c.Stop()
|
||||
|
||||
n := negroni.Classic()
|
||||
// Middleware for authentication
|
||||
n.Use(authMiddleware)
|
||||
// Serve static files
|
||||
n.Use(negroni.NewStatic(http.Dir(cf.PublicDir)))
|
||||
// Compress responses
|
||||
n.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
n.UseHandler(env.Router)
|
||||
n.Run(":" + cf.Port)
|
||||
|
51
src/routes.go
Normal file
51
src/routes.go
Normal file
@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/external_medias"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/movies"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/shows"
|
||||
"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"
|
||||
)
|
||||
|
||||
func setupRoutes(env *web.Env) {
|
||||
// User's route
|
||||
env.Handle("/users/login", users.LoginPOSTHandler).Methods("POST")
|
||||
env.Handle("/users/signup", users.SignupPOSTHandler).Methods("POST")
|
||||
env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole).Methods("POST")
|
||||
|
||||
// Movies routes
|
||||
env.Handle("/movies/polochon", movies.PolochonMoviesHandler).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/movies/explore", extmedias.ExploreMovies).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/movies/search", movies.SearchMovie).WithRole(users.UserRole).Methods("POST")
|
||||
env.Handle("/movies/{id:tt[0-9]+}", movies.PolochonDeleteHandler).WithRole(users.AdminRole).Methods("DELETE")
|
||||
env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.RefreshMovieHandler).WithRole(users.UserRole).Methods("POST")
|
||||
env.Handle("/movies/refresh", extmedias.RefreshMoviesHandler).WithRole(users.AdminRole).Methods("POST")
|
||||
|
||||
// Shows routes
|
||||
env.Handle("/shows/polochon", shows.PolochonShowsHandler).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/{id:tt[0-9]+}", shows.GetDetailsHandler).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/shows/{id:tt[0-9]+}/refresh", shows.RefreshShowHandler).WithRole(users.UserRole).Methods("POST")
|
||||
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}", shows.RefreshEpisodeHandler).WithRole(users.UserRole).Methods("POST")
|
||||
env.Handle("/shows/refresh", extmedias.RefreshShowsHandler).WithRole(users.AdminRole).Methods("POST")
|
||||
|
||||
// Wishlist routes for shows
|
||||
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")
|
||||
|
||||
// Wishlist routes for movies
|
||||
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")
|
||||
|
||||
// Torrents routes
|
||||
env.Handle("/torrents", torrents.DownloadHandler).WithRole(users.UserRole).Methods("POST")
|
||||
|
||||
// Route to refresh all movies and shows
|
||||
env.Handle("/refresh", extmedias.RefreshHandler).WithRole(users.AdminRole).Methods("POST")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user