Added movie search
This commit is contained in:
41
handler/movie_search.go
Normal file
41
handler/movie_search.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"projectreshoot/config"
|
||||||
|
"projectreshoot/tmdb"
|
||||||
|
"projectreshoot/view/component/search"
|
||||||
|
"projectreshoot/view/page"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SearchMovies(
|
||||||
|
logger *zerolog.Logger,
|
||||||
|
config *config.Config,
|
||||||
|
) http.Handler {
|
||||||
|
return http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
query := r.FormValue("search")
|
||||||
|
if query == "" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
movies, err := tmdb.SearchMovies(config.TMDBToken, query, false, 1)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
search.MovieResults(movies, &config.TMDBConfig.Image).Render(r.Context(), w)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoviesPage() http.Handler {
|
||||||
|
return http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
page.Movies().Render(r.Context(), w)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -61,6 +61,10 @@ func addRoutes(
|
|||||||
route("POST /change-bio", loggedIn(handler.ChangeBio(logger, conn)))
|
route("POST /change-bio", loggedIn(handler.ChangeBio(logger, conn)))
|
||||||
route("POST /change-password", loggedIn(fresh(handler.ChangePassword(logger, conn))))
|
route("POST /change-password", loggedIn(fresh(handler.ChangePassword(logger, conn))))
|
||||||
|
|
||||||
|
// Movies Search
|
||||||
|
route("GET /movies", handler.MoviesPage())
|
||||||
|
route("POST /search-movies", handler.SearchMovies(logger, config))
|
||||||
|
|
||||||
// Movie page
|
// Movie page
|
||||||
route("GET /movie/{movie_id}", handler.Movie(logger, config))
|
route("GET /movie/{movie_id}", handler.Movie(logger, config))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ func (movie *Movie) GetPoster(image *Image, size string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (movie *Movie) ReleaseYear() string {
|
func (movie *Movie) ReleaseYear() string {
|
||||||
return movie.ReleaseDate[:4]
|
if movie.ReleaseDate == "" {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return "(" + movie.ReleaseDate[:4] + ")"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (movie *Movie) FGenres() string {
|
func (movie *Movie) FGenres() string {
|
||||||
@@ -31,5 +35,8 @@ func (movie *Movie) FGenres() string {
|
|||||||
for _, genre := range movie.Genres {
|
for _, genre := range movie.Genres {
|
||||||
genres += genre.Name + ", "
|
genres += genre.Name + ", "
|
||||||
}
|
}
|
||||||
return genres[:len(genres)-2]
|
if len(genres) > 2 {
|
||||||
|
return genres[:len(genres)-2]
|
||||||
|
}
|
||||||
|
return genres
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@@ -16,23 +17,51 @@ type Result struct {
|
|||||||
|
|
||||||
type ResultMovies struct {
|
type ResultMovies struct {
|
||||||
Result
|
Result
|
||||||
Results []struct {
|
Results []ResultMovie `json:"results"`
|
||||||
Adult bool `json:"adult"`
|
|
||||||
BackdropPath string `json:"backdrop_path"`
|
|
||||||
GenreIDs []int `json:"genre_ids"`
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
OriginalLanguage string `json:"original_language"`
|
|
||||||
OriginalTitle string `json:"original_title"`
|
|
||||||
Overview string `json:"overview"`
|
|
||||||
Popularity int `json:"popularity"`
|
|
||||||
PosterPath string `json:"poster_path"`
|
|
||||||
ReleaseDate string `json:"release_date"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Video bool `json:"video"`
|
|
||||||
VoteAverage int `json:"vote_average"`
|
|
||||||
VoteCount int `json:"vote_count"`
|
|
||||||
} `json:"results"`
|
|
||||||
}
|
}
|
||||||
|
type ResultMovie struct {
|
||||||
|
Adult bool `json:"adult"`
|
||||||
|
BackdropPath string `json:"backdrop_path"`
|
||||||
|
GenreIDs []int `json:"genre_ids"`
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
OriginalLanguage string `json:"original_language"`
|
||||||
|
OriginalTitle string `json:"original_title"`
|
||||||
|
Overview string `json:"overview"`
|
||||||
|
Popularity int `json:"popularity"`
|
||||||
|
PosterPath string `json:"poster_path"`
|
||||||
|
ReleaseDate string `json:"release_date"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Video bool `json:"video"`
|
||||||
|
VoteAverage int `json:"vote_average"`
|
||||||
|
VoteCount int `json:"vote_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (movie *ResultMovie) GetPoster(image *Image, size string) string {
|
||||||
|
base, err := url.Parse(image.SecureBaseURL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
fullPath := path.Join(base.Path, size, movie.PosterPath)
|
||||||
|
base.Path = fullPath
|
||||||
|
return base.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (movie *ResultMovie) ReleaseYear() string {
|
||||||
|
if movie.ReleaseDate == "" {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return "(" + movie.ReleaseDate[:4] + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: genres list https://developer.themoviedb.org/reference/genre-movie-list
|
||||||
|
// func (movie *ResultMovie) FGenres() string {
|
||||||
|
// genres := ""
|
||||||
|
// for _, genre := range movie.Genres {
|
||||||
|
// genres += genre.Name + ", "
|
||||||
|
// }
|
||||||
|
// return genres[:len(genres)-2]
|
||||||
|
// }
|
||||||
|
|
||||||
func SearchMovies(token string, query string, adult bool, page int) (*ResultMovies, error) {
|
func SearchMovies(token string, query string, adult bool, page int) (*ResultMovies, error) {
|
||||||
url := "https://api.themoviedb.org/3/search/movie" +
|
url := "https://api.themoviedb.org/3/search/movie" +
|
||||||
|
|||||||
44
view/component/search/movies_results.templ
Normal file
44
view/component/search/movies_results.templ
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package search
|
||||||
|
|
||||||
|
import "projectreshoot/tmdb"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
templ MovieResults(movies *tmdb.ResultMovies, image *tmdb.Image) {
|
||||||
|
for _, movie := range movies.Results {
|
||||||
|
<div
|
||||||
|
class="bg-surface0 p-4 rounded-lg shadow-lg flex
|
||||||
|
items-start space-x-4"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={ movie.GetPoster(image, "w92") }
|
||||||
|
alt="Movie Poster"
|
||||||
|
class="rounded-lg object-cover"
|
||||||
|
width="96"
|
||||||
|
height="144"
|
||||||
|
onerror="this.onerror=null; setFallbackColor(this);"
|
||||||
|
/>
|
||||||
|
<script>
|
||||||
|
function setFallbackColor(img) {
|
||||||
|
const baseColor = getComputedStyle(document.documentElement).
|
||||||
|
getPropertyValue('--base').trim();
|
||||||
|
img.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='144'%3E%3Crect width='100%' height='100%' fill='${baseColor}'/%3E%3C/svg%3E`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href={ templ.SafeURL(fmt.Sprintf("/movie/%v", movie.ID)) }
|
||||||
|
class="text-xl font-semibold transition hover:text-green"
|
||||||
|
>{ movie.Title } { movie.ReleaseYear() }</a>
|
||||||
|
<p class="text-subtext0">
|
||||||
|
Released:
|
||||||
|
<span class="font-medium">{ movie.ReleaseDate }</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-subtext0">
|
||||||
|
Original Title:
|
||||||
|
<span class="font-medium">{ movie.OriginalTitle }</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-subtext0">{ movie.Overview }</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
31
view/page/movie_search.templ
Normal file
31
view/page/movie_search.templ
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package page
|
||||||
|
|
||||||
|
import "projectreshoot/view/layout"
|
||||||
|
|
||||||
|
templ Movies() {
|
||||||
|
@layout.Global() {
|
||||||
|
<div class="max-w-4xl mx-auto md:mt-0 mt-2 px-2 md:px-0">
|
||||||
|
<form hx-post="/search-movies" hx-target="#search-movies-results">
|
||||||
|
<div
|
||||||
|
class="max-w-100 flex items-center space-x-2 mb-2"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="search"
|
||||||
|
name="search"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search movies..."
|
||||||
|
class="flex-grow p-2 border rounded-lg
|
||||||
|
bg-mantle border-surface2 shadow-sm
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-blue"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="py-2 px-4 bg-green text-mantle rounded-lg transition
|
||||||
|
hover:cursor-pointer hover:bg-green/75"
|
||||||
|
>Search</button>
|
||||||
|
</div>
|
||||||
|
<div id="search-movies-results" class="space-y-4"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user