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-password", loggedIn(fresh(handler.ChangePassword(logger, conn))))
|
||||
|
||||
// Movies Search
|
||||
route("GET /movies", handler.MoviesPage())
|
||||
route("POST /search-movies", handler.SearchMovies(logger, config))
|
||||
|
||||
// Movie page
|
||||
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 {
|
||||
return movie.ReleaseDate[:4]
|
||||
if movie.ReleaseDate == "" {
|
||||
return ""
|
||||
} else {
|
||||
return "(" + movie.ReleaseDate[:4] + ")"
|
||||
}
|
||||
}
|
||||
|
||||
func (movie *Movie) FGenres() string {
|
||||
@@ -31,5 +35,8 @@ func (movie *Movie) FGenres() string {
|
||||
for _, genre := range movie.Genres {
|
||||
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"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -16,23 +17,51 @@ type Result struct {
|
||||
|
||||
type ResultMovies struct {
|
||||
Result
|
||||
Results []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"`
|
||||
} `json:"results"`
|
||||
Results []ResultMovie `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) {
|
||||
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