sakila/internal/httpapi/actors.go

288 lines
7.9 KiB
Go
Raw Normal View History

package httpapi
import (
"database/sql"
"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 RegisterActorRoutes(r *gin.Engine, db *sql.DB) {
// List actors
r.GET("/actors", 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"))
q := sq.New(db)
actors, err := q.ListActors(c, sq.ListActorsParams{Limit: int64(limit), Offset: int64(offset)})
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
total, err := q.CountActors(c)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
selfURL := buildURL("/actors", limit, offset, order, c.Query("expand"))
res := hres.New(selfURL)
res.AddCurie()
res.AddLink("self", halgo.Link{Href: selfURL})
if int64(offset+limit) < total {
nextURL := buildURL("/actors", limit, offset+limit, order, c.Query("expand"))
res.AddLink("next", halgo.Link{Href: nextURL})
}
if offset > 0 {
prev := offset - limit
if prev < 0 {
prev = 0
}
prevURL := buildURL("/actors", limit, prev, order, c.Query("expand"))
res.AddLink("prev", halgo.Link{Href: prevURL})
}
var embedded []interface{}
for _, a := range actors {
itemURL := "/actors/" + strconv.FormatInt(int64(a.ActorID), 10)
relFilmsURL := itemURL + "/films"
item := hres.New(itemURL)
item.SetState(a)
item.AddLink("self", halgo.Link{Href: itemURL})
item.AddLink("sakila:films", halgo.Link{Href: relFilmsURL})
if expand["films"] {
films, err := q.ListFilmsByActor(c, int64(a.ActorID))
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
var filmEmbeds []interface{}
for _, f := range films {
filmURL := "/films/" + strconv.FormatInt(f.FilmID, 10)
film := hres.New(filmURL)
film.SetState(f)
film.AddLink("self", halgo.Link{Href: filmURL})
film.AddLink("sakila:actors", halgo.Link{Href: filmURL + "/actors"})
filmEmbeds = append(filmEmbeds, film)
}
item.Embed("films", filmEmbeds)
}
embedded = append(embedded, item)
}
res.Embed("actors", embedded)
res.SetState(gin.H{"count": len(actors), "total": total, "limit": limit, "offset": offset})
c.Header("Content-Type", "application/hal+json")
c.JSON(200, res)
})
// Get actor
r.GET("/actors/: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)
aRec, err := q.GetActor(c, id)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
// sqlc returns error for not found
self := "/actors/" + strconv.FormatInt(int64(aRec.ActorID), 10)
relFilms := self + "/films"
res := hres.New(self)
res.AddCurie()
res.SetState(aRec)
res.AddLink("self", halgo.Link{Href: self})
res.AddLink("sakila:films", halgo.Link{Href: relFilms})
if expand["films"] {
films, err := q.ListFilmsByActor(c, int64(aRec.ActorID))
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
var filmEmbeds []interface{}
for _, f := range films {
filmURL := "/films/" + strconv.FormatInt(f.FilmID, 10)
film := hres.New(filmURL)
film.SetState(f)
film.AddLink("self", halgo.Link{Href: filmURL})
film.AddLink("sakila:actors", halgo.Link{Href: filmURL + "/actors"})
filmEmbeds = append(filmEmbeds, film)
}
res.Embed("films", filmEmbeds)
}
c.Header("Content-Type", "application/hal+json")
c.JSON(200, res)
})
// Create actor
r.POST("/actors", func(c *gin.Context) {
var in struct {
ActorID *int64 `json:"actor_id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
if err := c.ShouldBindJSON(&in); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if strings.TrimSpace(in.FirstName) == "" || strings.TrimSpace(in.LastName) == "" {
c.JSON(400, gin.H{"error": "first_name and last_name are required"})
return
}
q := sq.New(db)
var id int64
if in.ActorID != nil {
id = *in.ActorID
} else {
var err error
id, err = q.NextActorID(c)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
}
if err := q.InsertActor(c, sq.InsertActorParams{ActorID: id, FirstName: in.FirstName, LastName: in.LastName}); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
created, err := q.GetActor(c, id)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
self := "/actors/" + strconv.FormatInt(int64(created.ActorID), 10)
res := hres.New(self)
res.AddCurie()
res.SetState(created)
res.AddLink("self", halgo.Link{Href: self})
res.AddLink("sakila:films", halgo.Link{Href: self + "/films"})
c.Header("Content-Type", "application/hal+json")
c.Header("Location", self)
c.JSON(201, res)
})
// Replace actor
r.PUT("/actors/: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 {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
if err := c.ShouldBindJSON(&in); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if strings.TrimSpace(in.FirstName) == "" || strings.TrimSpace(in.LastName) == "" {
c.JSON(400, gin.H{"error": "first_name and last_name are required"})
return
}
q := sq.New(db)
aff, err := q.UpdateActorPut(c, sq.UpdateActorPutParams{FirstName: in.FirstName, LastName: in.LastName, ActorID: id})
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if aff == 0 {
c.JSON(404, gin.H{"error": "not found"})
return
}
updated, err := q.GetActor(c, id)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
self := "/actors/" + strconv.FormatInt(int64(updated.ActorID), 10)
res := hres.New(self)
res.AddCurie()
res.SetState(updated)
res.AddLink("self", halgo.Link{Href: self})
res.AddLink("sakila:films", halgo.Link{Href: self + "/films"})
c.Header("Content-Type", "application/hal+json")
c.JSON(200, res)
})
// Patch actor
r.PATCH("/actors/: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 {
FirstName *string `json:"first_name"`
LastName *string `json:"last_name"`
}
if err := c.ShouldBindJSON(&in); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
q := sq.New(db)
toNullString := func(p *string) sql.NullString {
if p == nil {
return sql.NullString{Valid: false}
}
return sql.NullString{String: *p, Valid: true}
}
aff, err := q.PatchActor(c, sq.PatchActorParams{FirstName: toNullString(in.FirstName), LastName: toNullString(in.LastName), ActorID: id})
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if aff == 0 {
c.JSON(404, gin.H{"error": "not found"})
return
}
updated, err := q.GetActor(c, id)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
self := "/actors/" + strconv.FormatInt(int64(updated.ActorID), 10)
res := hres.New(self)
res.AddCurie()
res.SetState(updated)
res.AddLink("self", halgo.Link{Href: self})
res.AddLink("sakila:films", halgo.Link{Href: self + "/films"})
c.Header("Content-Type", "application/hal+json")
c.JSON(200, res)
})
// Delete actor
r.DELETE("/actors/: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
}
q := sq.New(db)
aff, err := q.DeleteActor(c, id)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if aff == 0 {
c.JSON(404, gin.H{"error": "not found"})
return
}
c.Status(204)
})
}