package httpapi import ( "database/sql" "net/url" "strconv" "strings" hres "form-builder-be-go/internal/hal" sq "form-builder-be-go/internal/sqlc" "github.com/gin-gonic/gin" "github.com/raff/halgo" ) func RegisterFilmRoutes(r *gin.Engine, db *sql.DB) { // List films r.GET("/films", func(c *gin.Context) { limit := parseIntDefault(c.Query("limit"), 20) if limit > 100 { limit = 100 } offset := parseIntDefault(c.Query("offset"), 0) order := c.Query("order") expand := parseExpand(c.Query("expand")) langName := strings.TrimSpace(c.Query("language.name")) catName := strings.TrimSpace(c.Query("category.name")) q := sq.New(db) var ( films []sq.Film total int64 err error ) if langName != "" { films, err = q.ListFilmsByLanguageName(c, sq.ListFilmsByLanguageNameParams{UPPER: langName, Limit: int64(limit), Offset: int64(offset)}) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } total, err = q.CountFilmsByLanguageName(c, langName) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } } else if catName != "" { films, err = q.ListFilmsByCategoryName(c, sq.ListFilmsByCategoryNameParams{UPPER: catName, Limit: int64(limit), Offset: int64(offset)}) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } total, err = q.CountFilmsByCategoryName(c, catName) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } } else { films, err = q.ListFilms(c, sq.ListFilmsParams{Limit: int64(limit), Offset: int64(offset)}) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } total, err = q.CountFilms(c) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } } selfURL := buildURL("/films", limit, offset, order, c.Query("expand")) if langName != "" { sep := "?" if strings.Contains(selfURL, "?") { sep = "&" } selfURL = selfURL + sep + "language.name=" + url.QueryEscape(langName) } if catName != "" { sep := "?" if strings.Contains(selfURL, "?") { sep = "&" } selfURL = selfURL + sep + "category.name=" + url.QueryEscape(catName) } res := hres.New(selfURL) res.AddCurie() res.AddLink("self", halgo.Link{Href: selfURL}) if int64(offset+limit) < total { nextURL := buildURL("/films", limit, offset+limit, order, c.Query("expand")) if langName != "" { sep := "?" if strings.Contains(nextURL, "?") { sep = "&" } nextURL += sep + "language.name=" + url.QueryEscape(langName) } if catName != "" { sep := "?" if strings.Contains(nextURL, "?") { sep = "&" } nextURL += sep + "category.name=" + url.QueryEscape(catName) } res.AddLink("next", halgo.Link{Href: nextURL}) } if offset > 0 { prev := offset - limit if prev < 0 { prev = 0 } prevURL := buildURL("/films", limit, prev, order, c.Query("expand")) if langName != "" { sep := "?" if strings.Contains(prevURL, "?") { sep = "&" } prevURL += sep + "language.name=" + url.QueryEscape(langName) } if catName != "" { sep := "?" if strings.Contains(prevURL, "?") { sep = "&" } prevURL += sep + "category.name=" + url.QueryEscape(catName) } res.AddLink("prev", halgo.Link{Href: prevURL}) } var embedded []interface{} for _, f := range films { itemURL := "/films/" + strconv.FormatInt(f.FilmID, 10) relActorsURL := itemURL + "/actors" item := hres.New(itemURL) item.SetState(f) item.AddLink("self", halgo.Link{Href: itemURL}) item.AddLink("sakila:actors", halgo.Link{Href: relActorsURL}) // language relations item.AddLink("sakila:language", halgo.Link{Href: "/languages/" + strconv.FormatInt(f.LanguageID, 10)}) if f.OriginalLanguageID.Valid { item.AddLink("sakila:original-language", halgo.Link{Href: "/languages/" + strconv.FormatInt(f.OriginalLanguageID.Int64, 10)}) } // optional embeds if expand["language"] { lang, err := q.GetLanguage(c, f.LanguageID) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } lr := hres.New("/languages/" + strconv.FormatInt(lang.LanguageID, 10)) lr.SetState(lang) lr.AddLink("self", halgo.Link{Href: "/languages/" + strconv.FormatInt(lang.LanguageID, 10)}) item.Embed("language", lr) } if expand["original_language"] && f.OriginalLanguageID.Valid { olang, err := q.GetLanguage(c, f.OriginalLanguageID.Int64) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } olr := hres.New("/languages/" + strconv.FormatInt(olang.LanguageID, 10)) olr.SetState(olang) olr.AddLink("self", halgo.Link{Href: "/languages/" + strconv.FormatInt(olang.LanguageID, 10)}) item.Embed("original_language", olr) } if expand["actors"] { actors, err := q.ListActorsByFilm(c, f.FilmID) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } var actorEmbeds []interface{} for _, a := range actors { actorURL := "/actors/" + strconv.FormatInt(int64(a.ActorID), 10) ar := hres.New(actorURL) ar.SetState(a) ar.AddLink("self", halgo.Link{Href: actorURL}) ar.AddLink("films", halgo.Link{Href: actorURL + "/films"}) actorEmbeds = append(actorEmbeds, ar) } item.Embed("actors", actorEmbeds) } embedded = append(embedded, item) } res.Embed("films", embedded) res.SetState(gin.H{"count": len(films), "total": total, "limit": limit, "offset": offset}) c.Header("Content-Type", "application/hal+json") c.JSON(200, res) }) // Get film r.GET("/films/:id", func(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(400, gin.H{"error": "invalid id"}) return } expand := parseExpand(c.Query("expand")) q := sq.New(db) f, err := q.GetFilm(c, id) if err != nil { if err == sql.ErrNoRows { c.JSON(404, gin.H{"error": "not found"}) return } c.JSON(500, gin.H{"error": err.Error()}) return } self := "/films/" + strconv.FormatInt(f.FilmID, 10) relActors := self + "/actors" res := hres.New(self) res.AddCurie() res.SetState(f) res.AddLink("self", halgo.Link{Href: self}) res.AddLink("sakila:actors", halgo.Link{Href: relActors}) // language relations res.AddLink("sakila:language", halgo.Link{Href: "/languages/" + strconv.FormatInt(f.LanguageID, 10)}) if f.OriginalLanguageID.Valid { res.AddLink("sakila:original-language", halgo.Link{Href: "/languages/" + strconv.FormatInt(f.OriginalLanguageID.Int64, 10)}) } // optional embeds if expand["language"] { lang, err := q.GetLanguage(c, f.LanguageID) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } lr := hres.New("/languages/" + strconv.FormatInt(lang.LanguageID, 10)) lr.SetState(lang) lr.AddLink("self", halgo.Link{Href: "/languages/" + strconv.FormatInt(lang.LanguageID, 10)}) res.Embed("language", lr) } if expand["original_language"] && f.OriginalLanguageID.Valid { olang, err := q.GetLanguage(c, f.OriginalLanguageID.Int64) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } olr := hres.New("/languages/" + strconv.FormatInt(olang.LanguageID, 10)) olr.SetState(olang) olr.AddLink("self", halgo.Link{Href: "/languages/" + strconv.FormatInt(olang.LanguageID, 10)}) res.Embed("original_language", olr) } if expand["actors"] { actors, err := q.ListActorsByFilm(c, f.FilmID) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } var actorEmbeds []interface{} for _, a := range actors { actorURL := "/actors/" + strconv.FormatInt(int64(a.ActorID), 10) ar := hres.New(actorURL) ar.SetState(a) ar.AddLink("self", halgo.Link{Href: actorURL}) ar.AddLink("films", halgo.Link{Href: actorURL + "/films"}) actorEmbeds = append(actorEmbeds, ar) } res.Embed("actors", actorEmbeds) } c.Header("Content-Type", "application/hal+json") c.JSON(200, res) }) /* // Create film r.POST("/films", func(c *gin.Context) { var in struct { FilmID *int64 `json:"film_id"` Title string `json:"title"` Description *string `json:"description"` ReleaseYear *string `json:"release_year"` LanguageID int64 `json:"language_id"` OriginalLangID *int64 `json:"original_language_id"` RentalDuration *int64 `json:"rental_duration"` RentalRate *float64 `json:"rental_rate"` Length *int64 `json:"length"` ReplacementCost *float64 `json:"replacement_cost"` Rating *string `json:"rating"` SpecialFeatures *string `json:"special_features"` } if err := c.ShouldBindJSON(&in); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } if strings.TrimSpace(in.Title) == "" || in.LanguageID == 0 { c.JSON(400, gin.H{"error": "title and language_id are required"}) return } f := sq.Film{Title: in.Title, Description: in.Description, ReleaseYear: in.ReleaseYear, LanguageID: in.LanguageID, OriginalLangID: in.OriginalLangID} if in.RentalDuration != nil { f.RentalDuration = *in.RentalDuration } else { f.RentalDuration = 3 } if in.RentalRate != nil { f.RentalRate = *in.RentalRate } else { f.RentalRate = 4.99 } if in.Length != nil { f.Length = in.Length } if in.ReplacementCost != nil { f.ReplacementCost = *in.ReplacementCost } else { f.ReplacementCost = 19.99 } if in.Rating != nil { f.Rating = in.Rating } if in.SpecialFeatures != nil { f.SpecialFeatures = in.SpecialFeatures } if in.FilmID != nil { f.FilmID = *in.FilmID } created, err := sq.CreateFilm(c, db, &f) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } self := "/films/" + strconv.FormatInt(created.FilmID, 10) res := hres.New(self) res.AddCurie() res.SetState(created) res.AddLink("self", halgo.Link{Href: self}) res.AddLink("sakila:actors", halgo.Link{Href: self + "/actors"}) c.Header("Content-Type", "application/hal+json") c.Header("Location", self) c.JSON(201, res) }) // Replace film r.PUT("/films/:id", func(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(400, gin.H{"error": "invalid id"}) return } var in struct { Title string `json:"title"` Description *string `json:"description"` ReleaseYear *string `json:"release_year"` LanguageID int64 `json:"language_id"` OriginalLangID *int64 `json:"original_language_id"` RentalDuration *int64 `json:"rental_duration"` RentalRate *float64 `json:"rental_rate"` Length *int64 `json:"length"` ReplacementCost *float64 `json:"replacement_cost"` Rating *string `json:"rating"` SpecialFeatures *string `json:"special_features"` } if err := c.ShouldBindJSON(&in); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } f := sq.Film{Title: in.Title, Description: in.Description, ReleaseYear: in.ReleaseYear, LanguageID: in.LanguageID, OriginalLangID: in.OriginalLangID} if in.RentalDuration != nil { f.RentalDuration = *in.RentalDuration } else { f.RentalDuration = 3 } if in.RentalRate != nil { f.RentalRate = *in.RentalRate } else { f.RentalRate = 4.99 } if in.Length != nil { f.Length = in.Length } if in.ReplacementCost != nil { f.ReplacementCost = *in.ReplacementCost } else { f.ReplacementCost = 19.99 } if in.Rating != nil { f.Rating = in.Rating } if in.SpecialFeatures != nil { f.SpecialFeatures = in.SpecialFeatures } updated, err := sq.UpdateFilmPut(c, db, id, f) if err != nil { if err == sql.ErrNoRows { c.JSON(404, gin.H{"error": "not found"}) return } c.JSON(400, gin.H{"error": err.Error()}) return } self := "/films/" + strconv.FormatInt(updated.FilmID, 10) res := hres.New(self) res.AddCurie() res.SetState(updated) res.AddLink("self", halgo.Link{Href: self}) res.AddLink("sakila:actors", halgo.Link{Href: self + "/actors"}) c.Header("Content-Type", "application/hal+json") c.JSON(200, res) }) // Patch film r.PATCH("/films/:id", func(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(400, gin.H{"error": "invalid id"}) return } var in struct { Title *string `json:"title"` Description *string `json:"description"` ReleaseYear *string `json:"release_year"` LanguageID *int64 `json:"language_id"` OriginalLangID *int64 `json:"original_language_id"` RentalDuration *int64 `json:"rental_duration"` RentalRate *float64 `json:"rental_rate"` Length *int64 `json:"length"` ReplacementCost *float64 `json:"replacement_cost"` Rating *string `json:"rating"` SpecialFeatures *string `json:"special_features"` } patch := sq.FilmPatch{Title: in.Title, Description: in.Description, ReleaseYear: in.ReleaseYear, LanguageID: in.LanguageID, OriginalLangID: in.OriginalLangID, RentalDuration: in.RentalDuration, RentalRate: in.RentalRate, Length: in.Length, ReplacementCost: in.ReplacementCost, Rating: in.Rating, SpecialFeatures: in.SpecialFeatures} updated, err := sq.PatchFilm(c, db, id, patch) if err != nil { if err == sql.ErrNoRows { c.JSON(404, gin.H{"error": "not found"}) return } c.JSON(400, gin.H{"error": err.Error()}) return } self := "/films/" + strconv.FormatInt(updated.FilmID, 10) res := hres.New(self) res.AddCurie() res.SetState(updated) res.AddLink("self", halgo.Link{Href: self}) res.AddLink("sakila:actors", halgo.Link{Href: self + "/actors"}) c.Header("Content-Type", "application/hal+json") c.JSON(200, res) }) // Delete film r.DELETE("/films/:id", func(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(400, gin.H{"error": "invalid id"}) return } if err := sq.DeleteFilm(c, db, id); err != nil { if err == sql.ErrNoRows { c.JSON(404, gin.H{"error": "not found"}) return } c.JSON(400, gin.H{"error": err.Error()}) return } c.Status(204) }) */ }