Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 99 additions & 16 deletions api.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,66 @@
package api

import (
"database/sql"
"net/http"

"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)

// album represents data about a record album.
type album struct {
ID string `json:"id"`
ID int64 `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}

// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
var db *sql.DB

func initDB() error {
var err error
db, err = sql.Open("sqlite3", "./albums.db")
if err != nil {
return err
}

createTableSQL := `CREATE TABLE IF NOT EXISTS albums (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
artist TEXT NOT NULL,
price REAL NOT NULL
);`

_, err = db.Exec(createTableSQL)
if err != nil {
return err
}

// Check if table is empty and seed with initial data
var count int
err = db.QueryRow("SELECT COUNT(*) FROM albums").Scan(&count)
if err != nil {
return err
}

if count == 0 {
seedData := []album{
{Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

for _, a := range seedData {
_, err = db.Exec("INSERT INTO albums (title, artist, price) VALUES (?, ?, ?)",
a.Title, a.Artist, a.Price)
if err != nil {
return err
}
}
}

return nil
}

// setupRouter configures and returns the Gin router with all routes
Expand All @@ -32,12 +74,35 @@ func setupRouter() *gin.Engine {
}

func main() {
if err := initDB(); err != nil {
panic(err)
}
defer db.Close()

router := setupRouter()
router.Run("localhost:8080")
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
rows, err := db.Query("SELECT id, title, artist, price FROM albums")
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer rows.Close()

var albums []album
for rows.Next() {
var a album
err := rows.Scan(&a.ID, &a.Title, &a.Artist, &a.Price)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
albums = append(albums, a)
}

c.IndentedJSON(http.StatusOK, albums)
}

Expand All @@ -51,8 +116,22 @@ func postAlbums(c *gin.Context) {
return
}

// Add the new album to the slice.
albums = append(albums, newAlbum)
// Insert the new album into the database and get the generated ID.
result, err := db.Exec("INSERT INTO albums (title, artist, price) VALUES (?, ?, ?)",
newAlbum.Title, newAlbum.Artist, newAlbum.Price)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

// Get the auto-generated ID
id, err := result.LastInsertId()
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
newAlbum.ID = id

c.IndentedJSON(http.StatusCreated, newAlbum)
}

Expand All @@ -61,13 +140,17 @@ func postAlbums(c *gin.Context) {
func getAlbumByID(c *gin.Context) {
id := c.Param("id")

// Loop through the list of albums, looking for
// an album whose ID value matches the parameter.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
var a album
err := db.QueryRow("SELECT id, title, artist, price FROM albums WHERE id = ?", id).
Scan(&a.ID, &a.Title, &a.Artist, &a.Price)

if err == sql.ErrNoRows {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
return
} else if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})

c.IndentedJSON(http.StatusOK, a)
}
3 changes: 1 addition & 2 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ func BenchmarkGetAlbumByIDNotFound(b *testing.B) {

func BenchmarkPostAlbumsValid(b *testing.B) {
newAlbum := album{
ID: "4",
Title: "Kind of Blue",
Artist: "Miles Davis",
Price: 29.99,
Expand All @@ -37,7 +36,7 @@ func BenchmarkPostAlbumsValid(b *testing.B) {
}

func BenchmarkPostAlbumsInvalidJSON(b *testing.B) {
invalidJSON := `{"id": "5", "title": "Invalid Album", "artist": "Test Artist", "price": "invalid_price"}`
invalidJSON := `{"title": "Invalid Album", "artist": "Test Artist", "price": "invalid_price"}`
req, _ := http.NewRequest("POST", "/albums", strings.NewReader(invalidJSON))
req.Header.Set("Content-Type", "application/json")
benchmarkRequest(b, req)
Expand Down
8 changes: 8 additions & 0 deletions benchmark_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ func setupBenchmarkRouter() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
// Discard all output during benchmarks to only preserve benchmark output
gin.DefaultWriter = io.Discard

// Initialize database if not already initialized
if db == nil {
if err := initDB(); err != nil {
panic(err)
}
}

return setupRouter()
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
Loading