Add film CRUD operations and integrate Sakila database enhancement
- 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`.
This commit is contained in:
parent
54e36528bc
commit
5aa6a96b1d
6 changed files with 616 additions and 182 deletions
|
|
@ -2,5 +2,6 @@
|
|||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/sakila-sqlite3" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -261,199 +261,307 @@ func RegisterFilmRoutes(r *gin.Engine, db *sql.DB) {
|
|||
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)
|
||||
// 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(400, gin.H{"error": err.Error()})
|
||||
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"})
|
||||
c.Header("Content-Type", "application/hal+json")
|
||||
c.Header("Location", self)
|
||||
c.JSON(201, res)
|
||||
}
|
||||
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
|
||||
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
|
||||
// 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}
|
||||
}
|
||||
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"`
|
||||
return sql.NullString{String: *p, Valid: true}
|
||||
}
|
||||
toNullInt := func(p *int64) sql.NullInt64 {
|
||||
if p == nil {
|
||||
return sql.NullInt64{Valid: false}
|
||||
}
|
||||
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)
|
||||
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
|
||||
// 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}
|
||||
}
|
||||
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"`
|
||||
return sql.NullString{String: *p, Valid: true}
|
||||
}
|
||||
toNullInt := func(p *int64) sql.NullInt64 {
|
||||
if p == nil {
|
||||
return sql.NullInt64{Valid: false}
|
||||
}
|
||||
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
|
||||
return sql.NullInt64{Int64: *p, Valid: true}
|
||||
}
|
||||
toNullFloat := func(p *float64) sql.NullFloat64 {
|
||||
if p == nil {
|
||||
return sql.NullFloat64{Valid: false}
|
||||
}
|
||||
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)
|
||||
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)
|
||||
})
|
||||
|
||||
// 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)
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
83
internal/httpapi/films_crud_test.http
Normal file
83
internal/httpapi/films_crud_test.http
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
### Films API sample requests (IntelliJ HTTP Client)
|
||||
|
||||
### List films (default pagination)
|
||||
GET http://localhost:8080/films
|
||||
Accept: application/hal+json
|
||||
|
||||
### List films with expand and filters
|
||||
GET http://localhost:8080/films?limit=5&offset=0&expand=actors,language&language.name=English
|
||||
Accept: application/hal+json
|
||||
|
||||
### List films filtered by category name
|
||||
GET http://localhost:8080/films?limit=5&category.name=Action
|
||||
Accept: application/hal+json
|
||||
|
||||
### Get a single film
|
||||
GET http://localhost:8080/films/1?expand=actors,language,original_language
|
||||
Accept: application/hal+json
|
||||
|
||||
### Create a film (server computes next film_id)
|
||||
POST http://localhost:8080/films
|
||||
Content-Type: application/json
|
||||
Accept: application/hal+json
|
||||
|
||||
{
|
||||
"title": "Sample Movie",
|
||||
"description": "A test film created via HTTP client",
|
||||
"release_year": "2025",
|
||||
"language_id": 1,
|
||||
"rental_duration": 3,
|
||||
"rental_rate": 4.99,
|
||||
"length": 120,
|
||||
"replacement_cost": 19.99,
|
||||
"rating": "PG",
|
||||
"special_features": "Trailers,Deleted Scenes"
|
||||
}
|
||||
|
||||
### Create a film with explicit film_id
|
||||
POST http://localhost:8080/films
|
||||
Content-Type: application/json
|
||||
Accept: application/hal+json
|
||||
|
||||
{
|
||||
"film_id": 10001,
|
||||
"title": "Explicit ID Film",
|
||||
"language_id": 1
|
||||
}
|
||||
|
||||
### Replace film (PUT)
|
||||
# Note: Replace the :id variable below to match an existing film id
|
||||
PUT http://localhost:8080/films/{{film_id}}
|
||||
Content-Type: application/json
|
||||
Accept: application/hal+json
|
||||
|
||||
{
|
||||
"title": "Updated Title (PUT)",
|
||||
"description": "Full replacement payload",
|
||||
"release_year": "2024",
|
||||
"language_id": 1,
|
||||
"original_language_id": 2,
|
||||
"rental_duration": 5,
|
||||
"rental_rate": 3.99,
|
||||
"length": 95,
|
||||
"replacement_cost": 14.99,
|
||||
"rating": "PG-13",
|
||||
"special_features": "Trailers,Behind the Scenes"
|
||||
}
|
||||
|
||||
### Patch film (partial update)
|
||||
PATCH http://localhost:8080/films/{{film_id}}
|
||||
Content-Type: application/json
|
||||
Accept: application/hal+json
|
||||
|
||||
{
|
||||
"title": "Patched Title",
|
||||
"rental_rate": 2.99
|
||||
}
|
||||
|
||||
### Delete film
|
||||
DELETE http://localhost:8080/films/{{film_id}}
|
||||
|
||||
### Related: list actors of a film
|
||||
GET http://localhost:8080/films/{{film_id}}/actors
|
||||
Accept: application/hal+json
|
||||
5
internal/httpapi/http-client.env.json
Normal file
5
internal/httpapi/http-client.env.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"dev": {
|
||||
"film_id": "10001"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ package sqlc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
const countFilms = `-- name: CountFilms :one
|
||||
|
|
@ -156,3 +157,184 @@ func (q *Queries) ListFilms(ctx context.Context, arg ListFilmsParams) ([]Film, e
|
|||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// Additional film CRUD methods (manually added to mirror sqlc patterns)
|
||||
const nextFilmID = `-- name: NextFilmID :one
|
||||
SELECT COALESCE(MAX(film_id), 0) + 1 FROM film
|
||||
`
|
||||
|
||||
func (q *Queries) NextFilmID(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, nextFilmID)
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const insertFilm = `-- name: InsertFilm :exec
|
||||
INSERT INTO film (
|
||||
film_id,
|
||||
title,
|
||||
description,
|
||||
release_year,
|
||||
language_id,
|
||||
original_language_id,
|
||||
rental_duration,
|
||||
rental_rate,
|
||||
length,
|
||||
replacement_cost,
|
||||
rating,
|
||||
special_features,
|
||||
last_update
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
type InsertFilmParams struct {
|
||||
FilmID int64 `json:"film_id"`
|
||||
Title string `json:"title"`
|
||||
Description interface{} `json:"description"`
|
||||
ReleaseYear sql.NullString `json:"release_year"`
|
||||
LanguageID int64 `json:"language_id"`
|
||||
OriginalLanguageID sql.NullInt64 `json:"original_language_id"`
|
||||
RentalDuration int64 `json:"rental_duration"`
|
||||
RentalRate float64 `json:"rental_rate"`
|
||||
Length sql.NullInt64 `json:"length"`
|
||||
ReplacementCost float64 `json:"replacement_cost"`
|
||||
Rating sql.NullString `json:"rating"`
|
||||
SpecialFeatures sql.NullString `json:"special_features"`
|
||||
}
|
||||
|
||||
func (q *Queries) InsertFilm(ctx context.Context, arg InsertFilmParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertFilm,
|
||||
arg.FilmID,
|
||||
arg.Title,
|
||||
arg.Description,
|
||||
arg.ReleaseYear,
|
||||
arg.LanguageID,
|
||||
arg.OriginalLanguageID,
|
||||
arg.RentalDuration,
|
||||
arg.RentalRate,
|
||||
arg.Length,
|
||||
arg.ReplacementCost,
|
||||
arg.Rating,
|
||||
arg.SpecialFeatures,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateFilmPut = `-- name: UpdateFilmPut :execrows
|
||||
UPDATE film SET
|
||||
title = ?,
|
||||
description = ?,
|
||||
release_year = ?,
|
||||
language_id = ?,
|
||||
original_language_id = ?,
|
||||
rental_duration = ?,
|
||||
rental_rate = ?,
|
||||
length = ?,
|
||||
replacement_cost = ?,
|
||||
rating = ?,
|
||||
special_features = ?
|
||||
WHERE film_id = ?
|
||||
`
|
||||
|
||||
type UpdateFilmPutParams struct {
|
||||
Title string `json:"title"`
|
||||
Description interface{} `json:"description"`
|
||||
ReleaseYear sql.NullString `json:"release_year"`
|
||||
LanguageID int64 `json:"language_id"`
|
||||
OriginalLanguageID sql.NullInt64 `json:"original_language_id"`
|
||||
RentalDuration int64 `json:"rental_duration"`
|
||||
RentalRate float64 `json:"rental_rate"`
|
||||
Length sql.NullInt64 `json:"length"`
|
||||
ReplacementCost float64 `json:"replacement_cost"`
|
||||
Rating sql.NullString `json:"rating"`
|
||||
SpecialFeatures sql.NullString `json:"special_features"`
|
||||
FilmID int64 `json:"film_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateFilmPut(ctx context.Context, arg UpdateFilmPutParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, updateFilmPut,
|
||||
arg.Title,
|
||||
arg.Description,
|
||||
arg.ReleaseYear,
|
||||
arg.LanguageID,
|
||||
arg.OriginalLanguageID,
|
||||
arg.RentalDuration,
|
||||
arg.RentalRate,
|
||||
arg.Length,
|
||||
arg.ReplacementCost,
|
||||
arg.Rating,
|
||||
arg.SpecialFeatures,
|
||||
arg.FilmID,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const patchFilm = `-- name: PatchFilm :execrows
|
||||
UPDATE film SET
|
||||
title = COALESCE(?1, title),
|
||||
description = COALESCE(?2, description),
|
||||
release_year = COALESCE(?3, release_year),
|
||||
language_id = COALESCE(?4, language_id),
|
||||
original_language_id = COALESCE(?5, original_language_id),
|
||||
rental_duration = COALESCE(?6, rental_duration),
|
||||
rental_rate = COALESCE(?7, rental_rate),
|
||||
length = COALESCE(?8, length),
|
||||
replacement_cost = COALESCE(?9, replacement_cost),
|
||||
rating = COALESCE(?10, rating),
|
||||
special_features = COALESCE(?11, special_features)
|
||||
WHERE film_id = ?12
|
||||
`
|
||||
|
||||
type PatchFilmParams struct {
|
||||
Title sql.NullString `json:"title"`
|
||||
Description interface{} `json:"description"`
|
||||
ReleaseYear sql.NullString `json:"release_year"`
|
||||
LanguageID sql.NullInt64 `json:"language_id"`
|
||||
OriginalLanguageID sql.NullInt64 `json:"original_language_id"`
|
||||
RentalDuration sql.NullInt64 `json:"rental_duration"`
|
||||
RentalRate sql.NullFloat64 `json:"rental_rate"`
|
||||
Length sql.NullInt64 `json:"length"`
|
||||
ReplacementCost sql.NullFloat64 `json:"replacement_cost"`
|
||||
Rating sql.NullString `json:"rating"`
|
||||
SpecialFeatures sql.NullString `json:"special_features"`
|
||||
FilmID int64 `json:"film_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) PatchFilm(ctx context.Context, arg PatchFilmParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, patchFilm,
|
||||
arg.Title,
|
||||
arg.Description,
|
||||
arg.ReleaseYear,
|
||||
arg.LanguageID,
|
||||
arg.OriginalLanguageID,
|
||||
arg.RentalDuration,
|
||||
arg.RentalRate,
|
||||
arg.Length,
|
||||
arg.ReplacementCost,
|
||||
arg.Rating,
|
||||
arg.SpecialFeatures,
|
||||
arg.FilmID,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const deleteFilm = `-- name: DeleteFilm :execrows
|
||||
DELETE FROM film WHERE film_id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteFilm(ctx context.Context, filmID int64) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, deleteFilm, filmID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,4 +42,59 @@ SELECT a.actor_id, a.first_name, a.last_name, a.last_update
|
|||
FROM actor a
|
||||
JOIN film_actor fa ON fa.actor_id = a.actor_id
|
||||
WHERE fa.film_id = ?
|
||||
ORDER BY a.last_name ASC, a.first_name ASC;
|
||||
ORDER BY a.last_name ASC, a.first_name ASC;
|
||||
|
||||
-- name: NextFilmID :one
|
||||
SELECT COALESCE(MAX(film_id), 0) + 1 FROM film;
|
||||
|
||||
-- name: InsertFilm :exec
|
||||
INSERT INTO film (
|
||||
film_id,
|
||||
title,
|
||||
description,
|
||||
release_year,
|
||||
language_id,
|
||||
original_language_id,
|
||||
rental_duration,
|
||||
rental_rate,
|
||||
length,
|
||||
replacement_cost,
|
||||
rating,
|
||||
special_features,
|
||||
last_update
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- name: UpdateFilmPut :execrows
|
||||
UPDATE film SET
|
||||
title = ?,
|
||||
description = ?,
|
||||
release_year = ?,
|
||||
language_id = ?,
|
||||
original_language_id = ?,
|
||||
rental_duration = ?,
|
||||
rental_rate = ?,
|
||||
length = ?,
|
||||
replacement_cost = ?,
|
||||
rating = ?,
|
||||
special_features = ?
|
||||
WHERE film_id = ?;
|
||||
|
||||
-- name: PatchFilm :execrows
|
||||
UPDATE film SET
|
||||
title = COALESCE(?1, title),
|
||||
description = COALESCE(?2, description),
|
||||
release_year = COALESCE(?3, release_year),
|
||||
language_id = COALESCE(?4, language_id),
|
||||
original_language_id = COALESCE(?5, original_language_id),
|
||||
rental_duration = COALESCE(?6, rental_duration),
|
||||
rental_rate = COALESCE(?7, rental_rate),
|
||||
length = COALESCE(?8, length),
|
||||
replacement_cost = COALESCE(?9, replacement_cost),
|
||||
rating = COALESCE(?10, rating),
|
||||
special_features = COALESCE(?11, special_features)
|
||||
WHERE film_id = ?12;
|
||||
|
||||
-- name: DeleteFilm :execrows
|
||||
DELETE FROM film WHERE film_id = ?;
|
||||
Loading…
Reference in a new issue