- Implemented Create, Read, Update (PUT/PATCH), and Delete (CRUD) operations for films. - Added SQL queries for film management (e.g., NextFilmID, InsertFilm, UpdateFilmPut, PatchFilm, and DeleteFilm). - Updated HTTP handlers to support enhanced film operations, including validation and null handling. - Refactored category handling logic for cleaner syntax. - Included HTTP client test samples for film API in `films_crud_test.http`.
567 lines
17 KiB
Go
567 lines
17 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"`
|
|
OriginalLanguageID *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 == nil {
|
|
c.JSON(400, gin.H{"error": "title and language_id are required"})
|
|
return
|
|
}
|
|
q := sq.New(db)
|
|
var id int64
|
|
if in.FilmID != nil {
|
|
id = *in.FilmID
|
|
} else {
|
|
var err error
|
|
id, err = q.NextFilmID(c)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
toNullString := func(p *string) sql.NullString {
|
|
if p == nil {
|
|
return sql.NullString{Valid: false}
|
|
}
|
|
return sql.NullString{String: *p, Valid: true}
|
|
}
|
|
toNullInt := func(p *int64) sql.NullInt64 {
|
|
if p == nil {
|
|
return sql.NullInt64{Valid: false}
|
|
}
|
|
return sql.NullInt64{Int64: *p, Valid: true}
|
|
}
|
|
var desc interface{}
|
|
if in.Description != nil {
|
|
desc = *in.Description
|
|
} else {
|
|
desc = nil
|
|
}
|
|
rentalDuration := int64(3)
|
|
if in.RentalDuration != nil {
|
|
rentalDuration = *in.RentalDuration
|
|
}
|
|
rentalRate := 4.99
|
|
if in.RentalRate != nil {
|
|
rentalRate = *in.RentalRate
|
|
}
|
|
replacementCost := 19.99
|
|
if in.ReplacementCost != nil {
|
|
replacementCost = *in.ReplacementCost
|
|
}
|
|
length := toNullInt(in.Length)
|
|
err := q.InsertFilm(c, sq.InsertFilmParams{
|
|
FilmID: id,
|
|
Title: in.Title,
|
|
Description: desc,
|
|
ReleaseYear: toNullString(in.ReleaseYear),
|
|
LanguageID: *in.LanguageID,
|
|
OriginalLanguageID: toNullInt(in.OriginalLanguageID),
|
|
RentalDuration: rentalDuration,
|
|
RentalRate: rentalRate,
|
|
Length: length,
|
|
ReplacementCost: replacementCost,
|
|
Rating: toNullString(in.Rating),
|
|
SpecialFeatures: toNullString(in.SpecialFeatures),
|
|
})
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
created, err := q.GetFilm(c, id)
|
|
if err != nil {
|
|
c.JSON(500, 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"})
|
|
res.AddLink("sakila:language", halgo.Link{Href: "/languages/" + strconv.FormatInt(created.LanguageID, 10)})
|
|
if created.OriginalLanguageID.Valid {
|
|
res.AddLink("sakila:original-language", halgo.Link{Href: "/languages/" + strconv.FormatInt(created.OriginalLanguageID.Int64, 10)})
|
|
}
|
|
c.Header("Content-Type", "application/hal+json")
|
|
c.Header("Location", self)
|
|
c.JSON(201, res)
|
|
})
|
|
|
|
// Replace film (PUT)
|
|
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"`
|
|
OriginalLanguageID *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) == "" {
|
|
c.JSON(400, gin.H{"error": "title is required"})
|
|
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}
|
|
}
|
|
toNullInt := func(p *int64) sql.NullInt64 {
|
|
if p == nil {
|
|
return sql.NullInt64{Valid: false}
|
|
}
|
|
return sql.NullInt64{Int64: *p, Valid: true}
|
|
}
|
|
var desc interface{}
|
|
if in.Description != nil {
|
|
desc = *in.Description
|
|
} else {
|
|
desc = nil
|
|
}
|
|
aff, err := q.UpdateFilmPut(c, sq.UpdateFilmPutParams{
|
|
Title: in.Title,
|
|
Description: desc,
|
|
ReleaseYear: toNullString(in.ReleaseYear),
|
|
LanguageID: in.LanguageID,
|
|
OriginalLanguageID: toNullInt(in.OriginalLanguageID),
|
|
RentalDuration: in.RentalDuration,
|
|
RentalRate: in.RentalRate,
|
|
Length: toNullInt(in.Length),
|
|
ReplacementCost: in.ReplacementCost,
|
|
Rating: toNullString(in.Rating),
|
|
SpecialFeatures: toNullString(in.SpecialFeatures),
|
|
FilmID: 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.GetFilm(c, id)
|
|
if err != nil {
|
|
c.JSON(500, 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"})
|
|
res.AddLink("sakila:language", halgo.Link{Href: "/languages/" + strconv.FormatInt(updated.LanguageID, 10)})
|
|
if updated.OriginalLanguageID.Valid {
|
|
res.AddLink("sakila:original-language", halgo.Link{Href: "/languages/" + strconv.FormatInt(updated.OriginalLanguageID.Int64, 10)})
|
|
}
|
|
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"`
|
|
OriginalLanguageID *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
|
|
}
|
|
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}
|
|
}
|
|
toNullInt := func(p *int64) sql.NullInt64 {
|
|
if p == nil {
|
|
return sql.NullInt64{Valid: false}
|
|
}
|
|
return sql.NullInt64{Int64: *p, Valid: true}
|
|
}
|
|
toNullFloat := func(p *float64) sql.NullFloat64 {
|
|
if p == nil {
|
|
return sql.NullFloat64{Valid: false}
|
|
}
|
|
return sql.NullFloat64{Float64: *p, Valid: true}
|
|
}
|
|
var desc interface{}
|
|
if in.Description != nil {
|
|
desc = *in.Description
|
|
} else {
|
|
desc = nil
|
|
}
|
|
aff, err := q.PatchFilm(c, sq.PatchFilmParams{
|
|
Title: toNullString(in.Title),
|
|
Description: desc,
|
|
ReleaseYear: toNullString(in.ReleaseYear),
|
|
LanguageID: toNullInt(in.LanguageID),
|
|
OriginalLanguageID: toNullInt(in.OriginalLanguageID),
|
|
RentalDuration: toNullInt(in.RentalDuration),
|
|
RentalRate: toNullFloat(in.RentalRate),
|
|
Length: toNullInt(in.Length),
|
|
ReplacementCost: toNullFloat(in.ReplacementCost),
|
|
Rating: toNullString(in.Rating),
|
|
SpecialFeatures: toNullString(in.SpecialFeatures),
|
|
FilmID: 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.GetFilm(c, id)
|
|
if err != nil {
|
|
c.JSON(500, 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"})
|
|
res.AddLink("sakila:language", halgo.Link{Href: "/languages/" + strconv.FormatInt(updated.LanguageID, 10)})
|
|
if updated.OriginalLanguageID.Valid {
|
|
res.AddLink("sakila:original-language", halgo.Link{Href: "/languages/" + strconv.FormatInt(updated.OriginalLanguageID.Int64, 10)})
|
|
}
|
|
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
|
|
}
|
|
q := sq.New(db)
|
|
aff, err := q.DeleteFilm(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)
|
|
})
|
|
|
|
}
|