generated from keploy/template
-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add simple books API with fasthttp & postgres (#51)
* adds simple books api Signed-off-by: DarkhanShakhan <[email protected]> * adds keploy testing Signed-off-by: DarkhanShakhan <[email protected]> * Update app.go for v2 * updated for v2 Signed-off-by: Animesh pathak <[email protected]> --------- Signed-off-by: DarkhanShakhan <[email protected]> Signed-off-by: Animesh pathak <[email protected]> Co-authored-by: Animesh Pathak <[email protected]> Co-authored-by: Animesh pathak <[email protected]>
- Loading branch information
1 parent
73502cf
commit ac2142e
Showing
16 changed files
with
1,026 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
version: '3.7' | ||
services: | ||
postgres: | ||
image: postgres:10.5 | ||
restart: always | ||
environment: | ||
- POSTGRES_DB=books | ||
- POSTGRES_USER=books_user | ||
- POSTGRES_PASSWORD=books_password | ||
ports: | ||
- '5433:5432' | ||
volumes: | ||
- ./migrations:/docker-entrypoint-initdb.d |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
module fasthttp-postgres | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/fasthttp/router v1.4.18 | ||
github.com/lib/pq v1.10.0 | ||
github.com/valyala/fasthttp v1.45.0 | ||
) | ||
|
||
require ( | ||
github.com/andybalholm/brotli v1.0.5 // indirect | ||
github.com/klauspost/compress v1.16.3 // indirect | ||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect | ||
github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= | ||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= | ||
github.com/fasthttp/router v1.4.18 h1:elMnlFq527oZd8MHsuUpO6uLDup1exv8rXPfIjClDHk= | ||
github.com/fasthttp/router v1.4.18/go.mod h1:ZmC20Mn0VgCBbUWFDmnYzFbQYRfdGeKgpkBy0+JioKA= | ||
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= | ||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | ||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= | ||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= | ||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= | ||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= | ||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||
github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= | ||
github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package app | ||
|
||
import ( | ||
"database/sql" | ||
"fasthttp-postgres/internal/handlers" | ||
"fasthttp-postgres/internal/repository" | ||
|
||
"log" | ||
|
||
"github.com/fasthttp/router" | ||
_ "github.com/lib/pq" | ||
"github.com/valyala/fasthttp" | ||
) | ||
|
||
func InitApp() { | ||
|
||
uri := "postgresql://books_user:books_password@localhost:5433/books?sslmode=disable" | ||
db, err := sql.Open("postgres", uri) | ||
if err != nil { | ||
log.Fatal("Error connecting to database") | ||
} | ||
repo := repository.NewRepository(db) | ||
ctrl := handlers.NewHandler(repo) | ||
router := router.New() | ||
|
||
router.GET("/authors", ctrl.GetAllAuthors) | ||
router.GET("/books", ctrl.GetAllBooks) | ||
router.GET("/books/{id}", ctrl.GetBookById) | ||
router.GET("/authors/{id}", ctrl.GetBooksByAuthorId) | ||
router.POST("/books", ctrl.CreateBook) | ||
router.POST("/authors", ctrl.CreateAuthor) | ||
server := &fasthttp.Server{ | ||
Handler: router.Handler, | ||
Name: "Server", | ||
} | ||
log.Println("Starting server: http://localhost:8080") | ||
if err := server.ListenAndServe(":8080"); err != nil { | ||
log.Fatalf("Error starting server: %s\n", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package entity | ||
|
||
type Author struct { | ||
ID uint `json:"id" db:"id"` | ||
FirstName string `json:"first_name" db:"first_name"` | ||
LastName string `json:"last_name" db:"last_name"` | ||
} | ||
|
||
type Book struct { | ||
ID uint `json:"id"` | ||
Title string `json:"title"` | ||
Year int `json:"year"` | ||
Author Author `json:"author"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package handlers | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fasthttp-postgres/internal/entity" | ||
"net/http" | ||
"strconv" | ||
|
||
"github.com/valyala/fasthttp" | ||
) | ||
|
||
type Handler struct { | ||
repository Repository | ||
} | ||
|
||
type Repository interface { | ||
GetAllAuthors(context.Context) ([]entity.Author, error) | ||
GetAllBooks(context.Context) ([]entity.Book, error) | ||
GetBookById(context.Context, int) ([]entity.Book, error) | ||
GetBooksByAuthorId(context.Context, int) ([]entity.Book, error) | ||
CreateBook(context.Context, entity.Book) error | ||
CreateAuthor(context.Context, entity.Author) error | ||
} | ||
|
||
func NewHandler(repository Repository) *Handler { | ||
return &Handler{ | ||
repository: repository, | ||
} | ||
} | ||
|
||
func (h *Handler) GetAllAuthors(ctx *fasthttp.RequestCtx) { | ||
authors, err := h.repository.GetAllAuthors(ctx) | ||
if err != nil { | ||
sendError(ctx, err, 500) | ||
return | ||
} | ||
sendData(ctx, authors) | ||
} | ||
|
||
func (h *Handler) GetAllBooks(ctx *fasthttp.RequestCtx) { | ||
books, err := h.repository.GetAllBooks(ctx) | ||
if err != nil { | ||
sendError(ctx, err, 500) | ||
return | ||
} | ||
sendData(ctx, books) | ||
} | ||
|
||
func (h *Handler) GetBookById(ctx *fasthttp.RequestCtx) { | ||
bookId := ctx.UserValue("id").(string) | ||
id, err := strconv.Atoi(bookId) | ||
if err != nil { | ||
sendError(ctx, nil, http.StatusNotFound) | ||
return | ||
} | ||
books, err := h.repository.GetBookById(ctx, id) | ||
if err != nil { | ||
sendError(ctx, nil, http.StatusNotFound) | ||
return | ||
} | ||
sendData(ctx, books[0]) | ||
} | ||
|
||
func (h *Handler) GetBooksByAuthorId(ctx *fasthttp.RequestCtx) { | ||
authorId := ctx.UserValue("id").(string) | ||
id, err := strconv.Atoi(authorId) | ||
if err != nil { | ||
sendError(ctx, nil, http.StatusNotFound) | ||
return | ||
} | ||
books, err := h.repository.GetBooksByAuthorId(ctx, id) | ||
if err != nil { | ||
sendError(ctx, nil, http.StatusNotFound) | ||
return | ||
} | ||
sendData(ctx, books) | ||
} | ||
|
||
func (h *Handler) CreateBook(ctx *fasthttp.RequestCtx) { | ||
var req createBookRequest | ||
err := json.Unmarshal(ctx.Request.Body(), &req) | ||
if err != nil { | ||
sendError(ctx, err, http.StatusBadRequest) | ||
return | ||
} | ||
if err := h.repository.CreateBook(ctx, req.convert()); err != nil { | ||
sendError(ctx, err, http.StatusInternalServerError) | ||
return | ||
} | ||
ctx.SetStatusCode(http.StatusCreated) | ||
} | ||
|
||
func (h *Handler) CreateAuthor(ctx *fasthttp.RequestCtx) { | ||
var author entity.Author | ||
err := json.Unmarshal(ctx.Request.Body(), &author) | ||
if err != nil { | ||
sendError(ctx, err, http.StatusBadRequest) | ||
return | ||
} | ||
if err := h.repository.CreateAuthor(ctx, author); err != nil { | ||
sendError(ctx, err, http.StatusInternalServerError) | ||
return | ||
} | ||
ctx.SetStatusCode(http.StatusCreated) | ||
} | ||
|
||
type createBookRequest struct { | ||
Title string `json:"title"` | ||
Year int `json:"year"` | ||
AuthorID uint `json:"author_id"` | ||
} | ||
|
||
func (r createBookRequest) convert() entity.Book { | ||
return entity.Book{ | ||
Title: r.Title, | ||
Year: r.Year, | ||
Author: entity.Author{ | ||
ID: r.AuthorID, | ||
}, | ||
} | ||
} | ||
|
||
func sendData(ctx *fasthttp.RequestCtx, data interface{}) { | ||
ctx.SetContentType("application/json") | ||
v, err := json.Marshal(data) | ||
if err != nil { | ||
sendError(ctx, err, 500) | ||
return | ||
} | ||
ctx.Response.SetBody(v) | ||
} | ||
|
||
func sendError(ctx *fasthttp.RequestCtx, err error, code int) { | ||
var msg string | ||
if err != nil { | ||
msg = err.Error() | ||
} | ||
ctx.Error(msg, code) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package repository | ||
|
||
import "fasthttp-postgres/internal/entity" | ||
|
||
type model struct { | ||
ID uint `db:"id"` | ||
Title string `db:"title"` | ||
Year int `db:"year"` | ||
AuthorID uint `db:"author_id"` | ||
FirstName string `db:"first_name"` | ||
LastName string `db:"last_name"` | ||
} | ||
|
||
func convert(b entity.Book) model { | ||
return model{ | ||
Title: b.Title, | ||
Year: b.Year, | ||
AuthorID: b.Author.ID, | ||
} | ||
} | ||
|
||
func (m model) convert() entity.Book { | ||
return entity.Book{ | ||
ID: m.ID, | ||
Title: m.Title, | ||
Year: m.Year, | ||
Author: entity.Author{ | ||
ID: m.AuthorID, | ||
FirstName: m.FirstName, | ||
LastName: m.LastName, | ||
}, | ||
} | ||
} | ||
|
||
type models []model | ||
|
||
func (mm models) convert() []entity.Book { | ||
out := make([]entity.Book, 0, len(mm)) | ||
for _, m := range mm { | ||
out = append(out, m.convert()) | ||
} | ||
return out | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package repository | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fasthttp-postgres/internal/entity" | ||
"fmt" | ||
) | ||
|
||
type Repository struct { | ||
db *sql.DB | ||
} | ||
|
||
func NewRepository(db *sql.DB) *Repository { | ||
return &Repository{ | ||
db: db, | ||
} | ||
} | ||
|
||
func (r *Repository) GetAllAuthors(ctx context.Context) ([]entity.Author, error) { | ||
var mm []entity.Author | ||
rows, err := r.db.QueryContext(ctx, getAllAuthors) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var m entity.Author | ||
for rows.Next() { | ||
if err = rows.Scan(&m.ID, &m.FirstName, &m.LastName); err != nil { | ||
return nil, err | ||
} | ||
mm = append(mm, m) | ||
} | ||
return mm, nil | ||
} | ||
|
||
func (r *Repository) GetAllBooks(ctx context.Context) ([]entity.Book, error) { | ||
var mm models | ||
rows, err := r.db.QueryContext(ctx, getAllBooks) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var m model | ||
for rows.Next() { | ||
if err = rows.Scan(&m.ID, &m.Title, &m.Year, &m.AuthorID, &m.FirstName, &m.LastName); err != nil { | ||
return nil, err | ||
} | ||
mm = append(mm, m) | ||
} | ||
return mm.convert(), nil | ||
} | ||
|
||
func (r *Repository) GetBookById(ctx context.Context, id int) ([]entity.Book, error) { | ||
var mm models | ||
rows, err := r.db.QueryContext(ctx, getBookById, id) | ||
if err != nil { | ||
fmt.Println("1") | ||
return nil, err | ||
} | ||
var m model | ||
for rows.Next() { | ||
if err = rows.Scan(&m.ID, &m.Title, &m.Year, &m.AuthorID, &m.FirstName, &m.LastName); err != nil { | ||
fmt.Println("3") | ||
return nil, err | ||
} | ||
mm = append(mm, m) | ||
} | ||
return mm.convert(), nil | ||
} | ||
|
||
func (r *Repository) GetBooksByAuthorId(ctx context.Context, id int) ([]entity.Book, error) { | ||
var mm models | ||
rows, err := r.db.QueryContext(ctx, getBooksByAuthorId, id) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var m model | ||
for rows.Next() { | ||
if err = rows.Scan(&m.ID, &m.Title, &m.Year, &m.AuthorID, &m.FirstName, &m.LastName); err != nil { | ||
return nil, err | ||
} | ||
mm = append(mm, m) | ||
} | ||
return mm.convert(), nil | ||
} | ||
|
||
func (r *Repository) CreateBook(ctx context.Context, b entity.Book) error { | ||
m := convert(b) | ||
_, err := r.db.ExecContext(ctx, createBookStmt, m.Title, m.Year, m.AuthorID) | ||
return err | ||
} | ||
|
||
func (r *Repository) CreateAuthor(ctx context.Context, a entity.Author) error { | ||
_, err := r.db.ExecContext(ctx, createAuthorStmt, a.FirstName, a.LastName) | ||
return err | ||
} |
Oops, something went wrong.