Skip to content

Commit

Permalink
Merge pull request #33 from 0x113/movie-by-id
Browse files Browse the repository at this point in the history
implemented function to get movie based on its id
  • Loading branch information
0x113 authored Jul 21, 2020
2 parents fede876 + 937dc9a commit cbee3ac
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 11 deletions.
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ services:
- traefik.http.routers.moviesvc.rule=Host("moviesvc")
ports:
- 8004:8004
volumes:
- /home/y0x/Videos:/data/movies

# MOVIE DATABASE
xmedia-movie-db:
Expand Down
20 changes: 20 additions & 0 deletions movie-svc/data/mongo_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (

"github.com/0x113/x-media/movie-svc/databases"
"github.com/0x113/x-media/movie-svc/models"

"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

const (
Expand Down Expand Up @@ -119,3 +121,21 @@ func (r *movieRepository) GetAll() ([]*models.Movie, error) {

return movies, nil
}

// GetByID returns movie from the database based on its id
func (r *movieRepository) GetByID(id primitive.ObjectID) (*models.Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

sessionCopy := databases.Database.Session
defer sessionCopy.EndSession(ctx)

collection := sessionCopy.Client().Database(databases.Database.DbName).Collection(collectionName)

var movie models.Movie
if err := collection.FindOne(ctx, bson.M{"_id": id}).Decode(&movie); err != nil {
return nil, err
}

return &movie, nil
}
7 changes: 6 additions & 1 deletion movie-svc/data/repository.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package data

import "github.com/0x113/x-media/movie-svc/models"
import (
"github.com/0x113/x-media/movie-svc/models"

"go.mongodb.org/mongo-driver/bson/primitive"
)

// MovieRepository contains all methods for operation on the Movie model
type MovieRepository interface {
Expand All @@ -9,4 +13,5 @@ type MovieRepository interface {
GetByTitle(title string) (*models.Movie, error)
GetByOriginalTitle(title string) (*models.Movie, error)
GetAll() ([]*models.Movie, error)
GetByID(id primitive.ObjectID) (*models.Movie, error)
}
1 change: 1 addition & 0 deletions movie-svc/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwX
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE=
go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.mongodb.org/mongo-driver v1.3.5 h1:S0ZOruh4YGHjD7JoN7mIsTrNjnQbOjrmgrx6l6pZN7I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
Expand Down
18 changes: 18 additions & 0 deletions movie-svc/handler/movie.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handler

import (
"fmt"
"net/http"

"github.com/0x113/x-media/movie-svc/models"
Expand All @@ -18,6 +19,7 @@ func NewMovieHandler(router *echo.Echo, movieService service.MovieService) {
h := &movieHandler{movieService}
router.POST("/api/v1/movies/update/all", h.UpdateAllMovies)
router.GET("/api/v1/movies/all", h.GetAllMovies)
router.GET("/api/v1/movies/:id", h.GetMovieByID)
}

// UpdateAllMovies calls the service to update all movies from the given directories
Expand Down Expand Up @@ -59,3 +61,19 @@ func (h *movieHandler) GetAllMovies(c echo.Context) error {
}
return c.JSON(http.StatusOK, res)
}

// GetMovieByID calls the movie service to get movie based on its id]
func (h *movieHandler) GetMovieByID(c echo.Context) error {
errMsg := new(models.Error)
id := c.Param("id")
movie, err := h.movieService.GetMovieByID(id)
if err != nil {
errMsg.Code = http.StatusInternalServerError
errMsg.Message = err.Error()
c.JSON(errMsg.Code, errMsg)
return err
}
fmt.Println(movie)

return c.JSON(http.StatusOK, movie)
}
45 changes: 45 additions & 0 deletions movie-svc/handler/movie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,48 @@ func (suite *MovieHandlerTestSuite) TestGetAllMovies() {
suite.Nil(err)
suite.Equal(200, rec.Code)
}

func (suite *MovieHandlerTestSuite) TestGetMovieByID() {
// setup
suite.httpClient = &mocks.MockClient{}
suite.movieService = service.NewMovieService(suite.movieRepository, suite.httpClient)
h := movieHandler{suite.movieService}

testCases := []struct {
name string
id string
expectedStatusCode int
wantErr bool
}{
{
name: "Success",
id: "507f1f77bcf86cd799439011",
expectedStatusCode: 200,
wantErr: false,
},
{
name: "Service error; non-existent movie or incorrect object id",
id: "507f1f77bcf86cd799439010",
expectedStatusCode: 500,
wantErr: true,
},
}

for _, tt := range testCases {
suite.Run(tt.name, func() {
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := suite.router.NewContext(req, rec)
c.SetPath("/api/v1/movies/:id")
c.SetParamNames("id")
c.SetParamValues(tt.id)
err := h.GetMovieByID(c)
if tt.wantErr {
suite.NotNil(err)
} else {
suite.Nil(err)
}
suite.Equal(tt.expectedStatusCode, rec.Code)
})
}
}
1 change: 1 addition & 0 deletions movie-svc/mocks/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type MockClient struct {
DoFunc func(req *http.Request) (*http.Response, error)
}

// Do is mocked function from the http.Client
func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
if m.DoFunc != nil {
return m.DoFunc(req)
Expand Down
19 changes: 18 additions & 1 deletion movie-svc/mocks/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"

"github.com/0x113/x-media/movie-svc/models"

"go.mongodb.org/mongo-driver/bson/primitive"
)

// MockMovieRepository represents in-memory user repository
Expand All @@ -14,7 +16,12 @@ type MockMovieRepository struct {
// NewMockMovieRepository creates new mocked movie repository
func NewMockMovieRepository() *MockMovieRepository {
var movies = map[string]*models.Movie{}
id, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439011")
if err != nil {
panic(err)
}
movies["Heat"] = &models.Movie{
ID: id,
TMDbID: 949,
Title: "Heat",
Overview: "Obsessive master thief, Neil McCauley leads a top-notch crew on various daring heists throughout Los Angeles while determined detective, Vincent Hanna pursues him without rest. Each man recognizes and respects the ability and the dedication of the other even though they are aware their cat-and-mouse game may end in violence.",
Expand Down Expand Up @@ -76,7 +83,7 @@ func (m *MockMovieRepository) GetByOriginalTitle(title string) (*models.Movie, e
return nil, fmt.Errorf("Couldn't get movie %s: no such movie in the database", title)
}

// GetAll returns all moves from the mocked database
// GetAll returns all movies from the mocked database
func (m *MockMovieRepository) GetAll() ([]*models.Movie, error) {
var movies []*models.Movie
for _, movie := range m.movies {
Expand All @@ -85,3 +92,13 @@ func (m *MockMovieRepository) GetAll() ([]*models.Movie, error) {

return movies, nil
}

// GetByID returns movie from the mocked database by its id
func (m *MockMovieRepository) GetByID(id primitive.ObjectID) (*models.Movie, error) {
for _, movie := range m.movies {
if movie.ID == id {
return movie, nil
}
}
return nil, fmt.Errorf("Unable to find movie with id: %s", id)
}
38 changes: 29 additions & 9 deletions movie-svc/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
"github.com/0x113/x-media/movie-svc/models"
"github.com/0x113/x-media/movie-svc/utils/filenameparser"
"github.com/0x113/x-media/movie-svc/utils/scandir"
"go.mongodb.org/mongo-driver/bson/primitive"

log "github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson/primitive"
)

// MovieService defines the movie service
Expand All @@ -22,6 +22,7 @@ type MovieService interface {
UpdateAllMovies(lang string) (map[string]string, map[string]string)
GetAllMovies() ([]*models.Movie, error)
GetLocalTMDbID(filename string) (int, error)
GetMovieByID(id string) (*models.Movie, error)
}

type movieService struct {
Expand All @@ -38,8 +39,8 @@ func NewMovieService(repo data.MovieRepository, httpClient httpclient.HTTPClient
// based on its ID and saves it to the database if doesn't exist
// or updates if exists.
func (s *movieService) UpdateMovieByID(id int, lang, filePath string, mutex *sync.Mutex) (*models.Movie, error) {
tmdbApiClient := &tmdb.TMDbAPIClient{s.httpClient}
tmdbMovie, err := tmdbApiClient.GetTMDbMovieInfo(id, lang)
tmdbAPIClient := &tmdb.TMDbAPIClient{s.httpClient}
tmdbMovie, err := tmdbAPIClient.GetTMDbMovieInfo(id, lang)
if err != nil {
log.Errorf("Unable to get the data from the TMDb API [movie_id: %d, lang: %s]: %v", id, lang, err)
return nil, err
Expand Down Expand Up @@ -96,11 +97,11 @@ func (s *movieService) UpdateMovieByID(id int, lang, filePath string, mutex *syn
// to the database if it doesn't exist or updates movie if there is already one.
func (s *movieService) UpdateAllMovies(lang string) (map[string]string, map[string]string) {
errorsMap := make(map[string]string)
type moviePathId struct {
type moviePathID struct {
filepath string
id int
}
var movieIDs []*moviePathId // contains list of moviePathId (filepath: tmdb_id)
var movieIDs []*moviePathID // contains list of moviePathID (filepath: tmdb_id)

for _, dir := range common.Config.MovieDirectories {
// get files from the given directories
Expand All @@ -122,7 +123,7 @@ func (s *movieService) UpdateAllMovies(lang string) (map[string]string, map[stri
errorsMap[title] = err.Error()
continue
}
movieIDs = append(movieIDs, &moviePathId{f, id})
movieIDs = append(movieIDs, &moviePathID{f, id})
}
}

Expand All @@ -132,7 +133,7 @@ func (s *movieService) UpdateAllMovies(lang string) (map[string]string, map[stri
wg.Add(len(movieIDs))

for _, m := range movieIDs {
go func(m *moviePathId) {
go func(m *moviePathID) {
defer wg.Done()
movie, err := s.UpdateMovieByID(m.id, lang, m.filepath, &mutex)
if err != nil {
Expand Down Expand Up @@ -164,10 +165,29 @@ func (s *movieService) GetAllMovies() ([]*models.Movie, error) {

// GetLocalTMDbID calls the TMDb API to get movie ID based on its title.
func (s *movieService) GetLocalTMDbID(title string) (int, error) {
tmdbApiClient := &tmdb.TMDbAPIClient{s.httpClient}
tmdbQMovie, err := tmdbApiClient.GetTMDbQueryMovieInfo(title, "en") // NOTE: "lang" param is probably useless
tmdbAPIClient := &tmdb.TMDbAPIClient{s.httpClient}
tmdbQMovie, err := tmdbAPIClient.GetTMDbQueryMovieInfo(title, "en") // NOTE: "lang" param is probably useless
if err != nil {
return 0, err
}
return tmdbQMovie.ID, nil
}

// GetMovieByID converts provided id to the ObjectID and then
// returns movie from the database with this id
func (s *movieService) GetMovieByID(id string) (*models.Movie, error) {
movieID, err := primitive.ObjectIDFromHex(id)
if err != nil {
log.Errorf("Unable to convert string id to ObjectID: %s", id)
return nil, fmt.Errorf("Couldn't get movie from the database: unable to convert provided id")
}

movie, err := s.repo.GetByID(movieID)
if err != nil {
log.Errorf("Unable to get movie by id: %s; err: %v", id, err)
return nil, fmt.Errorf("Couldn't get movie from the database")
}

log.Infof("Successfully found movie with id: %s", id)
return movie, nil
}
65 changes: 65 additions & 0 deletions movie-svc/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/0x113/x-media/movie-svc/mocks"
"github.com/0x113/x-media/movie-svc/models"
"github.com/0x113/x-media/movie-svc/service"
"go.mongodb.org/mongo-driver/bson/primitive"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/suite"
Expand All @@ -21,12 +22,16 @@ import (
type MovieServiceTestSuite struct {
suite.Suite
httpClient httpclient.HTTPClient
movieID primitive.ObjectID
movieRepo *mocks.MockMovieRepository
movieService service.MovieService
}

// SetupTest initiates mocked database and disables the logrus output
func (suite *MovieServiceTestSuite) SetupTest() {
id, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439011") // must be same like in the mocked database
suite.Nil(err)
suite.movieID = id
common.Config = &common.Configuration{
TMDbAPIKey: "fake-key",
}
Expand Down Expand Up @@ -409,6 +414,7 @@ func (suite *MovieServiceTestSuite) TestGetAll() {

expectedMovies := []*models.Movie{
&models.Movie{
ID: suite.movieID,
TMDbID: 949,
Title: "Heat",
Overview: "Obsessive master thief, Neil McCauley leads a top-notch crew on various daring heists throughout Los Angeles while determined detective, Vincent Hanna pursues him without rest. Each man recognizes and respects the ability and the dedication of the other even though they are aware their cat-and-mouse game may end in violence.",
Expand All @@ -433,3 +439,62 @@ func (suite *MovieServiceTestSuite) TestGetAll() {
suite.Nil(err)
suite.Equal(expectedMovies, movies)
}

func (suite *MovieServiceTestSuite) TestGetMovieByID() {
testCases := []struct {
name string
id string
expectedMovie *models.Movie
wantErr bool
}{
{
name: "Success",
id: "507f1f77bcf86cd799439011",
expectedMovie: &models.Movie{
ID: suite.movieID,
TMDbID: 949,
Title: "Heat",
Overview: "Obsessive master thief, Neil McCauley leads a top-notch crew on various daring heists throughout Los Angeles while determined detective, Vincent Hanna pursues him without rest. Each man recognizes and respects the ability and the dedication of the other even though they are aware their cat-and-mouse game may end in violence.",
OriginalTitle: "Heat",
OriginalLanguage: "en",
ReleaseDate: "1995-12-15",
Genres: []string{
"Action",
"Crime",
"Drama",
"Thriller",
},
Rating: 7.9,
Runtime: 170,
BackdropPath: "/rfEXNlql4CafRmtgp2VFQrBC4sh.jpg",
PosterPath: "/rrBuGu0Pjq7Y2BWSI6teGfZzviY.jpg",
DirPath: "/home/y0x/Videos/Heat.1995.mp4",
},
wantErr: false,
},
{
name: "Incorrect object id",
id: "123",
expectedMovie: nil,
wantErr: true,
},
{
name: "No movie with provided id in the database",
id: "507f1f77bcf86cd799439010",
expectedMovie: nil,
wantErr: true,
},
}

for _, tt := range testCases {
suite.Run(tt.name, func() {
movie, err := suite.movieService.GetMovieByID(tt.id)
if tt.wantErr {
suite.NotNil(err)
} else {
suite.Nil(err)
}
suite.Equal(tt.expectedMovie, movie)
})
}
}

0 comments on commit cbee3ac

Please sign in to comment.