diff --git a/.env.example b/.env.example index 54e6b94..035e905 100644 --- a/.env.example +++ b/.env.example @@ -15,9 +15,6 @@ DEBUG_MODE=true # External Services ## PSQL -### To enable PSQL connection, set PSQL_ENABLED true -PSQL_ENABLED=false - POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_USER=postgres diff --git a/README.md b/README.md index f09c6e1..5d1056c 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,25 @@ cp .env.example .env nano .env ``` -4. Run the application +4. Setup the database ```bash -make run || go run main.go +docker-compose up -d db ``` -5. Visit the application in your browser +5. Run the migrations + +```bash +make migrate-up +``` + +6. Run the application + +```bash +make run +``` + +7. Visit the application in your browser Feel free to visit the application at `localhost:8000` and move around available paths diff --git a/controller/api/get_users.go b/controller/api/get_users.go index 551c63a..619ea0d 100644 --- a/controller/api/get_users.go +++ b/controller/api/get_users.go @@ -2,6 +2,7 @@ package api import ( "net/http" + sql "web/repository/db" "github.com/gin-gonic/gin" diff --git a/controller/error/http_errors.go b/controller/error/client_http.go similarity index 96% rename from controller/error/http_errors.go rename to controller/error/client_http.go index 627070b..061cc5b 100644 --- a/controller/error/http_errors.go +++ b/controller/error/client_http.go @@ -1,4 +1,4 @@ -package ctrlerror +package httperr import ( "io" @@ -17,7 +17,9 @@ func StatusNotFound(c *gin.Context) { log.Printf("error opening file: %v", err) return } + defer file.Close() + if _, err := io.Copy(c.Writer, file); err != nil { if err := c.Error(err); err != nil { log.Printf("error adding error to Gin context: %v", err) diff --git a/go.mod b/go.mod index c09fc5c..1ca8657 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.22.1 require ( github.com/jarcoal/httpmock v1.3.1 github.com/stretchr/testify v1.9.0 + gorm.io/driver/postgres v1.5.7 + gorm.io/gorm v1.25.10 ) require ( @@ -13,6 +15,11 @@ require ( github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect diff --git a/go.sum b/go.sum index 216e982..a5c51f4 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,18 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -103,5 +113,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= +gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/helpers/file.go b/helpers/file.go new file mode 100644 index 0000000..fed4d0a --- /dev/null +++ b/helpers/file.go @@ -0,0 +1,8 @@ +package helper + +import "os" + +func CheckIfFileExists(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} diff --git a/helpers/http.go b/helpers/http.go new file mode 100644 index 0000000..7572041 --- /dev/null +++ b/helpers/http.go @@ -0,0 +1,32 @@ +package helper + +import ( + "log" + "os" + "strings" + + "github.com/gin-contrib/static" + "github.com/gin-gonic/gin" +) + +func ServePageAssets(router *gin.Engine) { + if CheckIfFileExists("./public/index.html") { + router.Use(static.Serve("/assets", static.LocalFile("./public/assets", true))) + } else { + router.Use(static.Serve("/assets", static.LocalFile("./views/assets", true))) + assetsPerPage(router) + } +} + +func assetsPerPage(router *gin.Engine) { + assetsDir, err := os.ReadDir("./views") + if err != nil { + log.Fatal(err) + } + + for _, directory := range assetsDir { + if directory.IsDir() && strings.Contains(directory.Name(), "_page") { + router.Use(static.Serve("/"+directory.Name()+"/assets", static.LocalFile("./views/"+directory.Name()+"/assets", true))) + } + } +} diff --git a/middleware/router.go b/middleware/router.go index 437919f..b0e1873 100644 --- a/middleware/router.go +++ b/middleware/router.go @@ -5,6 +5,7 @@ import ( "os" "web/controller/api" errorController "web/controller/error" + helper "web/helpers" httpTemplates "web/views/templates" "github.com/gin-contrib/static" @@ -35,19 +36,23 @@ func NewRouter(router *gin.Engine) *gin.Engine { c.Header("Permissions-Policy", "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()") }) - if _, err := os.Stat("./public/index.html"); os.IsNotExist(err) { - // Load welcome page from html template + // Frontend & Static Files Handling + router.LoadHTMLGlob("./views/**/*.html") + + router.Use(static.Serve("/assets", static.LocalFile("./public/assets", true))) + if !helper.CheckIfFileExists("./public/index.html") { router.GET("/", WelcomePageMiddleware()) + router.GET("/favicon.ico", func(c *gin.Context) { + c.String(http.StatusNoContent, "") + }) } else { - // Handle static files from the public folder router.Use(static.Serve("/", static.LocalFile("./public/", true))) } - // Serve static assets - router.Use(static.Serve("/assets", static.LocalFile("./public/assets", true))) - router.GET("/favicon.ico", func(c *gin.Context) { - c.String(http.StatusNoContent, "") - }) + helper.ServePageAssets(router) + + // HTML Template generated pages + router.GET("/status", httpTemplates.StatusPageResponse(), StatusPageMiddleware()) // API Handling apiGroup := router.Group(os.Getenv("API_PATH")) @@ -56,11 +61,6 @@ func NewRouter(router *gin.Engine) *gin.Engine { apiGroup.GET("/users", api.GetUsers) } - // HTML Templates (e.g Status page) - router.LoadHTMLGlob("./views/**/*") - router.GET("/status", httpTemplates.StatusPageResponse(), StatusPageMiddleware()) - router.GET("/docs", DocumentationPageMiddleware()) - // Error handling router.NoRoute(errorController.StatusNotFound) diff --git a/middleware/sites.go b/middleware/sites.go index 4ee2693..648308a 100644 --- a/middleware/sites.go +++ b/middleware/sites.go @@ -9,9 +9,9 @@ import ( // StatusPageMiddleware handles the status page func StatusPageMiddleware() gin.HandlerFunc { return func(c *gin.Context) { - statuses := c.MustGet("statuses").([]map[string]string) + status := c.MustGet("statuses").([]map[string]string) c.HTML(http.StatusOK, "status.html", gin.H{ - "services": statuses, + "services": status, }) } } @@ -22,9 +22,3 @@ func WelcomePageMiddleware() gin.HandlerFunc { c.HTML(http.StatusOK, "welcome.html", gin.H{}) } } - -func DocumentationPageMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - c.HTML(http.StatusOK, "docs.html", gin.H{}) - } -} diff --git a/repository/db/db_connection.go b/repository/db/db_connection.go index 0bac9b1..876abc2 100644 --- a/repository/db/db_connection.go +++ b/repository/db/db_connection.go @@ -1,24 +1,17 @@ package sql import ( - "database/sql" "fmt" - "log" "os" - // Import pq to register the Postgres driver. - _ "github.com/lib/pq" + "gorm.io/driver/postgres" + "gorm.io/gorm" ) -var DB *sql.DB +var DB *gorm.DB func NewDBConnection() error { - if os.Getenv("PSQL_ENABLED") != "true" && os.Getenv("APP_ENV") == "development" { - log.Println("Warning: PSQL is not enabled. Database queries will fail.") - return nil - } - - connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbConnAttrs := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", os.Getenv("POSTGRES_HOST"), os.Getenv("POSTGRES_PORT"), os.Getenv("POSTGRES_USER"), @@ -26,16 +19,12 @@ func NewDBConnection() error { os.Getenv("POSTGRES_DB"), ) - db, err := sql.Open("postgres", connStr) + db, err := gorm.Open(postgres.Open(dbConnAttrs), &gorm.Config{}) if err != nil { return err } - if err = db.Ping(); err != nil { - return err - } - - // assign the *sql.DB instance to DB + // assign the *gorm.DB instance to DB DB = db return nil } diff --git a/repository/db/get_users.go b/repository/db/get_users.go index a465f8d..9fe1546 100644 --- a/repository/db/get_users.go +++ b/repository/db/get_users.go @@ -5,20 +5,10 @@ import ( ) func GetUsers() ([]model.User, error) { - rows, err := DB.Query("SELECT id, name FROM users") - if err != nil { - return nil, err - } - defer rows.Close() - var users []model.User - for rows.Next() { - var user model.User - if err := rows.Scan(&user.UserID, &user.Name); err != nil { - return nil, err - } - users = append(users, user) + result := DB.Find(&users) + if result.Error != nil { + return nil, result.Error } - return users, nil } diff --git a/tests/statuspage_test.go b/tests/statuspage_test.go index f5b463a..f192621 100644 --- a/tests/statuspage_test.go +++ b/tests/statuspage_test.go @@ -4,6 +4,7 @@ import ( "net/http" "os" "testing" + model "web/model" httpTemplates "web/views/templates" diff --git a/public/assets/img/logo.png b/views/assets/img/logo.png similarity index 100% rename from public/assets/img/logo.png rename to views/assets/img/logo.png diff --git a/public/assets/main.css b/views/assets/main.css similarity index 100% rename from public/assets/main.css rename to views/assets/main.css diff --git a/views/components/header.html b/views/components/header.html index 05e0ecd..098e74c 100644 --- a/views/components/header.html +++ b/views/components/header.html @@ -10,7 +10,7 @@ diff --git a/views/docs_page/docs.html b/views/docs_page/docs.html deleted file mode 100644 index 6d29dc7..0000000 --- a/views/docs_page/docs.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - GoBlitz Web Framework - - - -
-

Coming Soon

-

Documentation page is coming soon

-

Take a look for the documentation page issue at GitHub

- Go Home -
- - \ No newline at end of file diff --git a/views/templates/statuspage.go b/views/templates/statuspage.go index 53df726..f411d5e 100644 --- a/views/templates/statuspage.go +++ b/views/templates/statuspage.go @@ -4,6 +4,7 @@ import ( "log" "net/http" "os" + model "web/model" "github.com/gin-gonic/gin"