Merge branch 'newCanape' into 'master'
Massive Rewrite See merge request !48
This commit is contained in:
commit
f68c2e7100
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)
|
@ -47,13 +47,7 @@ yarn start
|
|||||||
## Connect to the database
|
## Connect to the database
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -it --rm --link canape_postgresql_dev:postgres postgres:9.5 psql -h postgres -U test
|
docker run -it --rm -e PGPASSWORD=test --link canape_postgresql_dev:postgres postgres:9.5 psql -h postgres -U test -d dev
|
||||||
```
|
|
||||||
|
|
||||||
You'll need to connect to the dev database with this command:
|
|
||||||
|
|
||||||
```
|
|
||||||
\c dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Default users
|
## Default users
|
||||||
|
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;
|
@ -21,7 +21,7 @@ var (
|
|||||||
|
|
||||||
// UserBackend interface for user backend
|
// UserBackend interface for user backend
|
||||||
type UserBackend interface {
|
type UserBackend interface {
|
||||||
Get(username string) (User, error)
|
GetUser(username string) (User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// User interface for user
|
// User interface for user
|
||||||
@ -64,7 +64,7 @@ func (a *Authorizer) GenHash(password string) (string, error) {
|
|||||||
|
|
||||||
// Login cheks password and creates a jwt token
|
// Login cheks password and creates a jwt token
|
||||||
func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) (User, error) {
|
func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) (User, error) {
|
||||||
u, err := a.Backend.Get(username)
|
u, err := a.Backend.GetUser(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (Use
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
u, err := a.Backend.Get(tokenClaims.Username)
|
u, err := a.Backend.GetUser(tokenClaims.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ type Middleware struct {
|
|||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authContextKey string
|
||||||
|
|
||||||
// NewMiddleware returns a new authentication middleware
|
// NewMiddleware returns a new authentication middleware
|
||||||
func NewMiddleware(authorizer *Authorizer, log *logrus.Entry) *Middleware {
|
func NewMiddleware(authorizer *Authorizer, log *logrus.Entry) *Middleware {
|
||||||
return &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")
|
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)
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
next(w, r)
|
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 {
|
func GetCurrentUser(r *http.Request, log *logrus.Entry) User {
|
||||||
log.Debug("getting user from context")
|
log.Debug("getting user from context")
|
||||||
|
|
||||||
u := r.Context().Value("auth.user")
|
ctxKey := authContextKey("auth.user")
|
||||||
|
u := r.Context().Value(ctxKey)
|
||||||
if u == nil {
|
if u == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
24
src/internal/backend/backend.go
Normal file
24
src/internal/backend/backend.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||||
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Backend represents the data backend
|
||||||
|
type Backend struct {
|
||||||
|
Database *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements the Module interface
|
||||||
|
func (b *Backend) Name() string {
|
||||||
|
return "canape-backend"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
106
src/internal/backend/episode_torrents.go
Normal file
106
src/internal/backend/episode_torrents.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
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)
|
||||||
|
r, err := db.NamedQuery(upsertEpisodeTorrentQuery, eDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
137
src/internal/backend/episodes.go
Normal file
137
src/internal/backend/episodes.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
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)
|
||||||
|
r, err := db.NamedQuery(upsertEpisodeQuery, e)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
return nil
|
||||||
|
}
|
48
src/internal/backend/explorer.go
Normal file
48
src/internal/backend/explorer.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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 {
|
||||||
|
r, err := db.NamedQuery(upsertExternalMediaQuery, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
return nil
|
||||||
|
}
|
102
src/internal/backend/movie_torrents.go
Normal file
102
src/internal/backend/movie_torrents.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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)
|
||||||
|
r, err := db.NamedQuery(upsertMovieTorrentQuery, mDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
128
src/internal/backend/movie_wishlist.go
Normal file
128
src/internal/backend/movie_wishlist.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
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
|
||||||
|
COUNT(*)
|
||||||
|
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 count int
|
||||||
|
// Check if the movie is wishlisted by the user
|
||||||
|
err := db.Get(&count, isMovieWishlistedQueryByUserID, imdbID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if count > 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, err := r.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
120
src/internal/backend/movies.go
Normal file
120
src/internal/backend/movies.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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)
|
||||||
|
r, err := db.NamedQuery(upsertMovieQuery, mDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
143
src/internal/backend/show_wishlist.go
Normal file
143
src/internal/backend/show_wishlist.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
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, err := r.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
105
src/internal/backend/shows.go
Normal file
105
src/internal/backend/shows.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
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
|
||||||
|
r, err := db.NamedQuery(upsertShowQuery, sDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
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"
|
polochon "github.com/odwrtw/polochon/lib"
|
||||||
"github.com/odwrtw/polochon/modules/eztv"
|
"github.com/odwrtw/polochon/modules/eztv"
|
||||||
"github.com/odwrtw/polochon/modules/tmdb"
|
"github.com/odwrtw/polochon/modules/tmdb"
|
||||||
|
"github.com/odwrtw/polochon/modules/trakttv"
|
||||||
"github.com/odwrtw/polochon/modules/tvdb"
|
"github.com/odwrtw/polochon/modules/tvdb"
|
||||||
"github.com/odwrtw/polochon/modules/yts"
|
"github.com/odwrtw/polochon/modules/yts"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Config represents the Config of the canape app
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Authorizer AuthorizerConfig `yaml:"authorizer"`
|
Authorizer AuthorizerConfig `yaml:"authorizer"`
|
||||||
PGDSN string `yaml:"pgdsn"`
|
PGDSN string `yaml:"pgdsn"`
|
||||||
@ -22,21 +24,26 @@ type Config struct {
|
|||||||
|
|
||||||
// TODO improve the detailers and torrenters configurations
|
// TODO improve the detailers and torrenters configurations
|
||||||
TmdbAPIKey string `yaml:"tmdb_api_key"`
|
TmdbAPIKey string `yaml:"tmdb_api_key"`
|
||||||
|
MovieExplorers []polochon.Explorer
|
||||||
MovieDetailers []polochon.Detailer
|
MovieDetailers []polochon.Detailer
|
||||||
MovieTorrenters []polochon.Torrenter
|
MovieTorrenters []polochon.Torrenter
|
||||||
MovieSearchers []polochon.Searcher
|
MovieSearchers []polochon.Searcher
|
||||||
|
ShowExplorers []polochon.Explorer
|
||||||
ShowDetailers []polochon.Detailer
|
ShowDetailers []polochon.Detailer
|
||||||
ShowTorrenters []polochon.Torrenter
|
ShowTorrenters []polochon.Torrenter
|
||||||
ShowSearchers []polochon.Searcher
|
ShowSearchers []polochon.Searcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AuthorizerConfig is the config for the authentication
|
||||||
type AuthorizerConfig struct {
|
type AuthorizerConfig struct {
|
||||||
Pepper string `yaml:"pepper"`
|
Pepper string `yaml:"pepper"`
|
||||||
Cost int `yaml:"cost"`
|
Cost int `yaml:"cost"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load loads a config file from a path and return a Config object
|
||||||
func Load(path string) (*Config, error) {
|
func Load(path string) (*Config, error) {
|
||||||
|
// Open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -45,26 +52,51 @@ func Load(path string) (*Config, error) {
|
|||||||
|
|
||||||
cf := &Config{}
|
cf := &Config{}
|
||||||
|
|
||||||
|
// Read it
|
||||||
b, err := ioutil.ReadAll(file)
|
b, err := ioutil.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unmarshal its content
|
||||||
err = yaml.Unmarshal(b, cf)
|
err = yaml.Unmarshal(b, cf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the default values
|
||||||
|
|
||||||
// Default movie detailers
|
// Default movie detailers
|
||||||
cf.MovieDetailers = []polochon.Detailer{}
|
cf.MovieDetailers = []polochon.Detailer{}
|
||||||
if cf.TmdbAPIKey != "" {
|
if cf.TmdbAPIKey != "" {
|
||||||
d, err := tmdb.New(&tmdb.Params{cf.TmdbAPIKey})
|
d, err := tmdb.New(
|
||||||
|
&tmdb.Params{
|
||||||
|
ApiKey: cf.TmdbAPIKey,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cf.MovieDetailers = append(cf.MovieDetailers, d)
|
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
|
// Default movie torrenters
|
||||||
cf.MovieTorrenters = []polochon.Torrenter{}
|
cf.MovieTorrenters = []polochon.Torrenter{}
|
||||||
d, err := yts.New()
|
d, err := yts.New()
|
||||||
@ -89,6 +121,23 @@ func Load(path string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
cf.ShowSearchers = append(cf.ShowSearchers, showSearcher)
|
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
|
// Default show detailers
|
||||||
cf.ShowDetailers = []polochon.Detailer{}
|
cf.ShowDetailers = []polochon.Detailer{}
|
||||||
showDetailer, err := tvdb.NewDetailer()
|
showDetailer, err := tvdb.NewDetailer()
|
||||||
|
@ -1,58 +1,187 @@
|
|||||||
package extmedias
|
package extmedias
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
"fmt"
|
||||||
"github.com/lib/pq"
|
|
||||||
"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/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 (
|
// NewExplorer returns a polochon.Explorer from the list of Explorers in the config
|
||||||
upsertExternalMediaQuery = `
|
func NewExplorer(env *web.Env, mediaType, name string) (polochon.Explorer, error) {
|
||||||
INSERT INTO external_medias (type, source, category, ids)
|
var explorers []polochon.Explorer
|
||||||
VALUES (:type, :source, :category, :ids)
|
if mediaType == "movie" {
|
||||||
ON CONFLICT (type, source, category)
|
explorers = env.Config.MovieExplorers
|
||||||
DO UPDATE SET type=:type, source=:source, category=:category, ids=:ids
|
} else {
|
||||||
RETURNING id;`
|
explorers = env.Config.ShowExplorers
|
||||||
|
}
|
||||||
|
|
||||||
deleteExternalMediaQuery = `DELETE FROM external_medias WHERE id=:id;`
|
for _, e := range explorers {
|
||||||
getExternalMediaQuery = `SELECT * FROM external_medias WHERE type=$1 AND source=$2 AND category=$3 LIMIT 1;`
|
// Check the name of the explorer
|
||||||
)
|
if e.Name() == name {
|
||||||
|
return e, nil
|
||||||
// Media represents an external media
|
}
|
||||||
type Media struct {
|
}
|
||||||
sqly.BaseModel
|
return nil, fmt.Errorf("no such explorer %s", name)
|
||||||
Type string `db:"type"`
|
|
||||||
Source string `db:"source"`
|
|
||||||
Category string `db:"category"`
|
|
||||||
IDs pq.StringArray `db:"ids"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upsert adds or updates or adds the Media in the database
|
// GetMediaIDs will get some media IDs
|
||||||
func (m *Media) Upsert(db *sqlx.DB) error {
|
func GetMediaIDs(env *web.Env, mediaType string, source string, category string) ([]string, error) {
|
||||||
var id string
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
r, err := db.NamedQuery(upsertExternalMediaQuery, m)
|
"mediaType": mediaType,
|
||||||
|
"source": source,
|
||||||
|
"category": category,
|
||||||
|
"function": "extmedias.GetMediaIds",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get the explorer that we need
|
||||||
|
explorer, err := NewExplorer(env, mediaType, source)
|
||||||
if err != nil {
|
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 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{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Iterate over the map of shows to refresh them
|
||||||
|
for id := range showMap {
|
||||||
|
show := shows.New(id, env.Config.PublicDir)
|
||||||
|
// 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{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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
|
package extmedias
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
"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/movies"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/shows"
|
"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/users"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
polochon "github.com/odwrtw/polochon/lib"
|
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
|
// RefreshHandler refresh the explored movies
|
||||||
func GetMediaIDs(env *web.Env, mediaType string, source string, category string, force bool) ([]string, error) {
|
func RefreshHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
log := env.Log.WithFields(logrus.Fields{
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
"mediaType": mediaType,
|
"function": "extmedias.RefreshHandler",
|
||||||
"source": source,
|
|
||||||
"category": category,
|
|
||||||
"function": "extmedias.GetMediaIds",
|
|
||||||
})
|
})
|
||||||
|
log.Debugf("refreshing shows and movies")
|
||||||
|
Refresh(env)
|
||||||
|
log.Debugf("done refreshing shows and movies")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
// RefreshMoviesHandler refresh the explored movies
|
||||||
media, err := Get(env.Database, mediaType, source, category)
|
func RefreshMoviesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
switch err {
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
case nil:
|
"function": "extmedias.RefreshMoviesHandler",
|
||||||
log.Debugf("%s medias found in database", mediaType)
|
})
|
||||||
if !force {
|
log.Debugf("refreshing movies")
|
||||||
log.Debug("returning medias from db")
|
RefreshMovies(env)
|
||||||
return media.IDs, nil
|
log.Debugf("done refreshing movies")
|
||||||
}
|
return nil
|
||||||
case sql.ErrNoRows:
|
}
|
||||||
log.Debugf("%s medias not found in database", mediaType)
|
|
||||||
default:
|
|
||||||
// Unexpected error
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
explorer, err := NewExplorer(env, source)
|
// RefreshShowsHandler refresh the explored shows
|
||||||
if err != nil {
|
func RefreshShowsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
return nil, err
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
}
|
"function": "extmedias.RefreshShowsHandler",
|
||||||
|
})
|
||||||
var ids []string
|
log.Debugf("refreshing shows")
|
||||||
if mediaType == "movie" {
|
RefreshShows(env)
|
||||||
medias, err := explorer.GetMovieList(polochon.ExploreByRate, log)
|
log.Debugf("done refreshing shows")
|
||||||
if err != nil {
|
return 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMovies get some movies
|
// GetMovies get some movies
|
||||||
func GetMovies(env *web.Env, user *users.User, source string, category string, force bool) ([]*movies.Movie, error) {
|
func GetMovies(env *web.Env, user *users.User, source string, category string) ([]*movies.Movie, error) {
|
||||||
movieIds, err := GetMediaIDs(env, "movie", source, category, force)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the URLs from polochon, we don't really care if it fails for now
|
// Create a papi client
|
||||||
var urls map[string]string
|
client, err := user.NewPapiClient()
|
||||||
urls, err = movies.GetPolochonMoviesURLs(user)
|
|
||||||
if err != nil {
|
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{}
|
movieList := []*movies.Movie{}
|
||||||
for _, id := range movieIds {
|
// Fill all the movies infos from the list of IDs
|
||||||
movie := movies.New(id)
|
for _, id := range media.IDs {
|
||||||
err := movie.GetDetails(env, user, force)
|
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 {
|
if err != nil {
|
||||||
env.Log.Errorf("error while getting movie details : %s", err)
|
env.Log.Errorf("error while getting movie details : %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
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 {
|
if err != nil {
|
||||||
env.Log.Errorf("error while getting movie torrents : %s", err)
|
env.Log.Errorf("error while getting movie torrents : %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if urls != nil {
|
|
||||||
if url, ok := urls[id]; ok {
|
|
||||||
movie.PolochonURL = url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
movieList = append(movieList, movie)
|
movieList = append(movieList, movie)
|
||||||
}
|
}
|
||||||
return movieList, nil
|
return movieList, nil
|
||||||
@ -132,31 +109,59 @@ func GetMovies(env *web.Env, user *users.User, source string, category string, f
|
|||||||
|
|
||||||
// GetShows get some shows
|
// GetShows get some shows
|
||||||
func GetShows(env *web.Env, user *users.User, source string, category string, force bool) ([]*shows.Show, error) {
|
func GetShows(env *web.Env, user *users.User, source string, category string, force bool) ([]*shows.Show, error) {
|
||||||
showIds, err := GetMediaIDs(env, "show", source, category, force)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
showList := []*shows.Show{}
|
showList := []*shows.Show{}
|
||||||
for _, id := range showIds {
|
// Fill all the shows infos from the list of IDs
|
||||||
show := shows.New(id)
|
for _, id := range media.IDs {
|
||||||
err := show.GetDetails(env, user, force)
|
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 {
|
if err != nil {
|
||||||
env.Log.Errorf("error while getting show details : %s", err)
|
env.Log.Errorf("error while getting show details : %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = show.GetTorrents(env, force)
|
|
||||||
if err != nil {
|
|
||||||
env.Log.Errorf("error while getting show torrents : %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
showList = append(showList, show)
|
showList = append(showList, show)
|
||||||
}
|
}
|
||||||
return showList, nil
|
return showList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explore will explore some movies
|
// ExploreMovies will explore some movies from the db
|
||||||
func Explore(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func ExploreMovies(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -165,7 +170,7 @@ func Explore(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
source := r.FormValue("source")
|
source := r.FormValue("source")
|
||||||
// Default source
|
// Default source
|
||||||
if source == "" {
|
if source == "" {
|
||||||
source = "yts"
|
source = "trakttv"
|
||||||
}
|
}
|
||||||
|
|
||||||
category := r.FormValue("category")
|
category := r.FormValue("category")
|
||||||
@ -181,7 +186,7 @@ func Explore(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the medias without trying to refresh them
|
// 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 {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
@ -205,7 +210,7 @@ func ExploreShows(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
category := r.FormValue("category")
|
category := r.FormValue("category")
|
||||||
// Default category
|
// Default category
|
||||||
if category == "" {
|
if category == "" {
|
||||||
category = "popular"
|
category = "rating"
|
||||||
}
|
}
|
||||||
|
|
||||||
v := auth.GetCurrentUser(r, env.Log)
|
v := auth.GetCurrentUser(r, env.Log)
|
||||||
@ -222,94 +227,3 @@ func ExploreShows(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
return env.RenderJSON(w, shows)
|
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,109 +4,50 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/odwrtw/papi"
|
"github.com/odwrtw/papi"
|
||||||
polochon "github.com/odwrtw/polochon/lib"
|
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/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/config"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrPolochonUnavailable is an error returned if the polochon server is not available
|
// PolochonMoviesHandler will returns movies from Polochon
|
||||||
var ErrPolochonUnavailable = fmt.Errorf("Invalid polochon address")
|
func PolochonMoviesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
// Get the user from the request
|
||||||
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 {
|
|
||||||
v := auth.GetCurrentUser(r, env.Log)
|
v := auth.GetCurrentUser(r, env.Log)
|
||||||
user, ok := v.(*users.User)
|
user, ok := v.(*users.User)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid user type")
|
return env.RenderError(w, fmt.Errorf("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
movies, err := getPolochonMovies(user)
|
// Get the polochon movies of the user
|
||||||
|
movies, err := getPolochonMovies(user, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new polochon Detailer to get infos about the movies we just got
|
||||||
var polochonConfig config.UserPolochon
|
var polochonConfig config.UserPolochon
|
||||||
err = user.GetConfig("polochon", &polochonConfig)
|
err = user.GetConfig("polochon", &polochonConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
detailer, err := pam.New(&pam.Params{
|
// Get details with the polochon Detailer for each polochon.Movies we have
|
||||||
Endpoint: polochonConfig.URL,
|
|
||||||
Token: polochonConfig.Token,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, m := range movies {
|
for _, m := range movies {
|
||||||
m.Detailers = []polochon.Detailer{detailer}
|
// First try from the db
|
||||||
err := m.GetDetails(env, user, false)
|
first := []polochon.Detailer{env.Backend.Detailer}
|
||||||
|
// Then try from the polochon detailer
|
||||||
|
detailers := env.Config.MovieDetailers
|
||||||
|
err := m.GetAndFetch(env, first, detailers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Log.Error(err)
|
env.Log.Error(err)
|
||||||
}
|
}
|
||||||
@ -115,20 +56,47 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
return env.RenderJSON(w, movies)
|
return env.RenderJSON(w, movies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDetailsHandler retrieves details for a movie
|
// RefreshMovieHandler refreshes details for a movie
|
||||||
func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
|
// Get the user
|
||||||
v := auth.GetCurrentUser(r, env.Log)
|
v := auth.GetCurrentUser(r, env.Log)
|
||||||
user, ok := v.(*users.User)
|
user, ok := v.(*users.User)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid user type")
|
return fmt.Errorf("invalid user type")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := New(id)
|
// Create a new papi client
|
||||||
if err := m.GetDetails(env, user, true); err != nil {
|
client, err := user.NewPapiClient()
|
||||||
return err
|
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)
|
return env.RenderJSON(w, m)
|
||||||
@ -154,8 +122,27 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
return env.RenderError(w, errors.New("invalid user"))
|
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
|
var movies []*polochon.Movie
|
||||||
searchers := env.Config.MovieSearchers
|
searchers := env.Config.MovieSearchers
|
||||||
|
// Search for the movie with all the Searchers
|
||||||
for _, searcher := range searchers {
|
for _, searcher := range searchers {
|
||||||
result, err := searcher.SearchMovie(data.Key, env.Log)
|
result, err := searcher.SearchMovie(data.Key, env.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -165,42 +152,45 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
movies = append(movies, result...)
|
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)
|
env.Log.Debugf("got %d movies doing search %q", len(movies), data.Key)
|
||||||
movieList := []*Movie{}
|
movieList := []*Movie{}
|
||||||
|
// For each movie found, fill the details
|
||||||
for _, m := range movies {
|
for _, m := range movies {
|
||||||
movie := New(m.ImdbID)
|
pMovie, _ := pMovies.Has(m.ImdbID)
|
||||||
err := movie.GetDetails(env, user, false)
|
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 {
|
if err != nil {
|
||||||
env.Log.Errorf("error while getting movie details : %s", err)
|
env.Log.Errorf("error while getting movie details : %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
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 {
|
if err != nil {
|
||||||
env.Log.Errorf("error while getting movie torrents : %s", err)
|
env.Log.Errorf("error while getting movie torrents : %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if urls != nil {
|
|
||||||
if url, ok := urls[m.ImdbID]; ok {
|
|
||||||
movie.PolochonURL = url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
movieList = append(movieList, movie)
|
movieList = append(movieList, movie)
|
||||||
}
|
}
|
||||||
|
|
||||||
return env.RenderJSON(w, movieList)
|
return env.RenderJSON(w, movieList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteHandler deletes the movie from polochon
|
// PolochonDeleteHandler deletes the movie from polochon
|
||||||
func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func PolochonDeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
@ -210,27 +200,20 @@ func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
})
|
})
|
||||||
log.Debugf("deleting movie")
|
log.Debugf("deleting movie")
|
||||||
|
|
||||||
|
// Get the user
|
||||||
v := auth.GetCurrentUser(r, env.Log)
|
v := auth.GetCurrentUser(r, env.Log)
|
||||||
user, ok := v.(*users.User)
|
user, ok := v.(*users.User)
|
||||||
if !ok {
|
if !ok {
|
||||||
return env.RenderError(w, errors.New("invalid user type"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
var polochonConfig config.UserPolochon
|
// Create a new papi client
|
||||||
err := user.GetConfig("polochon", &polochonConfig)
|
client, err := user.NewPapiClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := papi.New(polochonConfig.URL)
|
// Delete the movie
|
||||||
if err != nil {
|
|
||||||
return env.RenderError(w, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if polochonConfig.Token != "" {
|
|
||||||
client.SetToken(polochonConfig.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.Delete(&papi.Movie{ImdbID: id})
|
return client.Delete(&papi.Movie{ImdbID: id})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,8 +228,7 @@ func AddToWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
return env.RenderError(w, errors.New("invalid user type"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
m := New(id)
|
if err := backend.AddMovieToWishlist(env.Database, user.ID, id); err != nil {
|
||||||
if err := m.AddToWishlist(env, user); err != nil {
|
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,8 +246,7 @@ func DeleteFromWishlist(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
|||||||
return env.RenderError(w, errors.New("invalid user type"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
m := New(id)
|
if err := backend.DeleteMovieFromWishlist(env.Database, user.ID, id); err != nil {
|
||||||
if err := m.DeleteFromWishlist(env, user); err != nil {
|
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,10 +261,55 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
|||||||
return env.RenderError(w, errors.New("invalid user type"))
|
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 {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
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,301 +2,198 @@ package movies
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/odwrtw/papi"
|
||||||
"github.com/lib/pq"
|
|
||||||
"github.com/odwrtw/polochon/lib"
|
"github.com/odwrtw/polochon/lib"
|
||||||
|
|
||||||
"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/torrents"
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
|
||||||
movies.id,
|
|
||||||
movies.title,
|
|
||||||
movies.imdb_id,
|
|
||||||
movies.tmdb_id,
|
|
||||||
movies.votes,
|
|
||||||
movies.rating,
|
|
||||||
movies.plot,
|
|
||||||
movies.year,
|
|
||||||
movies.original_title,
|
|
||||||
movies.runtime,
|
|
||||||
movies.genres,
|
|
||||||
movies.sort_title,
|
|
||||||
movies.tagline,
|
|
||||||
movies.created_at,
|
|
||||||
movies.updated_at,
|
|
||||||
movies_tracked.user_id
|
|
||||||
FROM movies LEFT JOIN movies_tracked
|
|
||||||
ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$2
|
|
||||||
WHERE movies.imdb_id=$1;`
|
|
||||||
|
|
||||||
getMovieQueryByID = `
|
|
||||||
SELECT
|
|
||||||
movies.id,
|
|
||||||
movies.title,
|
|
||||||
movies.imdb_id,
|
|
||||||
movies.tmdb_id,
|
|
||||||
movies.votes,
|
|
||||||
movies.rating,
|
|
||||||
movies.plot,
|
|
||||||
movies.year,
|
|
||||||
movies.original_title,
|
|
||||||
movies.runtime,
|
|
||||||
movies.genres,
|
|
||||||
movies.sort_title,
|
|
||||||
movies.tagline,
|
|
||||||
movies.created_at,
|
|
||||||
movies.updated_at,
|
|
||||||
movies_tracked.user_id
|
|
||||||
FROM movies LEFT JOIN movies_tracked
|
|
||||||
ON movies.imdb_id=movies_tracked.imdb_id AND movies_tracked.user_id=$2
|
|
||||||
WHERE movies.id=$1;`
|
|
||||||
|
|
||||||
deleteMovieQuery = `DELETE FROM movies WHERE id=$1;`
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
// Movie represents a movie
|
||||||
type Movie struct {
|
type Movie struct {
|
||||||
sqly.BaseModel
|
*polochon.Movie
|
||||||
polochon.Movie
|
client *papi.Client
|
||||||
|
pMovie *papi.Movie
|
||||||
|
publicDir string
|
||||||
Wishlisted bool `json:"wishlisted"`
|
Wishlisted bool `json:"wishlisted"`
|
||||||
PolochonURL string `json:"polochon_url"`
|
|
||||||
PosterURL string `json:"poster_url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Movie with an ImDB id
|
// MarshalJSON implements the Marshal interface
|
||||||
func New(imdbID string) *Movie {
|
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{
|
return &Movie{
|
||||||
Movie: polochon.Movie{
|
client: client,
|
||||||
|
pMovie: pMovie,
|
||||||
|
publicDir: publicDir,
|
||||||
|
Wishlisted: isWishlisted,
|
||||||
|
Movie: &polochon.Movie{
|
||||||
ImdbID: imdbID,
|
ImdbID: imdbID,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns show details in database from id or imdbid or an error
|
// GetDetails retrieves details for the movie with the given detailers
|
||||||
func (m *Movie) Get(env *web.Env, user *users.User) error {
|
func (m *Movie) GetDetails(env *web.Env, detailers []polochon.Detailer) error {
|
||||||
var mDB MovieDB
|
|
||||||
var err error
|
|
||||||
if m.ID != "" {
|
|
||||||
err = env.Database.QueryRowx(getMovieQueryByID, m.ID, user.ID).StructScan(&mDB)
|
|
||||||
} else if m.ImdbID != "" {
|
|
||||||
err = env.Database.QueryRowx(getMovieQueryByImdbID, m.ImdbID, user.ID).StructScan(&mDB)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Can't get movie details, you have to specify an ID or ImdbID")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the poster url
|
|
||||||
m.PosterURL = m.GetPosterURL(env)
|
|
||||||
|
|
||||||
m.FillFromDB(&mDB)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDetails retrieves details for the movie, first try to get info from db,
|
|
||||||
// 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 {
|
|
||||||
log := env.Log.WithFields(logrus.Fields{
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
"imdb_id": m.ImdbID,
|
"imdb_id": m.ImdbID,
|
||||||
"function": "movies.GetDetails",
|
"function": "movies.GetDetails",
|
||||||
})
|
})
|
||||||
log.Debugf("getting details")
|
log.Debugf("getting details")
|
||||||
|
|
||||||
if len(m.Detailers) == 0 {
|
m.Detailers = detailers
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDetail
|
// GetDetail
|
||||||
err = m.Movie.GetDetails(env.Log)
|
err := m.Movie.GetDetails(env.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("got details from detailers")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTorrents retrieves torrents for the movie, first try to get info from db,
|
// GetAndFetch retrieves details for the movie with the given
|
||||||
// if not exists, use polochon.Torrenter and save informations in the database
|
// detailers 'before'
|
||||||
// for future use
|
// If found, return
|
||||||
//
|
// If not, retrives details with the detailers 'after' and update them in
|
||||||
// If force is used, the torrenter will be used even if the torrent is found in
|
|
||||||
// database
|
// 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{
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
"imdb_id": m.ImdbID,
|
"imdb_id": m.ImdbID,
|
||||||
"function": "movies.GetTorrents",
|
"function": "movies.GetTorrents",
|
||||||
})
|
})
|
||||||
log.Debugf("getting torrents")
|
log.Debugf("getting torrents")
|
||||||
|
|
||||||
if len(m.Torrenters) == 0 {
|
m.Torrenters = torrenters
|
||||||
m.Torrenters = env.Config.MovieTorrenters
|
|
||||||
}
|
|
||||||
|
|
||||||
movieTorrents, err := torrents.GetMovieTorrents(env.Database, m.ImdbID)
|
err := m.Movie.GetTorrents(env.Log)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("got %d torrents from torrenters", len(m.Movie.Torrents))
|
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 {
|
for _, t := range m.Movie.Torrents {
|
||||||
torrent := torrents.NewMovie(m.ImdbID, t)
|
err = backend.UpsertMovieTorrent(env.Database, &t, m.ImdbID)
|
||||||
err = torrent.Upsert(env.Database)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("error while adding torrent", err)
|
log.Error("error while adding torrent", err)
|
||||||
continue
|
continue
|
||||||
@ -306,51 +203,59 @@ func (m *Movie) GetTorrents(env *web.Env, force bool) error {
|
|||||||
return nil
|
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
|
// 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)
|
return fmt.Sprintf("img/movies/%s.jpg", m.ImdbID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// imgFile returns the image location on disk
|
// imgFile returns the image location on disk
|
||||||
func (m *Movie) imgFile(env *web.Env) string {
|
func (m *Movie) imgFile() string {
|
||||||
return filepath.Join(env.Config.PublicDir, m.imgURL(env))
|
return filepath.Join(m.publicDir, m.imgURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPosterURL returns the image URL or the default image if the poster is not yet downloaded
|
// PosterURL returns the image URL or the default image if the poster is not yet downloaded
|
||||||
func (m *Movie) GetPosterURL(env *web.Env) string {
|
func (m *Movie) PosterURL() string {
|
||||||
// Check if the movie image exists
|
// 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 ?
|
// TODO image in the config ?
|
||||||
return "img/noimage.png"
|
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())
|
var src = rand.NewSource(time.Now().UnixNano())
|
||||||
|
|
||||||
|
// String returns n random strings
|
||||||
func String(n int) string {
|
func String(n int) string {
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||||
|
@ -1,181 +1,140 @@
|
|||||||
package shows
|
package shows
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"encoding/json"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/odwrtw/papi"
|
||||||
polochon "github.com/odwrtw/polochon/lib"
|
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"
|
"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
|
// Episode represents an episode
|
||||||
type Episode struct {
|
type Episode struct {
|
||||||
sqly.BaseModel
|
client *papi.Client
|
||||||
polochon.ShowEpisode
|
pShow *papi.Show
|
||||||
PolochonURL string `json:"polochon_url"`
|
*polochon.ShowEpisode
|
||||||
}
|
}
|
||||||
|
|
||||||
// EpisodeDB represents the Episode in the DB
|
// MarshalJSON implements the Marshal interface
|
||||||
type EpisodeDB struct {
|
func (e *Episode) MarshalJSON() ([]byte, error) {
|
||||||
ID string `db:"id"`
|
type alias Episode
|
||||||
TvdbID int `db:"tvdb_id"`
|
|
||||||
ImdbID string `db:"imdb_id"`
|
var downloadURL string
|
||||||
ShowImdbID string `db:"show_imdb_id"`
|
// If the episode is present, fill the downloadURL
|
||||||
ShowTvdbID int `db:"show_tvdb_id"`
|
if e.pShow != nil && e.pShow.HasEpisode(e.Season, e.Episode) {
|
||||||
Season int `db:"season"`
|
downloadURL, _ = e.client.DownloadURL(
|
||||||
Episode int `db:"episode"`
|
&papi.Episode{
|
||||||
Title string `db:"title"`
|
ShowImdbID: e.ShowImdbID,
|
||||||
Rating float32 `db:"rating"`
|
Episode: e.Episode,
|
||||||
Plot string `db:"plot"`
|
Season: e.Season,
|
||||||
Thumb string `db:"thumb"`
|
},
|
||||||
Runtime int `db:"runtime"`
|
)
|
||||||
Aired string `db:"aired"`
|
}
|
||||||
ReleaseGroup string `db:"release_group"`
|
|
||||||
Created time.Time `db:"created_at"`
|
// Marshal the episode with its polochon_url
|
||||||
Updated time.Time `db:"updated_at"`
|
episodeToMarshal := &struct {
|
||||||
|
*alias
|
||||||
|
PolochonURL string `json:"polochon_url"`
|
||||||
|
}{
|
||||||
|
alias: (*alias)(e),
|
||||||
|
PolochonURL: downloadURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(episodeToMarshal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEpisode returns an Episode
|
// NewEpisode returns an Episode
|
||||||
func NewEpisode() *Episode {
|
func NewEpisode(client *papi.Client, pShow *papi.Show, imdbID string, season, episode int) *Episode {
|
||||||
return &Episode{}
|
return &Episode{
|
||||||
}
|
client: client,
|
||||||
|
pShow: pShow,
|
||||||
// NewEpisodeDB returns an Episode ready to be put in DB from an
|
ShowEpisode: &polochon.ShowEpisode{
|
||||||
// Episode
|
ShowImdbID: imdbID,
|
||||||
func NewEpisodeDB(e *Episode) EpisodeDB {
|
Season: season,
|
||||||
return EpisodeDB{
|
Episode: episode,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillFromDB returns a Show from a ShowDB extracted from the DB
|
// GetAndFetchTorrents retrieves torrents for the episode with the given
|
||||||
func (e *Episode) FillFromDB(eDB *EpisodeDB) {
|
// torrenters 'before'
|
||||||
e.ID = eDB.ID
|
// If found, return
|
||||||
e.TvdbID = eDB.TvdbID
|
// If not, retrives torrents with the torrenters 'after' and update them in
|
||||||
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
|
|
||||||
// database
|
// 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{
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
"imdb_id": e.ShowEpisode.ShowImdbID,
|
"imdb_id": e.ShowImdbID,
|
||||||
"season": e.ShowEpisode.Season,
|
"season": e.Season,
|
||||||
"episode": e.ShowEpisode.Episode,
|
"episode": e.Episode,
|
||||||
"function": "shows.GetTorrents",
|
"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")
|
log.Debugf("getting torrents")
|
||||||
|
|
||||||
if len(e.Torrenters) == 0 {
|
showEpisode.Torrenters = torrenters
|
||||||
e.Torrenters = env.Config.ShowTorrenters
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeTorrents, err := torrents.GetEpisodeTorrents(
|
err := showEpisode.GetTorrents(env.Log)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
return nil
|
||||||
torrent := torrents.NewEpisodeFromPolochon(e.ShowEpisode, t)
|
}
|
||||||
err = torrent.Upsert(env.Database)
|
|
||||||
|
// 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 {
|
if err != nil {
|
||||||
log.Errorf("error while adding torrent : %s", err)
|
log.Errorf("error while adding torrent : %s", err)
|
||||||
continue
|
continue
|
||||||
@ -185,15 +144,35 @@ func (e *Episode) GetTorrents(env *web.Env, force bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns an episode
|
// Refresh retrieves details for the episode with the given detailers
|
||||||
func (e *Episode) Get(env *web.Env) error {
|
// and update them in database
|
||||||
var episodeDB EpisodeDB
|
func (e *Episode) Refresh(env *web.Env, detailers []polochon.Detailer) error {
|
||||||
err := env.Database.QueryRowx(getEpisodeQuery, e.ShowImdbID, e.Season, e.Episode).StructScan(&episodeDB)
|
// Refresh
|
||||||
|
err := e.GetEpisodeDetails(env, detailers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,37 +4,26 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"log"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"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/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/users"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
"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
|
// ErrPolochonUnavailable is an error returned if the polochon server is not available
|
||||||
var ErrPolochonUnavailable = fmt.Errorf("Invalid polochon address")
|
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 {
|
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)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
@ -44,28 +33,91 @@ func DetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request, force
|
|||||||
return env.RenderError(w, errors.New("invalid user type"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
s := New(id)
|
client, err := user.NewPapiClient()
|
||||||
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)
|
|
||||||
if err != nil {
|
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 {
|
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 {
|
for _, e := range s.Episodes {
|
||||||
if e.Season != pEpisode.Season || e.Episode != pEpisode.Episode {
|
// Get torrents from the db
|
||||||
continue
|
err := RefreshTorrents(env, e, env.Config.ShowTorrenters)
|
||||||
}
|
if err != nil {
|
||||||
e.PolochonURL = pEpisode.PolochonURL
|
env.Log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env.Log.Debug("getting polochon show")
|
||||||
|
|
||||||
return env.RenderJSON(w, s)
|
return env.RenderJSON(w, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,23 +142,50 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
var shows []*polochon.Show
|
var shows []*polochon.Show
|
||||||
searchers := env.Config.ShowSearchers
|
searchers := env.Config.ShowSearchers
|
||||||
|
// Iterate on all the searchers to search for the show
|
||||||
for _, searcher := range searchers {
|
for _, searcher := range searchers {
|
||||||
result, err := searcher.SearchShow(data.Key, env.Log)
|
result, err := searcher.SearchShow(data.Key, env.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Log.Errorf("error while searching show : %s", err)
|
env.Log.Errorf("error while searching show : %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Add the results to the list of results
|
||||||
shows = append(shows, result...)
|
shows = append(shows, result...)
|
||||||
}
|
}
|
||||||
|
|
||||||
env.Log.Debugf("got %d shows doing search %q", len(shows), data.Key)
|
env.Log.Debugf("got %d shows doing search %q", len(shows), data.Key)
|
||||||
showList := []*Show{}
|
|
||||||
for _, s := range shows {
|
client, err := user.NewPapiClient()
|
||||||
show := New(s.ImdbID)
|
|
||||||
err := show.GetDetails(env, user, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Log.Errorf("error while getting show details : %s", err)
|
return env.RenderError(w, err)
|
||||||
continue
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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.Error(err)
|
||||||
}
|
}
|
||||||
showList = append(showList, show)
|
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"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
s := New(id)
|
if err := backend.AddShowToWishlist(env.Database, user.ID, id, data.Season, data.Episode); err != nil {
|
||||||
if err := s.AddToWishlist(env, user, data.Season, data.Episode); err != nil {
|
|
||||||
env.Log.Warnf("Error while adding to db : %s", err)
|
env.Log.Warnf("Error while adding to db : %s", err)
|
||||||
return env.RenderError(w, 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"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
s := New(id)
|
if err := backend.DeleteShowFromWishlist(env.Database, user.ID, id); err != nil {
|
||||||
if err := s.DeleteFromWishlist(env, user); err != nil {
|
|
||||||
env.Log.Warnf("Error while deleting to db : %s", err)
|
env.Log.Warnf("Error while deleting to db : %s", err)
|
||||||
return env.RenderError(w, 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"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
shows, err := GetWishlist(env, user)
|
client, err := user.NewPapiClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
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
|
// PolochonShowsHandler will returns shows from Polochon
|
||||||
func getPolochonShows(user *users.User) ([]*Show, error) {
|
func PolochonShowsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) 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 {
|
|
||||||
v := auth.GetCurrentUser(r, env.Log)
|
v := auth.GetCurrentUser(r, env.Log)
|
||||||
user, ok := v.(*users.User)
|
user, ok := v.(*users.User)
|
||||||
if !ok {
|
if !ok {
|
||||||
return env.RenderError(w, errors.New("invalid user type"))
|
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 {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var polochonConfig config.UserPolochon
|
// Get details in DB for each shows
|
||||||
err = user.GetConfig("polochon", &polochonConfig)
|
// Fetch the details if not found
|
||||||
if err != nil {
|
|
||||||
return env.RenderError(w, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
detailer, err := pam.New(&pam.Params{
|
|
||||||
Endpoint: polochonConfig.URL,
|
|
||||||
Token: polochonConfig.Token,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, s := range shows {
|
for _, s := range shows {
|
||||||
s.Detailers = []polochon.Detailer{detailer}
|
// First try from the db
|
||||||
err := s.GetDetails(env, user, false)
|
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 {
|
if err != nil {
|
||||||
env.Log.Error(err)
|
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
|
// RefreshEpisodeHandler refresh details of an episode
|
||||||
func RefreshEpisodeHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
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)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
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"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
s := New(id)
|
client, err := user.NewPapiClient()
|
||||||
e, err := s.GetEpisodeDetails(env, season, episode, force)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = e.GetTorrents(env, force)
|
pShow, err := client.GetShow(id)
|
||||||
if err != nil && customError.IsFatal(err) {
|
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)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the show from the polochon of the user
|
// Refresh the torrents
|
||||||
pShow, err := getPolochonShow(user, id)
|
err = e.RefreshTorrents(env, env.Config.ShowTorrenters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Log.Warnf("error while getting polochon episode %s S%02dE%02d : %s", id, season, episode, err)
|
env.Log.Error(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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return env.RenderJSON(w, e)
|
return env.RenderJSON(w, e)
|
||||||
|
@ -1,398 +1,216 @@
|
|||||||
package shows
|
package shows
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"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/users"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/odwrtw/papi"
|
||||||
"github.com/odwrtw/polochon/lib"
|
"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
|
// Show represents a show
|
||||||
type Show struct {
|
type Show struct {
|
||||||
sqly.BaseModel
|
client *papi.Client
|
||||||
polochon.Show
|
pShow *papi.Show
|
||||||
Episodes []*Episode `json:"episodes"`
|
*polochon.Show
|
||||||
TrackedSeason *int `json:"tracked_season"`
|
TrackedSeason *int `json:"tracked_season"`
|
||||||
TrackedEpisode *int `json:"tracked_episode"`
|
TrackedEpisode *int `json:"tracked_episode"`
|
||||||
|
publicDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
BannerURL string `json:"banner_url"`
|
||||||
FanartURL string `json:"fanart_url"`
|
FanartURL string `json:"fanart_url"`
|
||||||
PosterURL string `json:"poster_url"`
|
PosterURL string `json:"poster_url"`
|
||||||
}
|
}{
|
||||||
|
alias: (*alias)(s),
|
||||||
// ShowDB represents the Show in the DB
|
BannerURL: s.GetImageURL("banner"),
|
||||||
type ShowDB struct {
|
FanartURL: s.GetImageURL("fanart"),
|
||||||
ID string `db:"id"`
|
PosterURL: s.GetImageURL("poster"),
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
if s.FirstAired != nil {
|
|
||||||
sDB.FirstAired = *s.FirstAired
|
|
||||||
}
|
|
||||||
return sDB
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillFromDB returns a Show from a ShowDB extracted from the DB
|
// Create Episode obj from polochon.Episodes and add them to the object to
|
||||||
func (s *Show) FillFromDB(sDB *ShowDB) {
|
// marshal
|
||||||
s.ID = sDB.ID
|
for _, e := range s.Show.Episodes {
|
||||||
s.ImdbID = sDB.ImdbID
|
showToMarshal.Episodes = append(showToMarshal.Episodes, Episode{
|
||||||
s.Title = sDB.Title
|
ShowEpisode: e,
|
||||||
s.Rating = sDB.Rating
|
client: s.client,
|
||||||
s.Plot = sDB.Plot
|
pShow: s.pShow,
|
||||||
s.TvdbID = sDB.TvdbID
|
})
|
||||||
s.Year = sDB.Year
|
}
|
||||||
s.FirstAired = &sDB.FirstAired
|
return json.Marshal(showToMarshal)
|
||||||
s.Created = sDB.Created
|
|
||||||
s.Updated = sDB.Updated
|
|
||||||
s.TrackedSeason = sDB.TrackedSeason
|
|
||||||
s.TrackedEpisode = sDB.TrackedEpisode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Show with a polochon ShowConfig
|
// New returns a new Show with a polochon ShowConfig
|
||||||
func New(imdbID string) *Show {
|
func New(imdbID string, publicDir string) *Show {
|
||||||
return &Show{
|
return &Show{
|
||||||
Show: polochon.Show{
|
Show: &polochon.Show{
|
||||||
ImdbID: imdbID,
|
ImdbID: imdbID,
|
||||||
},
|
},
|
||||||
|
publicDir: publicDir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a show with user info like tracked
|
// NewWithClient returns a new Show with a polochon ShowConfig
|
||||||
func (s *Show) Get(env *web.Env, user *users.User) error {
|
func NewWithClient(imdbID string, client *papi.Client, pShow *papi.Show, wShow *backend.WishedShow, publicDir string) *Show {
|
||||||
var err error
|
s := &Show{
|
||||||
var sDB ShowDB
|
Show: &polochon.Show{
|
||||||
if s.ID != "" {
|
ImdbID: imdbID,
|
||||||
err = env.Database.QueryRowx(getShowWithUserQueryByID, s.ID, user.ID).StructScan(&sDB)
|
},
|
||||||
} else if s.ImdbID != "" {
|
client: client,
|
||||||
err = env.Database.QueryRowx(getShowWithUserQueryByImdbID, s.ImdbID, user.ID).StructScan(&sDB)
|
pShow: pShow,
|
||||||
} else {
|
publicDir: publicDir,
|
||||||
err = fmt.Errorf("Can't get show details, you have to specify an ID or ImdbID")
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if wShow != nil {
|
||||||
return err
|
s.TrackedSeason = &wShow.Season
|
||||||
|
s.TrackedEpisode = &wShow.Episode
|
||||||
}
|
}
|
||||||
// Set the poster url
|
return s
|
||||||
s.PosterURL = s.GetPosterURL(env)
|
|
||||||
|
|
||||||
s.FillFromDB(&sDB)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDetails retrieves details for the show, first try to
|
// GetDetails retrieves details for the show with the given detailers
|
||||||
// get info from db, if not exists, use polochon.Detailer
|
func (s *Show) GetDetails(env *web.Env, detailers []polochon.Detailer) error {
|
||||||
// and save informations in the database for future use
|
|
||||||
func (s *Show) GetDetails(env *web.Env, user *users.User, force bool) error {
|
|
||||||
log := env.Log.WithFields(logrus.Fields{
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
"imdb_id": s.ImdbID,
|
"imdb_id": s.ImdbID,
|
||||||
"function": "shows.GetDetails",
|
"function": "shows.GetDetails",
|
||||||
})
|
})
|
||||||
log.Debugf("getting details")
|
var detailersName []string
|
||||||
|
for _, d := range detailers {
|
||||||
if len(s.Detailers) == 0 {
|
detailersName = append(detailersName, d.Name())
|
||||||
s.Detailers = env.Config.ShowDetailers
|
|
||||||
}
|
}
|
||||||
|
log.Debugf("getting details with %s", strings.Join(detailersName, ", "))
|
||||||
|
|
||||||
var err error
|
s.Detailers = detailers
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDetail
|
// Get the details
|
||||||
err = s.Show.GetDetails(env.Log)
|
err := s.Show.GetDetails(env.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("got details from detailers")
|
log.Debugf("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)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPosterURL returns the image URL or the default image if the poster is not yet downloaded
|
// GetAndFetch retrieves details for the show with the given
|
||||||
func (s *Show) GetPosterURL(env *web.Env) string {
|
// 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
|
// 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 ?
|
// TODO image in the config ?
|
||||||
return "img/noimage.png"
|
return "img/noimage.png"
|
||||||
}
|
}
|
||||||
return s.imgURL(env, "poster")
|
return s.imgURL(imgType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadImages will download the show images
|
// downloadImages will download the show images
|
||||||
func (s *Show) downloadImages(env *web.Env) {
|
func (s *Show) downloadImages(env *web.Env) {
|
||||||
// Download the banner
|
// 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 {
|
if err != nil {
|
||||||
env.Log.Errorf("failed to dowload banner: %s", err)
|
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 {
|
if err != nil {
|
||||||
env.Log.Errorf("failed to dowload fanart: %s", err)
|
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 {
|
if err != nil {
|
||||||
env.Log.Errorf("failed to dowload poster: %s", err)
|
env.Log.Errorf("failed to dowload poster: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// imgFile returns the image location on disk
|
// 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)
|
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
|
// 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)
|
return fmt.Sprintf("img/shows/%s-%s.jpg", s.ImdbID, imgType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upsert a show in the database
|
// getPolochonShows returns all the Shows from the polochon of a user
|
||||||
func (s *Show) Upsert(db *sqlx.DB) error {
|
func getPolochonShows(env *web.Env, user *users.User) ([]*Show, error) {
|
||||||
sDB := NewShowDB(s)
|
shows := []*Show{}
|
||||||
var id string
|
|
||||||
r, err := db.NamedQuery(upsertShowQuery, sDB)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for r.Next() {
|
|
||||||
r.Scan(&id)
|
|
||||||
}
|
|
||||||
s.ID = id
|
|
||||||
|
|
||||||
for _, e := range s.Episodes {
|
client, err := user.NewPapiClient()
|
||||||
e.ShowImdbID = s.ImdbID
|
|
||||||
err = e.Upsert(db)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return shows, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the polochon's shows
|
||||||
|
pshows, err := client.GetShows()
|
||||||
|
if err != nil {
|
||||||
|
return shows, err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
wShows, err := backend.GetShowWishlist(env.Database, user.ID)
|
||||||
|
if err != nil {
|
||||||
// Delete show from database
|
return shows, err
|
||||||
func (s *Show) Delete(db *sqlx.DB) error {
|
}
|
||||||
r, err := db.Exec(deleteShowQueryByID, s.ID)
|
|
||||||
if err != nil {
|
// Create Shows objects from the shows retrieved
|
||||||
return err
|
for _, pShow := range pshows.List() {
|
||||||
}
|
wShow, _ := wShows.IsShowInWishlist(pShow.ImdbID)
|
||||||
count, _ := r.RowsAffected()
|
show := NewWithClient(pShow.ImdbID, client, pShow, wShow, env.Config.PublicDir)
|
||||||
if count != 1 {
|
shows = append(shows, show)
|
||||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
}
|
||||||
}
|
return shows, nil
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(episodesDB) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -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"
|
"net/http"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
"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/users"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||||
|
|
||||||
"github.com/odwrtw/papi"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DownloadHandler downloads a movie via polochon
|
// 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"))
|
return env.RenderError(w, errors.New("invalid user type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
var polochonConfig config.UserPolochon
|
client, err := user.NewPapiClient()
|
||||||
err = user.GetConfig("polochon", &polochonConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, errors.New("problem when getting user config"))
|
return env.RenderError(w, err)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.AddTorrent(data.URL)
|
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"
|
"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 {
|
func SignupPOSTHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
var data struct {
|
var data struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
@ -2,10 +2,14 @@ package users
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/jmoiron/sqlx/types"
|
"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/random"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/sqly"
|
||||||
)
|
)
|
||||||
@ -23,7 +27,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// UserRole represents the user's role
|
||||||
UserRole = "user"
|
UserRole = "user"
|
||||||
|
// AdminRole represents the admin's role
|
||||||
AdminRole = "admin"
|
AdminRole = "admin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,6 +95,25 @@ func (u *User) NewConfig() error {
|
|||||||
return u.RawConfig.UnmarshalJSON(b)
|
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
|
// Token represents a token
|
||||||
type Token struct {
|
type Token struct {
|
||||||
sqly.BaseModel
|
sqly.BaseModel
|
||||||
@ -201,6 +226,7 @@ func (u *User) GetName() string {
|
|||||||
return u.Name
|
return u.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasRole checks if a user as a role
|
||||||
func (u *User) HasRole(role string) bool {
|
func (u *User) HasRole(role string) bool {
|
||||||
if role == AdminRole && !u.Admin {
|
if role == AdminRole && !u.Admin {
|
||||||
return false
|
return false
|
||||||
@ -208,6 +234,7 @@ func (u *User) HasRole(role string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAdmin checks if a user is admin
|
||||||
func (u *User) IsAdmin() bool {
|
func (u *User) IsAdmin() bool {
|
||||||
return u.HasRole(AdminRole)
|
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/Sirupsen/logrus"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
polochon "github.com/odwrtw/polochon/lib"
|
||||||
"github.com/unrolled/render"
|
"github.com/unrolled/render"
|
||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Env describes an environement object passed to all handlers
|
// Env describes an environement object passed to all handlers
|
||||||
type Env struct {
|
type Env struct {
|
||||||
|
Backend DetailerTorrenter
|
||||||
Database *sqlx.DB
|
Database *sqlx.DB
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
Router *mux.Router
|
Router *mux.Router
|
||||||
@ -28,6 +30,14 @@ type EnvParams struct {
|
|||||||
Auth *auth.Authorizer
|
Auth *auth.Authorizer
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
Config *config.Config
|
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
|
// NewEnv returns a new *Env
|
||||||
@ -39,24 +49,29 @@ func NewEnv(p EnvParams) *Env {
|
|||||||
Auth: p.Auth,
|
Auth: p.Auth,
|
||||||
Config: p.Config,
|
Config: p.Config,
|
||||||
Render: render.New(),
|
Render: render.New(),
|
||||||
|
Backend: p.Backend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Route represents a route
|
||||||
type Route struct {
|
type Route struct {
|
||||||
env *Env
|
env *Env
|
||||||
mRoute *mux.Route
|
mRoute *mux.Route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of a route
|
||||||
func (r *Route) Name(name string) *Route {
|
func (r *Route) Name(name string) *Route {
|
||||||
r.mRoute.Name(name)
|
r.mRoute.Name(name)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Methods returns a list of methods for a route
|
||||||
func (r *Route) Methods(methods ...string) *Route {
|
func (r *Route) Methods(methods ...string) *Route {
|
||||||
r.mRoute.Methods(methods...)
|
r.mRoute.Methods(methods...)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithRole sets a given role for a route
|
||||||
func (r *Route) WithRole(role string) *Route {
|
func (r *Route) WithRole(role string) *Route {
|
||||||
handler := r.mRoute.GetHandler()
|
handler := r.mRoute.GetHandler()
|
||||||
newHandler := negroni.New(
|
newHandler := negroni.New(
|
||||||
|
70
src/main.go
70
src/main.go
@ -5,31 +5,19 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
"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/config"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/external_medias"
|
extmedias "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"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"github.com/phyber/negroni-gzip/gzip"
|
"github.com/phyber/negroni-gzip/gzip"
|
||||||
|
"github.com/robfig/cron"
|
||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 main() {
|
func main() {
|
||||||
var cfgPath string
|
var cfgPath string
|
||||||
cfgPath = os.Getenv("CONFIG_FILE")
|
cfgPath = os.Getenv("CONFIG_FILE")
|
||||||
@ -48,62 +36,60 @@ func main() {
|
|||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connect to the database
|
||||||
db, err := sqlx.Connect("postgres", cf.PGDSN)
|
db, err := sqlx.Connect("postgres", cf.PGDSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
uBackend := &UserBackend{Database: db}
|
backend := &backend.Backend{Database: db}
|
||||||
|
|
||||||
|
// Generate auth params
|
||||||
authParams := auth.Params{
|
authParams := auth.Params{
|
||||||
Backend: uBackend,
|
Backend: backend,
|
||||||
Pepper: cf.Authorizer.Pepper,
|
Pepper: cf.Authorizer.Pepper,
|
||||||
Cost: cf.Authorizer.Cost,
|
Cost: cf.Authorizer.Cost,
|
||||||
Secret: cf.Authorizer.Secret,
|
Secret: cf.Authorizer.Secret,
|
||||||
}
|
}
|
||||||
authorizer := auth.New(authParams)
|
authorizer := auth.New(authParams)
|
||||||
|
|
||||||
|
// Create web environment needed by the app
|
||||||
env := web.NewEnv(web.EnvParams{
|
env := web.NewEnv(web.EnvParams{
|
||||||
Database: db,
|
Database: db,
|
||||||
Auth: authorizer,
|
Auth: authorizer,
|
||||||
Log: log,
|
Log: log,
|
||||||
Config: cf,
|
Config: cf,
|
||||||
|
Backend: web.DetailerTorrenter{
|
||||||
|
Torrenter: backend,
|
||||||
|
Detailer: backend,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
authMiddleware := auth.NewMiddleware(env.Auth, log)
|
authMiddleware := auth.NewMiddleware(env.Auth, log)
|
||||||
|
|
||||||
env.Handle("/users/login", users.LoginPOSTHandler).Methods("POST")
|
// Setup the routes
|
||||||
env.Handle("/users/signup", users.SignupPOSTHandler).Methods("POST")
|
setupRoutes(env)
|
||||||
env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole).Methods("GET")
|
|
||||||
env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole).Methods("POST")
|
|
||||||
|
|
||||||
env.Handle("/movies/polochon", movies.FromPolochon).WithRole(users.UserRole).Methods("GET")
|
// Create the cron object
|
||||||
env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.GetDetailsHandler).WithRole(users.UserRole).Methods("POST")
|
c := cron.New()
|
||||||
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")
|
|
||||||
|
|
||||||
env.Handle("/shows/polochon", shows.FromPolochon).WithRole(users.UserRole).Methods("GET")
|
// Refresh the library every 6h
|
||||||
env.Handle("/shows/{id:tt[0-9]+}", shows.GetDetailsHandler).WithRole(users.UserRole).Methods("GET")
|
c.AddFunc("@every 6h", func() {
|
||||||
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}", shows.RefreshEpisodeHandler).WithRole(users.UserRole).Methods("POST")
|
env.Log.Infof("Running refresh cron!")
|
||||||
env.Handle("/shows/{id:tt[0-9]+}/refresh", shows.RefreshDetailsHandler).WithRole(users.UserRole).Methods("POST")
|
extmedias.Refresh(env)
|
||||||
env.Handle("/shows/refresh", extmedias.RefreshShows).WithRole(users.UserRole).Methods("POST")
|
})
|
||||||
env.Handle("/shows/explore", extmedias.ExploreShows).WithRole(users.UserRole).Methods("GET")
|
|
||||||
env.Handle("/shows/search", shows.SearchShow).WithRole(users.UserRole).Methods("POST")
|
|
||||||
|
|
||||||
env.Handle("/torrents", torrents.DownloadHandler).WithRole(users.UserRole).Methods("POST")
|
// Start the cron
|
||||||
|
c.Start()
|
||||||
|
|
||||||
env.Handle("/wishlist/shows", shows.GetWishlistHandler).WithRole(users.UserRole).Methods("GET")
|
// Stop the cron
|
||||||
env.Handle("/wishlist/shows/{id:tt[0-9]+}", shows.AddToWishlist).WithRole(users.UserRole).Methods("POST")
|
defer c.Stop()
|
||||||
env.Handle("/wishlist/shows/{id:tt[0-9]+}", shows.DeleteFromWishlist).WithRole(users.UserRole).Methods("DELETE")
|
|
||||||
|
|
||||||
env.Handle("/wishlist/movies", movies.GetWishlistHandler).WithRole(users.UserRole).Methods("GET")
|
|
||||||
env.Handle("/wishlist/movies/{id:tt[0-9]+}", movies.AddToWishlist).WithRole(users.UserRole).Methods("POST")
|
|
||||||
env.Handle("/wishlist/movies/{id:tt[0-9]+}", movies.DeleteFromWishlist).WithRole(users.UserRole).Methods("DELETE")
|
|
||||||
|
|
||||||
n := negroni.Classic()
|
n := negroni.Classic()
|
||||||
|
// Middleware for authentication
|
||||||
n.Use(authMiddleware)
|
n.Use(authMiddleware)
|
||||||
|
// Serve static files
|
||||||
n.Use(negroni.NewStatic(http.Dir(cf.PublicDir)))
|
n.Use(negroni.NewStatic(http.Dir(cf.PublicDir)))
|
||||||
|
// Compress responses
|
||||||
n.Use(gzip.Gzip(gzip.DefaultCompression))
|
n.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||||
n.UseHandler(env.Router)
|
n.UseHandler(env.Router)
|
||||||
n.Run(":" + cf.Port)
|
n.Run(":" + cf.Port)
|
||||||
|
@ -95,7 +95,7 @@ function updateEpisode(show, fetching, data = null) {
|
|||||||
|
|
||||||
let seasonIndex = show.seasons.map((el) => el.season).indexOf(data.season.toString());
|
let seasonIndex = show.seasons.map((el) => el.season).indexOf(data.season.toString());
|
||||||
let episodeIndex = show.seasons[seasonIndex].episodes.map((el) => el.episode).indexOf(data.episode);
|
let episodeIndex = show.seasons[seasonIndex].episodes.map((el) => el.episode).indexOf(data.episode);
|
||||||
if ('id' in data) {
|
if ('imdb_id' in data) {
|
||||||
show.seasons[seasonIndex].episodes[episodeIndex] = data;
|
show.seasons[seasonIndex].episodes[episodeIndex] = data;
|
||||||
}
|
}
|
||||||
show.seasons[seasonIndex].episodes[episodeIndex].fetching = fetching;
|
show.seasons[seasonIndex].episodes[episodeIndex].fetching = fetching;
|
||||||
|
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