460 lines
14 KiB
Go
460 lines
14 KiB
Go
|
|
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)
|
||
|
|
})
|
||
|
|
*/
|
||
|
|
}
|