diff --git a/admin-app/app.go b/admin-app/app.go index adf65d8..7384895 100644 --- a/admin-app/app.go +++ b/admin-app/app.go @@ -2,7 +2,6 @@ package admin_app import ( "encoding/json" - "fmt" "net/http" "strconv" @@ -33,7 +32,7 @@ type DeletePostRequest struct { Id int `json:"id"` } -func getPostHandler(database *database.Database) func(*gin.Context) { +func getPostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { // localhost:8080/post/{id} var post_binding PostBinding @@ -73,7 +72,7 @@ func getPostHandler(database *database.Database) func(*gin.Context) { } } -func postPostHandler(database *database.Database) func(*gin.Context) { +func postPostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { var add_post_request AddPostRequest decoder := json.NewDecoder(c.Request.Body) @@ -108,7 +107,7 @@ func postPostHandler(database *database.Database) func(*gin.Context) { } } -func putPostHandler(database *database.Database) func(*gin.Context) { +func putPostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { var change_post_request ChangePostRequest decoder := json.NewDecoder(c.Request.Body) @@ -145,7 +144,7 @@ func putPostHandler(database *database.Database) func(*gin.Context) { } } -func deletePostHandler(database *database.Database) func(*gin.Context) { +func deletePostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { var delete_post_request DeletePostRequest decoder := json.NewDecoder(c.Request.Body) @@ -177,21 +176,14 @@ func deletePostHandler(database *database.Database) func(*gin.Context) { } } -func Run(app_settings common.AppSettings, database database.Database) error { +func SetupRoutes(app_settings common.AppSettings, database database.Database) *gin.Engine{ r := gin.Default() r.MaxMultipartMemory = 1 - r.GET("/posts/:id", getPostHandler(&database)) - r.POST("/posts", postPostHandler(&database)) - r.PUT("/posts", putPostHandler(&database)) - r.DELETE("/posts", deletePostHandler(&database)) - - err := r.Run(fmt.Sprintf(":%s", app_settings.WebserverPort)) - if err != nil { - log.Error().Msgf("could not run app: %v", err) - return err - } - - return nil + r.GET("/posts/:id", getPostHandler(database)) + r.POST("/posts", postPostHandler(database)) + r.PUT("/posts", putPostHandler(database)) + r.DELETE("/posts", deletePostHandler(database)) + return r } diff --git a/app/app.go b/app/app.go index 3ddc312..c7aac8b 100644 --- a/app/app.go +++ b/app/app.go @@ -2,7 +2,6 @@ package app import ( "bytes" - "fmt" "net/http" "time" @@ -15,9 +14,9 @@ import ( const CACHE_TIMEOUT = 20 * time.Second -type Generator = func(*gin.Context, common.AppSettings, *database.Database) ([]byte, error) +type Generator = func(*gin.Context, common.AppSettings, database.Database) ([]byte, error) -func Run(app_settings common.AppSettings, database *database.Database) error { +func SetupRoutes(app_settings common.AppSettings, database database.Database) *gin.Engine{ r := gin.Default() r.MaxMultipartMemory = 1 @@ -31,16 +30,10 @@ func Run(app_settings common.AppSettings, database *database.Database) error { r.POST("/contact-send", makeContactFormHandler()) r.Static("/static", "./static") - err := r.Run(fmt.Sprintf(":%s", app_settings.WebserverPort)) - if err != nil { - log.Error().Msgf("could not run app: %v", err) - return err - } - - return nil + return r } -func addCachableHandler(e *gin.Engine, method string, endpoint string, generator Generator, cache *Cache, app_settings common.AppSettings, db *database.Database) { +func addCachableHandler(e *gin.Engine, method string, endpoint string, generator Generator, cache *Cache, app_settings common.AppSettings, db database.Database) { handler := func(c *gin.Context) { // if the endpoint is cached @@ -79,9 +72,9 @@ func addCachableHandler(e *gin.Engine, method string, endpoint string, generator } } -// / This function will act as the handler for -// / the home page -func homeHandler(c *gin.Context, settings common.AppSettings, db *database.Database) ([]byte, error) { +/// This function will act as the handler for +/// the home page +func homeHandler(c *gin.Context, settings common.AppSettings, db database.Database) ([]byte, error) { posts, err := db.GetPosts() if err != nil { return nil, err diff --git a/app/contact.go b/app/contact.go index 7b85b63..a9cbfc5 100644 --- a/app/contact.go +++ b/app/contact.go @@ -58,7 +58,7 @@ func makeContactFormHandler() func(*gin.Context) { } // TODO : This is a duplicate of the index handler... abstract -func contactHandler(c *gin.Context, app_settings common.AppSettings, db *database.Database) ([]byte, error) { +func contactHandler(c *gin.Context, app_settings common.AppSettings, db database.Database) ([]byte, error) { index_view := views.MakeContactPage() html_buffer := bytes.NewBuffer(nil) if err := index_view.Render(c, html_buffer); err != nil { diff --git a/app/post.go b/app/post.go index 433c307..7eb6eef 100644 --- a/app/post.go +++ b/app/post.go @@ -32,7 +32,7 @@ func mdToHTML(md []byte) []byte { return markdown.Render(doc, renderer) } -func postHandler(c *gin.Context, app_settings common.AppSettings, database *database.Database) ([]byte, error) { +func postHandler(c *gin.Context, app_settings common.AppSettings, database database.Database) ([]byte, error) { // localhost:8080/post/{id} var post_binding PostBinding diff --git a/cmd/urchin-admin/main.go b/cmd/urchin-admin/main.go index b51c94e..ccdaef1 100644 --- a/cmd/urchin-admin/main.go +++ b/cmd/urchin-admin/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + _ "github.com/go-sql-driver/mysql" admin_app "github.com/matheusgomes28/urchin/admin-app" "github.com/matheusgomes28/urchin/common" @@ -30,8 +32,6 @@ func main() { log.Fatal().Msgf("could not create database: %v", err) } - err = admin_app.Run(app_settings, database) - if err != nil { - log.Fatal().Msgf("could not run app: %v", err) - } + r := admin_app.SetupRoutes(app_settings, database) + r.Run(fmt.Sprintf(":%d", app_settings.WebserverPort)) } diff --git a/cmd/urchin/index_test.go b/cmd/urchin/index_test.go new file mode 100644 index 0000000..6826a97 --- /dev/null +++ b/cmd/urchin/index_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/matheusgomes28/urchin/app" + "github.com/matheusgomes28/urchin/common" + + "github.com/stretchr/testify/assert" +) + +type DatabaseMock struct {} + +func (db DatabaseMock) GetPosts() ([]common.Post, error) { + return []common.Post{ + { + Title: "TestPost", + Content: "TestContent", + Excerpt: "TestExcerpt", + Id: 0, + }, + }, nil +} + +func (db DatabaseMock) GetPost(post_id int) (common.Post, error) { + return common.Post{}, fmt.Errorf("not implemented") +} + +func (db DatabaseMock) AddPost(title string, excerpt string, content string) (int, error) { + return 0, fmt.Errorf("not implemented") +} + +func (db DatabaseMock) ChangePost(id int, title string, excerpt string, content string) error { + return nil +} + +func (db DatabaseMock) DeletePost(id int) error { + return fmt.Errorf("not implemented") +} + +func TestIndexPing(t *testing.T) { + app_settings := common.AppSettings{ + DatabaseAddress: "localhost", + DatabasePort: 3006, + DatabaseUser: "root", + DatabasePassword: "root", + DatabaseName: "urchin", + WebserverPort: 8080, + } + + database_mock := DatabaseMock{} + + r := app.SetupRoutes(app_settings, database_mock) + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + r.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "TestPost") + assert.Contains(t, w.Body.String(), "TestExcerpt") +} diff --git a/cmd/urchin/main.go b/cmd/urchin/main.go index 5335563..fe0ef9d 100644 --- a/cmd/urchin/main.go +++ b/cmd/urchin/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + _ "github.com/go-sql-driver/mysql" "github.com/matheusgomes28/urchin/app" "github.com/matheusgomes28/urchin/common" @@ -27,7 +29,6 @@ func main() { log.Error().Msgf("could not create database connection: %v", err) } - if err = app.Run(app_settings, &db_connection); err != nil { - log.Error().Msgf("could not run app: %v", err) - } + r := app.SetupRoutes(app_settings, db_connection) + r.Run(fmt.Sprintf(":%d", app_settings.WebserverPort)) } diff --git a/common/app_settings.go b/common/app_settings.go index 4f2b767..20adc6f 100644 --- a/common/app_settings.go +++ b/common/app_settings.go @@ -11,8 +11,8 @@ type AppSettings struct { DatabasePort int DatabaseUser string DatabasePassword string - DatabaseName string - WebserverPort string + DatabaseName string + WebserverPort int } func LoadSettings() (AppSettings, error) { @@ -47,11 +47,16 @@ func LoadSettings() (AppSettings, error) { return AppSettings{}, fmt.Errorf("URCHIN_DATABASE_PORT is not a valid integer: %v", err) } - webserver_port := os.Getenv("URCHIN_WEBSERVER_PORT") - if webserver_port == "" { + webserver_port_str := os.Getenv("URCHIN_WEBSERVER_PORT") + if webserver_port_str == "" { return AppSettings{}, fmt.Errorf("URCHIN_WEBSERVER_PORT is not defined") } + webserver_port, err := strconv.Atoi(webserver_port_str) + if err != nil { + return AppSettings{}, fmt.Errorf("URCHIN_WEBSERVER_PORT is not valid: %v", err) + } + return AppSettings{ DatabaseUser: database_user, DatabasePassword: database_password, diff --git a/database/database.go b/database/database.go index 7edf72d..d6a6a3a 100644 --- a/database/database.go +++ b/database/database.go @@ -10,7 +10,15 @@ import ( "github.com/rs/zerolog/log" ) -type Database struct { +type Database interface { + GetPosts() ([]common.Post, error) + GetPost(post_id int) (common.Post, error) + AddPost(title string, excerpt string, content string) (int, error) + ChangePost(id int, title string, excerpt string, content string) error + DeletePost(id int) error +} + +type SqlDatabase struct { Address string Port int User string @@ -19,7 +27,7 @@ type Database struct { // / This function gets all the posts from the current // / database connection. -func (db Database) GetPosts() ([]common.Post, error) { +func (db SqlDatabase) GetPosts() ([]common.Post, error) { rows, err := db.Connection.Query("SELECT title, excerpt, id FROM posts;") if err != nil { return make([]common.Post, 0), err @@ -40,7 +48,7 @@ func (db Database) GetPosts() ([]common.Post, error) { // / This function gets a post from the database // / with the given ID. -func (db *Database) GetPost(post_id int) (common.Post, error) { +func (db SqlDatabase) GetPost(post_id int) (common.Post, error) { rows, err := db.Connection.Query("SELECT title, content FROM posts WHERE id=?;", post_id) if err != nil { return common.Post{}, err @@ -56,8 +64,8 @@ func (db *Database) GetPost(post_id int) (common.Post, error) { return post, nil } -// / This function adds a post to the database -func (db *Database) AddPost(title string, excerpt string, content string) (int, error) { +/// This function adds a post to the database +func (db SqlDatabase) AddPost(title string, excerpt string, content string) (int, error) { res, err := db.Connection.Exec("INSERT INTO posts(content, title, excerpt) VALUES(?, ?, ?)", content, title, excerpt) if err != nil { return -1, err @@ -75,10 +83,10 @@ func (db *Database) AddPost(title string, excerpt string, content string) (int, return int(id), nil } -// / This function changes a post based on the values -// / provided. Note that empty strings will mean that -// / the value will not be updated. -func (db *Database) ChangePost(id int, title string, excerpt string, content string) error { +/// This function changes a post based on the values +/// provided. Note that empty strings will mean that +/// the value will not be updated. +func (db SqlDatabase) ChangePost(id int, title string, excerpt string, content string) error { tx, err := db.Connection.Begin() if err != nil { return err @@ -115,10 +123,10 @@ func (db *Database) ChangePost(id int, title string, excerpt string, content str return nil } -// / This function changes a post based on the values -// / provided. Note that empty strings will mean that -// / the value will not be updated. -func (db *Database) DeletePost(id int) error { +/// This function changes a post based on the values +/// provided. Note that empty strings will mean that +/// the value will not be updated. +func (db SqlDatabase) DeletePost(id int) error { if _, err := db.Connection.Exec("DELETE FROM posts WHERE id=?;", id); err != nil { return err } @@ -126,20 +134,20 @@ func (db *Database) DeletePost(id int) error { return nil } -func MakeSqlConnection(user string, password string, address string, port int, database string) (Database, error) { +func MakeSqlConnection(user string, password string, address string, port int, database string) (SqlDatabase, error) { /// Checking the DB connection /// TODO : let user specify the DB connection_str := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", user, password, address, port, database) db, err := sql.Open("mysql", connection_str) if err != nil { - return Database{}, err + return SqlDatabase{}, err } // See "Important settings" section. db.SetConnMaxLifetime(time.Second * 5) db.SetMaxOpenConns(10) db.SetMaxIdleConns(10) - return Database{ + return SqlDatabase{ Address: address, Port: port, User: user, diff --git a/go.mod b/go.mod index 5ad8dd5..b2af3bf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/matheusgomes28/urchin -go 1.22 +go 1.22.0 require ( github.com/a-h/templ v0.2.543 @@ -8,12 +8,14 @@ require ( github.com/go-sql-driver/mysql v1.7.1 github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 github.com/rs/zerolog v1.31.0 + github.com/stretchr/testify v1.8.4 github.com/zutto/shardedmap v0.0.0-20180201164343-415202d0910e ) require ( github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -28,6 +30,7 @@ require ( 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.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect diff --git a/go.sum b/go.sum index 04e3ac7..6c5e826 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=