diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..27b03c1 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/internal/httpapi/films.go b/internal/httpapi/films.go index eb757e3..8ca6a92 100644 --- a/internal/httpapi/films.go +++ b/internal/httpapi/films.go @@ -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) - }) - */ } diff --git a/internal/httpapi/films_crud_test.http b/internal/httpapi/films_crud_test.http new file mode 100644 index 0000000..5247860 --- /dev/null +++ b/internal/httpapi/films_crud_test.http @@ -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 diff --git a/internal/httpapi/http-client.env.json b/internal/httpapi/http-client.env.json new file mode 100644 index 0000000..3a09a38 --- /dev/null +++ b/internal/httpapi/http-client.env.json @@ -0,0 +1,5 @@ +{ + "dev": { + "film_id": "10001" + } +} \ No newline at end of file diff --git a/internal/sqlc/film.sql.go b/internal/sqlc/film.sql.go index 35be453..9b132a8 100644 --- a/internal/sqlc/film.sql.go +++ b/internal/sqlc/film.sql.go @@ -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() +} diff --git a/queries/film.sql b/queries/film.sql index 9df6279..a6d5f44 100644 --- a/queries/film.sql +++ b/queries/film.sql @@ -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; \ No newline at end of file +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 = ?; \ No newline at end of file