chore: use the echo framework instead of plain go net/http

This commit is contained in:
2025-06-02 17:35:07 +01:00
parent 4e6d939fd5
commit 9bd04f843a
8 changed files with 199 additions and 71 deletions
+2 -8
View File
@@ -4,8 +4,6 @@ Copyright © 2025 Hazem Krimi me@hazemkrimi.tech
package cmd
import (
"fmt"
"net/http"
"os"
"github.com/hazemKrimi/crimson-vault/internal/api"
@@ -25,13 +23,9 @@ to quickly create a Cobra application.`,
// Uncomment the following line if your bare application
// has an action associated with it:
Run: func(cmd *cobra.Command, args []string) {
apiWrapper := api.APIWrapper{}
mux := http.NewServeMux()
server := api.API{}
apiWrapper.Initialize()
mux.Handle("/clients/", api.ClientRoutes(&apiWrapper))
fmt.Println("Server listening on PORT 5000...")
http.ListenAndServe(":5000", mux)
server.Initialize()
},
}
+10
View File
@@ -3,6 +3,7 @@ module github.com/hazemKrimi/crimson-vault
go 1.24.3
require (
github.com/labstack/echo/v4 v4.13.4
github.com/spf13/cobra v1.9.1
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.30.0
@@ -12,7 +13,16 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
)
+28
View File
@@ -1,20 +1,48 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
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.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
+21 -8
View File
@@ -1,16 +1,29 @@
package api
import "github.com/hazemKrimi/crimson-vault/internal/models"
import (
"github.com/hazemKrimi/crimson-vault/internal/models"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/echo/v4"
)
type APIWrapper struct {
dbWrapper *models.DBWrapper
type API struct {
instance *echo.Echo
db *models.DB
}
func (api *APIWrapper) Initialize() {
wrapper := models.DBWrapper{}
func (api *API) Initialize() {
db := &models.DB{}
ech := echo.New()
wrapper.Connect()
wrapper.MigrateClients()
db.Connect()
db.MigrateClients()
api.dbWrapper = &wrapper;
api.db = db
api.instance = ech
api.ClientRoutes()
api.instance.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
}))
api.instance.Logger.Fatal(api.instance.Start(":5000"))
}
+86 -34
View File
@@ -1,60 +1,112 @@
package api
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/hazemKrimi/crimson-vault/internal/models"
"github.com/labstack/echo/v4"
)
func (api *APIWrapper) CreateClientHandler(writer http.ResponseWriter, request *http.Request) {
func (api *API) CreateClientHandler(context echo.Context) error {
var body models.CreateClientBody
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
http.Error(writer, "Invalid JSON", http.StatusBadRequest)
if err := context.Bind(&body); err != nil {
log.Println(fmt.Sprintf("Error creating Client: %v.", err))
return
return context.String(http.StatusBadRequest, "Invalid JSON!")
}
client := api.dbWrapper.CreateClient(body)
client := api.db.CreateClient(body)
log.Println(fmt.Sprintf("Client created with ID %d.", client.ID))
json.NewEncoder(writer).Encode(client)
return context.JSON(http.StatusOK, client)
}
func (api *APIWrapper) GetClientsHandler(writer http.ResponseWriter, request *http.Request) {
idString := request.URL.Query().Get("id")
if idString != "" {
id, err := strconv.Atoi(idString)
if err != nil {
http.Error(writer, "Unexpected error getting Client.", http.StatusInternalServerError)
return
}
var client models.Client
if err := api.dbWrapper.GetClient(id, &client); err != nil {
http.Error(writer, "Client not found.", http.StatusNotFound)
return
}
log.Println(fmt.Sprintf("Got client with ID %d.", client.ID))
json.NewEncoder(writer).Encode(client)
return
}
clients, err := api.dbWrapper.GetClients()
func (api *API) GetAllClientsHandler(context echo.Context) error {
clients, err := api.db.GetClients()
if err != nil {
http.Error(writer, "Unexpected error getting Clients.", http.StatusInternalServerError)
return
return context.String(http.StatusInternalServerError, "Unexpected error getting Clients!")
}
log.Println("Got all Clients.")
json.NewEncoder(writer).Encode(clients)
return context.JSON(http.StatusOK, clients)
}
func (api *API) GetClientHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to get a Client!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error getting Client!")
}
var client models.Client
if err := api.db.GetClient(id, &client); err != nil {
return context.String(http.StatusNotFound, "Client not found!")
}
log.Println(fmt.Sprintf("Got client with ID %d.", client.ID))
return context.JSON(http.StatusOK, client)
}
func (api *API) UpdateClientHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to update a Client!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error updating Client!")
}
var body models.UpdateClientBody
if err := context.Bind(&body); err != nil {
log.Println(fmt.Sprintf("Error updating Client: %v.", err))
return context.String(http.StatusBadRequest, "Invalid JSON!")
}
var client models.Client
if err := api.db.UpdateClient(id, body, &client); err != nil {
return context.String(http.StatusNotFound, "Client not found!")
}
log.Println(fmt.Sprintf("Updated client with ID %d.", client.ID))
return context.JSON(http.StatusOK, client)
}
func (api *API) DeleteClientHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to delete a Client!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error deleting Client!")
}
var client models.Client
if err := api.db.DeleteClient(id); err != nil {
return context.String(http.StatusNotFound, "Client not found!")
}
log.Println(fmt.Sprintf("Deleted client with ID %d.", client.ID))
return context.String(http.StatusOK, "Client deleted successfully!")
}
+8 -9
View File
@@ -1,12 +1,11 @@
package api
import "net/http"
func ClientRoutes(api *APIWrapper) (*http.ServeMux) {
mux := http.NewServeMux()
mux.HandleFunc("GET /", api.GetClientsHandler)
mux.HandleFunc("POST /", api.CreateClientHandler)
return mux
func (api *API) ClientRoutes() {
group := api.instance.Group("/clients")
group.GET("/", api.GetAllClientsHandler)
group.POST("/", api.CreateClientHandler)
group.GET("/:id", api.GetClientHandler)
group.PUT("/:id", api.UpdateClientHandler)
group.DELETE("/:id", api.DeleteClientHandler)
}
+40 -8
View File
@@ -22,21 +22,27 @@ type CreateClientBody struct {
Phone string `json:"phone"`
}
func (wrapper *DBWrapper) MigrateClients() {
wrapper.db.AutoMigrate(&Client{})
type UpdateClientBody struct {
Name string `json:"name"`
Country string `json:"country"`
Phone string `json:"phone"`
}
func (wrapper *DBWrapper) CreateClient(body CreateClientBody) Client {
func (db *DB) MigrateClients() {
db.instance.AutoMigrate(&Client{})
}
func (db *DB) CreateClient(body CreateClientBody) Client {
client := Client{Name: body.Name, Country: body.Country, Phone: body.Phone}
wrapper.db.Create(&client)
db.instance.Create(&client)
return client
}
func (wrapper *DBWrapper) GetClients() ([]Client, error) {
func (db *DB) GetClients() ([]Client, error) {
var clients []Client
result := wrapper.db.Find(&clients)
result := db.instance.Find(&clients)
if result.Error != nil {
return nil, result.Error
@@ -45,8 +51,34 @@ func (wrapper *DBWrapper) GetClients() ([]Client, error) {
return clients, nil
}
func (wrapper *DBWrapper) GetClient(id int, client *Client) error {
result := wrapper.db.Where("id = ?", id).First(&client, id)
func (db *DB) GetClient(id int, client *Client) error {
result := db.instance.Where("id = ?", id).First(&client, id)
if result.Error != nil {
return result.Error
}
return nil
}
func (db *DB) UpdateClient(id int, body UpdateClientBody, client *Client) error {
result := db.instance.Where("id = ?", id).First(&client, id)
if result.Error != nil {
return result.Error
}
result = db.instance.Model(&client).Updates(Client{Name: body.Name, Country: body.Country, Phone: body.Phone})
if result.Error != nil {
return result.Error
}
return nil
}
func (db *DB) DeleteClient(id int) error {
result := db.instance.Delete(&Client{}, id)
if result.Error != nil {
return result.Error
+4 -4
View File
@@ -7,16 +7,16 @@ import (
"gorm.io/gorm"
)
type DBWrapper struct {
db *gorm.DB
type DB struct {
instance *gorm.DB
}
func (wrapper *DBWrapper) Connect() {
func (wrapper *DB) Connect() {
db, err := gorm.Open(sqlite.Open("crimson_vault.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
wrapper.db = db
wrapper.instance = db
}