From d8d2307859910990ef2b244b936c6edc9e56b7e2 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sun, 23 Feb 2025 14:16:27 +1100 Subject: [PATCH 01/17] Added TMDB attribution --- view/component/footer/footer.templ | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/view/component/footer/footer.templ b/view/component/footer/footer.templ index 565166e..eb5962e 100644 --- a/view/component/footer/footer.templ +++ b/view/component/footer/footer.templ @@ -75,8 +75,13 @@ templ Footer() {
-

- by Haelnorr +

+ by Haelnorr | + Film data from + TMDB

From 725038009a22ecc03323229e3066cbcd208ee180 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sun, 23 Feb 2025 15:20:45 +1100 Subject: [PATCH 02/17] Added GetMovie and related structs --- config/config.go | 5 +++++ tmdb/movie.go | 35 +++++++++++++++++++++++++++++++ tmdb/structs.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 tmdb/movie.go create mode 100644 tmdb/structs.go diff --git a/config/config.go b/config/config.go index 0b9bc90..c974639 100644 --- a/config/config.go +++ b/config/config.go @@ -30,6 +30,7 @@ type Config struct { LogLevel zerolog.Level // Log level for global logging. Defaults to info LogOutput string // "file", "console", or "both". Defaults to console LogDir string // Path to create log files + TMDBToken string // Read access token for TMDB API } // Load the application configuration and get a pointer to the Config object @@ -96,11 +97,15 @@ func GetConfig(args map[string]string) (*Config, error) { LogLevel: logLevel, LogOutput: logOutput, LogDir: GetEnvDefault("LOG_DIR", ""), + TMDBToken: os.Getenv("TMDB_API_TOKEN"), } if config.SecretKey == "" && args["dbver"] != "true" { return nil, errors.New("Envar not set: SECRET_KEY") } + if config.TMDBToken == "" && args["dbver"] != "true" { + return nil, errors.New("Envar not set: TMDB_API_TOKEN") + } return config, nil } diff --git a/tmdb/movie.go b/tmdb/movie.go new file mode 100644 index 0000000..8e081f5 --- /dev/null +++ b/tmdb/movie.go @@ -0,0 +1,35 @@ +package tmdb + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/pkg/errors" +) + +func GetMovie(id int32, token string) (*Movie, error) { + url := fmt.Sprintf("https://api.themoviedb.org/3/movie/%v?language=en-US", id) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, errors.Wrap(err, "http.NewRequest") + } + + req.Header.Add("accept", "application/json") + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, errors.Wrap(err, "http.DefaultClient.Do") + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, errors.Wrap(err, "io.ReadAll") + } + movie := Movie{} + json.Unmarshal(body, &movie) + return &movie, nil +} diff --git a/tmdb/structs.go b/tmdb/structs.go new file mode 100644 index 0000000..fd7c04a --- /dev/null +++ b/tmdb/structs.go @@ -0,0 +1,54 @@ +package tmdb + +import ( +// "encoding/json" +) + +type Movie struct { + Adult bool `json:"adult"` + Backdrop string `json:"backdrop_path"` + Collection string `json:"belongs_to_collection"` + Budget int `json:"budget"` + Genres []Genre `json:"genres"` + Homepage string `json:"homepage"` + ID int32 `json:"id"` + IMDbID string `json:"imdb_id"` + OriginalLanguage string `json:"original_language"` + OriginalTitle string `json:"original_title"` + Overview string `json:"overview"` + Popularity float32 `json:"popularity"` + Poster string `json:"poster_path"` + ProductionCompanies []ProductionCompany `json:"production_companies"` + ProductionCountries []ProductionCountry `json:"production_countries"` + ReleaseDate string `json:"release_date"` + Revenue int `json:"revenue"` + Runtime int `json:"runtime"` + SpokenLanguages []SpokenLanguage `json:"spoken_languages"` + Status string `json:"status"` + Tagline string `json:"tagline"` + Title string `json:"title"` + Video bool `json:"video"` +} + +type Genre struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type ProductionCompany struct { + ID int `json:"id"` + Logo string `json:"logo_path"` + Name string `json:"name"` + OriginCountry string `json:"origin_country"` +} + +type ProductionCountry struct { + ISO_3166_1 string `json:"iso_3166_1"` + Name string `json:"name"` +} + +type SpokenLanguage struct { + EnglishName string `json:"english_name"` + ISO_639_1 string `json:"iso_639_1"` + Name string `json:"name"` +} From e794024786d12aa396f281bcb694bc3963c0ca0b Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sun, 23 Feb 2025 16:06:47 +1100 Subject: [PATCH 03/17] Added movie page and route handler --- handler/movie.go | 37 +++++++++++++++++++++++++++++++++++++ server/routes.go | 3 +++ view/page/movie.templ | 30 ++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 handler/movie.go create mode 100644 view/page/movie.templ diff --git a/handler/movie.go b/handler/movie.go new file mode 100644 index 0000000..6deeee3 --- /dev/null +++ b/handler/movie.go @@ -0,0 +1,37 @@ +package handler + +import ( + "net/http" + "projectreshoot/config" + "projectreshoot/tmdb" + "projectreshoot/view/page" + "strconv" + + "github.com/rs/zerolog" +) + +func Movie( + logger *zerolog.Logger, + config *config.Config, +) http.Handler { + return http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("movie_id") + movie_id, err := strconv.ParseInt(id, 10, 32) + if err != nil { + ErrorPage(http.StatusNotFound, w, r) + logger.Error().Err(err).Str("movie_id", id). + Msg("Error occured getting the movie") + return + } + movie, err := tmdb.GetMovie(int32(movie_id), config.TMDBToken) + if err != nil { + ErrorPage(http.StatusInternalServerError, w, r) + logger.Error().Err(err).Int32("movie_id", int32(movie_id)). + Msg("Error occured getting the movie") + return + } + page.Movie(movie).Render(r.Context(), w) + }, + ) +} diff --git a/server/routes.go b/server/routes.go index 8462e72..66b3a05 100644 --- a/server/routes.go +++ b/server/routes.go @@ -60,4 +60,7 @@ func addRoutes( route("POST /change-username", loggedIn(fresh(handler.ChangeUsername(logger, conn)))) route("POST /change-bio", loggedIn(handler.ChangeBio(logger, conn))) route("POST /change-password", loggedIn(fresh(handler.ChangePassword(logger, conn)))) + + // Movie page + route("GET /movie/{movie_id}", handler.Movie(logger, config)) } diff --git a/view/page/movie.templ b/view/page/movie.templ new file mode 100644 index 0000000..740bc3f --- /dev/null +++ b/view/page/movie.templ @@ -0,0 +1,30 @@ +package page + +import "projectreshoot/tmdb" +import "projectreshoot/view/layout" +import "fmt" + +func formatRuntime(minutes int) string { + hours := minutes / 60 + mins := minutes % 60 + return fmt.Sprintf("%dh %02dm", hours, mins) +} + +templ Movie(movie *tmdb.Movie) { + @layout.Global() { +
+
+
{ movie.Poster }
+
+ + { movie.Title } + + + { formatRuntime(movie.Runtime) } + - { movie.ReleaseDate[:4] } + +
+
+
+ } +} From 838d6264c92f1c7a299522743e7b2cff9935efac Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sun, 23 Feb 2025 16:06:59 +1100 Subject: [PATCH 04/17] Fixed error page always sending 401 instead of correct error code --- handler/errorpage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/errorpage.go b/handler/errorpage.go index 19aa760..67fe951 100644 --- a/handler/errorpage.go +++ b/handler/errorpage.go @@ -18,7 +18,7 @@ func ErrorPage( continues to happen contact an administrator.`, 503: "The server is currently down for maintenance and should be back soon. =)", } - w.WriteHeader(http.StatusUnauthorized) + w.WriteHeader(errorCode) page.Error(errorCode, http.StatusText(errorCode), message[errorCode]). Render(r.Context(), w) } From a3e9ffb01253fbb12489097dac51eb498338c0ec Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sun, 23 Feb 2025 16:20:36 +1100 Subject: [PATCH 05/17] Added tmdb config getter and made tmdbGet function --- tmdb/config.go | 32 ++++++++++++++++++++++++++++++++ tmdb/movie.go | 48 +++++++++++++++++++++++++++++------------------- tmdb/request.go | 28 ++++++++++++++++++++++++++++ tmdb/structs.go | 26 -------------------------- 4 files changed, 89 insertions(+), 45 deletions(-) create mode 100644 tmdb/config.go create mode 100644 tmdb/request.go diff --git a/tmdb/config.go b/tmdb/config.go new file mode 100644 index 0000000..b58df22 --- /dev/null +++ b/tmdb/config.go @@ -0,0 +1,32 @@ +package tmdb + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +type Config struct { + Image Image `json:"images"` +} + +type Image struct { + BaseURL string `json:"base_url"` + SecureBaseURL string `json:"secure_base_url"` + BackdropSizes []string `json:"backdrop_sizes"` + LogoSizes []string `json:"logo_sizes"` + PosterSizes []string `json:"poster_sizes"` + ProfileSizes []string `json:"profile_sizes"` + StillSizes []string `json:"still_sizes"` +} + +func GetConfig(token string) (*Config, error) { + url := "https://api.themoviedb.org/3/configuration" + data, err := tmdbGet(url, token) + if err != nil { + return nil, errors.Wrap(err, "tmdbGet") + } + config := Config{} + json.Unmarshal(data, &config) + return &config, nil +} diff --git a/tmdb/movie.go b/tmdb/movie.go index 8e081f5..a3b77f7 100644 --- a/tmdb/movie.go +++ b/tmdb/movie.go @@ -3,33 +3,43 @@ package tmdb import ( "encoding/json" "fmt" - "io" - "net/http" "github.com/pkg/errors" ) +type Movie struct { + Adult bool `json:"adult"` + Backdrop string `json:"backdrop_path"` + Collection string `json:"belongs_to_collection"` + Budget int `json:"budget"` + Genres []Genre `json:"genres"` + Homepage string `json:"homepage"` + ID int32 `json:"id"` + IMDbID string `json:"imdb_id"` + OriginalLanguage string `json:"original_language"` + OriginalTitle string `json:"original_title"` + Overview string `json:"overview"` + Popularity float32 `json:"popularity"` + Poster string `json:"poster_path"` + ProductionCompanies []ProductionCompany `json:"production_companies"` + ProductionCountries []ProductionCountry `json:"production_countries"` + ReleaseDate string `json:"release_date"` + Revenue int `json:"revenue"` + Runtime int `json:"runtime"` + SpokenLanguages []SpokenLanguage `json:"spoken_languages"` + Status string `json:"status"` + Tagline string `json:"tagline"` + Title string `json:"title"` + Video bool `json:"video"` +} + func GetMovie(id int32, token string) (*Movie, error) { url := fmt.Sprintf("https://api.themoviedb.org/3/movie/%v?language=en-US", id) - - req, err := http.NewRequest("GET", url, nil) + data, err := tmdbGet(url, token) if err != nil { - return nil, errors.Wrap(err, "http.NewRequest") - } - - req.Header.Add("accept", "application/json") - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) - - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, errors.Wrap(err, "http.DefaultClient.Do") - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, errors.Wrap(err, "io.ReadAll") + return nil, errors.Wrap(err, "tmdbGet") } movie := Movie{} - json.Unmarshal(body, &movie) + json.Unmarshal(data, &movie) return &movie, nil } diff --git a/tmdb/request.go b/tmdb/request.go new file mode 100644 index 0000000..6c454b4 --- /dev/null +++ b/tmdb/request.go @@ -0,0 +1,28 @@ +package tmdb + +import ( + "fmt" + "io" + "net/http" + + "github.com/pkg/errors" +) + +func tmdbGet(url string, token string) ([]byte, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, errors.Wrap(err, "http.NewRequest") + } + req.Header.Add("accept", "application/json") + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, errors.Wrap(err, "http.DefaultClient.Do") + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, errors.Wrap(err, "io.ReadAll") + } + return body, nil +} diff --git a/tmdb/structs.go b/tmdb/structs.go index fd7c04a..0929dbd 100644 --- a/tmdb/structs.go +++ b/tmdb/structs.go @@ -4,32 +4,6 @@ import ( // "encoding/json" ) -type Movie struct { - Adult bool `json:"adult"` - Backdrop string `json:"backdrop_path"` - Collection string `json:"belongs_to_collection"` - Budget int `json:"budget"` - Genres []Genre `json:"genres"` - Homepage string `json:"homepage"` - ID int32 `json:"id"` - IMDbID string `json:"imdb_id"` - OriginalLanguage string `json:"original_language"` - OriginalTitle string `json:"original_title"` - Overview string `json:"overview"` - Popularity float32 `json:"popularity"` - Poster string `json:"poster_path"` - ProductionCompanies []ProductionCompany `json:"production_companies"` - ProductionCountries []ProductionCountry `json:"production_countries"` - ReleaseDate string `json:"release_date"` - Revenue int `json:"revenue"` - Runtime int `json:"runtime"` - SpokenLanguages []SpokenLanguage `json:"spoken_languages"` - Status string `json:"status"` - Tagline string `json:"tagline"` - Title string `json:"title"` - Video bool `json:"video"` -} - type Genre struct { ID int `json:"id"` Name string `json:"name"` From 8fa20e05c0e5cefdd1094c94a7b9d4aa6fdfe1a4 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sun, 23 Feb 2025 17:29:00 +1100 Subject: [PATCH 06/17] Added helper functions to tmdb package --- config/config.go | 9 ++++++++- tmdb/functions.go | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tmdb/functions.go diff --git a/config/config.go b/config/config.go index c974639..f3e1012 100644 --- a/config/config.go +++ b/config/config.go @@ -1,14 +1,15 @@ package config import ( - "errors" "fmt" "os" "time" "projectreshoot/logging" + "projectreshoot/tmdb" "github.com/joho/godotenv" + "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -31,6 +32,7 @@ type Config struct { LogOutput string // "file", "console", or "both". Defaults to console LogDir string // Path to create log files TMDBToken string // Read access token for TMDB API + TMDBConfig *tmdb.Config // Config data for interfacing with TMDB } // Load the application configuration and get a pointer to the Config object @@ -78,6 +80,10 @@ func GetConfig(args map[string]string) (*Config, error) { if logOutput != "both" && logOutput != "console" && logOutput != "file" { logOutput = "console" } + tmdbcfg, err := tmdb.GetConfig(os.Getenv("TMDB_API_TOKEN")) + if err != nil { + return nil, errors.Wrap(err, "tmdb.GetConfig") + } config := &Config{ Host: host, @@ -98,6 +104,7 @@ func GetConfig(args map[string]string) (*Config, error) { LogOutput: logOutput, LogDir: GetEnvDefault("LOG_DIR", ""), TMDBToken: os.Getenv("TMDB_API_TOKEN"), + TMDBConfig: tmdbcfg, } if config.SecretKey == "" && args["dbver"] != "true" { diff --git a/tmdb/functions.go b/tmdb/functions.go new file mode 100644 index 0000000..52663fe --- /dev/null +++ b/tmdb/functions.go @@ -0,0 +1,23 @@ +package tmdb + +import ( + "fmt" + "net/url" + "path" +) + +func FormatRuntime(minutes int) string { + hours := minutes / 60 + mins := minutes % 60 + return fmt.Sprintf("%dh %02dm", hours, mins) +} + +func GetPoster(image *Image, size, imgpath string) string { + base, err := url.Parse(image.SecureBaseURL) + if err != nil { + return "" + } + fullPath := path.Join(base.Path, size, imgpath) + base.Path = fullPath + return base.String() +} From e2d66fc26d76d9ac7a51a906df9d349dab92e41e Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Mon, 24 Feb 2025 10:21:31 +1100 Subject: [PATCH 07/17] Basic movie page layout created --- handler/movie.go | 2 +- tmdb/functions.go | 23 -------------- tmdb/movie_functions.go | 35 +++++++++++++++++++++ view/component/account/container.templ | 2 +- view/layout/global.templ | 2 +- view/page/movie.templ | 43 ++++++++++++++++---------- 6 files changed, 65 insertions(+), 42 deletions(-) delete mode 100644 tmdb/functions.go create mode 100644 tmdb/movie_functions.go diff --git a/handler/movie.go b/handler/movie.go index 6deeee3..66f4762 100644 --- a/handler/movie.go +++ b/handler/movie.go @@ -31,7 +31,7 @@ func Movie( Msg("Error occured getting the movie") return } - page.Movie(movie).Render(r.Context(), w) + page.Movie(movie, &config.TMDBConfig.Image).Render(r.Context(), w) }, ) } diff --git a/tmdb/functions.go b/tmdb/functions.go deleted file mode 100644 index 52663fe..0000000 --- a/tmdb/functions.go +++ /dev/null @@ -1,23 +0,0 @@ -package tmdb - -import ( - "fmt" - "net/url" - "path" -) - -func FormatRuntime(minutes int) string { - hours := minutes / 60 - mins := minutes % 60 - return fmt.Sprintf("%dh %02dm", hours, mins) -} - -func GetPoster(image *Image, size, imgpath string) string { - base, err := url.Parse(image.SecureBaseURL) - if err != nil { - return "" - } - fullPath := path.Join(base.Path, size, imgpath) - base.Path = fullPath - return base.String() -} diff --git a/tmdb/movie_functions.go b/tmdb/movie_functions.go new file mode 100644 index 0000000..a73f3bf --- /dev/null +++ b/tmdb/movie_functions.go @@ -0,0 +1,35 @@ +package tmdb + +import ( + "fmt" + "net/url" + "path" +) + +func (movie *Movie) FRuntime() string { + hours := movie.Runtime / 60 + mins := movie.Runtime % 60 + return fmt.Sprintf("%dh %02dm", hours, mins) +} + +func (movie *Movie) GetPoster(image *Image, size string) string { + base, err := url.Parse(image.SecureBaseURL) + if err != nil { + return "" + } + fullPath := path.Join(base.Path, size, movie.Poster) + base.Path = fullPath + return base.String() +} + +func (movie *Movie) ReleaseYear() string { + return movie.ReleaseDate[:4] +} + +func (movie *Movie) FGenres() string { + genres := "" + for _, genre := range movie.Genres { + genres += genre.Name + ", " + } + return genres[:len(genres)-2] +} diff --git a/view/component/account/container.templ b/view/component/account/container.templ index f065949..199396f 100644 --- a/view/component/account/container.templ +++ b/view/component/account/container.templ @@ -3,7 +3,7 @@ package account templ AccountContainer(subpage string) {
diff --git a/view/layout/global.templ b/view/layout/global.templ index 2469f00..a632894 100644 --- a/view/layout/global.templ +++ b/view/layout/global.templ @@ -97,7 +97,7 @@ templ Global() { class="flex flex-col h-screen justify-between" > @nav.Navbar() -
+
{ children... }
@footer.Footer() diff --git a/view/page/movie.templ b/view/page/movie.templ index 740bc3f..8cdb550 100644 --- a/view/page/movie.templ +++ b/view/page/movie.templ @@ -2,27 +2,38 @@ package page import "projectreshoot/tmdb" import "projectreshoot/view/layout" -import "fmt" -func formatRuntime(minutes int) string { - hours := minutes / 60 - mins := minutes % 60 - return fmt.Sprintf("%dh %02dm", hours, mins) -} - -templ Movie(movie *tmdb.Movie) { +templ Movie(movie *tmdb.Movie, image *tmdb.Image) { @layout.Global() { -
-
-
{ movie.Poster }
-
- +
+
+
+ Poster +
+
+ { movie.Title } - - { formatRuntime(movie.Runtime) } - - { movie.ReleaseDate[:4] } + + { movie.FGenres() } + - { movie.FRuntime() } + - { movie.ReleaseYear() } +
+
+
+
From f7f610d7ef67f6d602b174a59bc445c7eb038bea Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Tue, 25 Feb 2025 15:21:13 +1100 Subject: [PATCH 08/17] Fixed wrong error message on db check version query --- db/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/connection.go b/db/connection.go index a156590..67cdf22 100644 --- a/db/connection.go +++ b/db/connection.go @@ -39,7 +39,7 @@ func checkDBVersion(db *sql.DB, expectVer int) error { ORDER BY version_id DESC LIMIT 1` rows, err := db.Query(query) if err != nil { - return errors.Wrap(err, "checkDBVersion") + return errors.Wrap(err, "db.Query") } defer rows.Close() if rows.Next() { From 05849d028d8d6254780bbc9824bbf59c9daf5faa Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sat, 1 Mar 2025 19:11:09 +1100 Subject: [PATCH 09/17] Added movie search --- tmdb/search.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tmdb/search.go diff --git a/tmdb/search.go b/tmdb/search.go new file mode 100644 index 0000000..9ce77bd --- /dev/null +++ b/tmdb/search.go @@ -0,0 +1,50 @@ +package tmdb + +import ( + "encoding/json" + "fmt" + "net/url" + + "github.com/pkg/errors" +) + +type Result struct { + Page int `json:"page"` + TotalPages int `json:"total_pages"` + TotalResults int `json:"total_results"` +} + +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"` +} + +func SearchMovies(token string, query string, adult bool, page int) (*ResultMovies, error) { + url := "https://api.themoviedb.org/3/search/movie" + + fmt.Sprintf("?query=%s", url.QueryEscape(query)) + + fmt.Sprintf("&include_adult=%t", adult) + + fmt.Sprintf("&page=%v", page) + + "&language=en-US" + response, err := tmdbGet(url, token) + if err != nil { + return nil, errors.Wrap(err, "tmdbGet") + } + var results ResultMovies + json.Unmarshal(response, &results) + return &results, nil +} From aa47802f4665c1e2db5ae1b8b1b1d4ba33aea942 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sat, 1 Mar 2025 19:13:55 +1100 Subject: [PATCH 10/17] Removed note left over from a refactor --- tests/database.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/database.go b/tests/database.go index 0010636..db417f9 100644 --- a/tests/database.go +++ b/tests/database.go @@ -71,7 +71,6 @@ func SetupTestDB(version int64) (*sql.DB, error) { return nil, errors.Wrap(err, "provider.UpTo") } - // NOTE: ================================================== // Load the test data dataPath, err := findTestData() if err != nil { From 8fcec675e67a96cf0931f8de7bd0ceabd6050783 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sat, 1 Mar 2025 19:14:56 +1100 Subject: [PATCH 11/17] Fixed id tag in register form malformed --- view/component/form/registerform.templ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/component/form/registerform.templ b/view/component/form/registerform.templ index 4775e2b..6db824f 100644 --- a/view/component/form/registerform.templ +++ b/view/component/form/registerform.templ @@ -44,7 +44,7 @@ templ RegisterForm(registerError string) {
Date: Sat, 1 Mar 2025 19:16:21 +1100 Subject: [PATCH 13/17] Commented out htmx logging in global layout --- view/layout/global.templ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/layout/global.templ b/view/layout/global.templ index a632894..39baf93 100644 --- a/view/layout/global.templ +++ b/view/layout/global.templ @@ -41,7 +41,7 @@ templ Global() {
@@ -21,8 +59,8 @@ templ Movie(movie *tmdb.Movie, image *tmdb.Image) { { movie.FGenres() } - - { movie.FRuntime() } - - { movie.ReleaseYear() } + • { movie.FRuntime() } + • { movie.ReleaseYear() }
+
+ + { movie.Tagline } + + + Overview + + { movie.Overview } + +
From 540782e2d5ccb1023618b7aea936cf526bd433c8 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sat, 1 Mar 2025 19:23:47 +1100 Subject: [PATCH 15/17] Removed unused function --- handler/withtransaction.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 handler/withtransaction.go diff --git a/handler/withtransaction.go b/handler/withtransaction.go deleted file mode 100644 index 37b709e..0000000 --- a/handler/withtransaction.go +++ /dev/null @@ -1,37 +0,0 @@ -package handler - -import ( - "context" - "net/http" - "time" - - "projectreshoot/db" - - "github.com/rs/zerolog" -) - -func removeme( - w http.ResponseWriter, - r *http.Request, - logger *zerolog.Logger, - conn *db.SafeConn, - handler func( - ctx context.Context, - tx *db.SafeTX, - w http.ResponseWriter, - r *http.Request, - ), - onfail func(err error), -) { - ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) - defer cancel() - - // Start the transaction - tx, err := conn.Begin(ctx) - if err != nil { - onfail(err) - return - } - - handler(ctx, tx, w, r) -} From 141b541e982ac0e8c46887321196457eb53da770 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sat, 1 Mar 2025 21:10:26 +1100 Subject: [PATCH 16/17] Added movie search --- handler/movie_search.go | 41 +++++++++++++++ server/routes.go | 4 ++ tmdb/movie_functions.go | 11 +++- tmdb/search.go | 61 ++++++++++++++++------ view/component/search/movies_results.templ | 44 ++++++++++++++++ view/page/movie_search.templ | 31 +++++++++++ 6 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 handler/movie_search.go create mode 100644 view/component/search/movies_results.templ create mode 100644 view/page/movie_search.templ diff --git a/handler/movie_search.go b/handler/movie_search.go new file mode 100644 index 0000000..bb19b11 --- /dev/null +++ b/handler/movie_search.go @@ -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) + }, + ) +} diff --git a/server/routes.go b/server/routes.go index 66b3a05..e99fd46 100644 --- a/server/routes.go +++ b/server/routes.go @@ -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)) } diff --git a/tmdb/movie_functions.go b/tmdb/movie_functions.go index a73f3bf..ebfa679 100644 --- a/tmdb/movie_functions.go +++ b/tmdb/movie_functions.go @@ -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 } diff --git a/tmdb/search.go b/tmdb/search.go index 9ce77bd..701ff04 100644 --- a/tmdb/search.go +++ b/tmdb/search.go @@ -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" + diff --git a/view/component/search/movies_results.templ b/view/component/search/movies_results.templ new file mode 100644 index 0000000..396a6fa --- /dev/null +++ b/view/component/search/movies_results.templ @@ -0,0 +1,44 @@ +package search + +import "projectreshoot/tmdb" +import "fmt" + +templ MovieResults(movies *tmdb.ResultMovies, image *tmdb.Image) { + for _, movie := range movies.Results { +
+ Movie Poster + +
+ { movie.Title } { movie.ReleaseYear() } +

+ Released: + { movie.ReleaseDate } +

+

+ Original Title: + { movie.OriginalTitle } +

+

{ movie.Overview }

+
+
+ } +} diff --git a/view/page/movie_search.templ b/view/page/movie_search.templ new file mode 100644 index 0000000..534a146 --- /dev/null +++ b/view/page/movie_search.templ @@ -0,0 +1,31 @@ +package page + +import "projectreshoot/view/layout" + +templ Movies() { + @layout.Global() { +
+
+
+ + +
+
+
+
+ } +} From 9e12f946b383a096e5660d01743ec446a12b0f1b Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Sat, 1 Mar 2025 21:27:23 +1100 Subject: [PATCH 17/17] Changed layout to have dynamic page title --- view/layout/global.templ | 4 ++-- view/page/about.templ | 2 +- view/page/account.templ | 2 +- view/page/error.templ | 2 +- view/page/index.templ | 2 +- view/page/login.templ | 2 +- view/page/movie.templ | 2 +- view/page/movie_search.templ | 2 +- view/page/profile.templ | 2 +- view/page/register.templ | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/view/layout/global.templ b/view/layout/global.templ index 39baf93..95536f2 100644 --- a/view/layout/global.templ +++ b/view/layout/global.templ @@ -6,7 +6,7 @@ import "projectreshoot/view/component/popup" // Global page layout. Includes HTML document settings, header tags // navbar and footer -templ Global() { +templ Global(title string) { - Project Reshoot + { title } diff --git a/view/page/about.templ b/view/page/about.templ index 6f121b2..66b28e8 100644 --- a/view/page/about.templ +++ b/view/page/about.templ @@ -4,7 +4,7 @@ import "projectreshoot/view/layout" // Returns the about page content templ About() { - @layout.Global() { + @layout.Global("About") {
About
What is Project Reshoot?
diff --git a/view/page/account.templ b/view/page/account.templ index c11b900..f4aa29a 100644 --- a/view/page/account.templ +++ b/view/page/account.templ @@ -4,7 +4,7 @@ import "projectreshoot/view/layout" import "projectreshoot/view/component/account" templ Account(subpage string) { - @layout.Global() { + @layout.Global("Account - " + subpage) { @account.AccountContainer(subpage) } } diff --git a/view/page/error.templ b/view/page/error.templ index 2ec5095..5da21ec 100644 --- a/view/page/error.templ +++ b/view/page/error.templ @@ -7,7 +7,7 @@ import "strconv" // a string, and err should be the corresponding response title. // Message is a custom error message displayed below the code and error. templ Error(code int, err string, message string) { - @layout.Global() { + @layout.Global(err) {
Project Reshoot
A better way to discover and rate films
diff --git a/view/page/login.templ b/view/page/login.templ index ee0905a..1335601 100644 --- a/view/page/login.templ +++ b/view/page/login.templ @@ -5,7 +5,7 @@ import "projectreshoot/view/component/form" // Returns the login page templ Login() { - @layout.Global() { + @layout.Global("Login") {
diff --git a/view/page/movie.templ b/view/page/movie.templ index 48c5e29..ef7700b 100644 --- a/view/page/movie.templ +++ b/view/page/movie.templ @@ -4,7 +4,7 @@ import "projectreshoot/tmdb" import "projectreshoot/view/layout" templ Movie(movie *tmdb.Movie, credits *tmdb.Credits, image *tmdb.Image) { - @layout.Global() { + @layout.Global(movie.Title) {
Hello, { user.Username }
diff --git a/view/page/register.templ b/view/page/register.templ index 75ded68..a8e3bb1 100644 --- a/view/page/register.templ +++ b/view/page/register.templ @@ -5,7 +5,7 @@ import "projectreshoot/view/component/form" // Returns the login page templ Register() { - @layout.Global() { + @layout.Global("Register") {