From 43fbccff87bd7d1a9336b90455d75761fd419210 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 14:06:54 +0800 Subject: [PATCH 01/21] extract metal pkg --- cli/main.go | 6 +- database/seeder/main.go | 8 +- main.go | 8 +- metal/app.go | 75 +++++++++++ metal/factory.go | 128 +++++++++++++++++++ metal/helpers.go | 53 ++++++++ metal/ignite.go | 15 +++ metal/metal_test.go | 272 ++++++++++++++++++++++++++++++++++++++++ metal/router.go | 122 ++++++++++++++++++ 9 files changed, 676 insertions(+), 11 deletions(-) create mode 100644 metal/app.go create mode 100644 metal/factory.go create mode 100644 metal/helpers.go create mode 100644 metal/ignite.go create mode 100644 metal/metal_test.go create mode 100644 metal/router.go diff --git a/cli/main.go b/cli/main.go index e36a349e..beded669 100644 --- a/cli/main.go +++ b/cli/main.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "github.com/oullin/boost" + "github.com/oullin/metal" "github.com/oullin/cli/accounts" "github.com/oullin/cli/panel" "github.com/oullin/cli/posts" @@ -17,10 +17,10 @@ var environment *env.Environment var dbConn *database.Connection func init() { - secrets := boost.Ignite("./../.env", pkg.GetDefaultValidator()) + secrets := metal.Ignite("./../.env", pkg.GetDefaultValidator()) environment = secrets - dbConn = boost.MakeDbConnection(environment) + dbConn = metal.MakeDbConnection(environment) } func main() { diff --git a/database/seeder/main.go b/database/seeder/main.go index de915045..b269d625 100644 --- a/database/seeder/main.go +++ b/database/seeder/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/oullin/boost" + "github.com/oullin/metal" "github.com/oullin/database" "github.com/oullin/database/seeder/seeds" "github.com/oullin/env" @@ -14,7 +14,7 @@ import ( var environment *env.Environment func init() { - secrets := boost.Ignite("./.env", pkg.GetDefaultValidator()) + secrets := metal.Ignite("./.env", pkg.GetDefaultValidator()) environment = secrets } @@ -22,8 +22,8 @@ func init() { func main() { cli.ClearScreen() - dbConnection := boost.MakeDbConnection(environment) - logs := boost.MakeLogs(environment) + dbConnection := metal.MakeDbConnection(environment) + logs := metal.MakeLogs(environment) defer (*logs).Close() defer (*dbConnection).Close() diff --git a/main.go b/main.go index f099a36f..823614d7 100644 --- a/main.go +++ b/main.go @@ -3,19 +3,19 @@ package main import ( "fmt" _ "github.com/lib/pq" - "github.com/oullin/boost" + "github.com/oullin/metal" "github.com/oullin/pkg" "github.com/rs/cors" "log/slog" baseHttp "net/http" ) -var app *boost.App +var app *metal.App func init() { validate := pkg.GetDefaultValidator() - secrets := boost.Ignite("./.env", validate) - application, err := boost.MakeApp(secrets, validate) + secrets := metal.Ignite("./.env", validate) + application, err := metal.MakeApp(secrets, validate) if err != nil { panic(fmt.Sprintf("init: Error creating application: %s", err)) diff --git a/metal/app.go b/metal/app.go new file mode 100644 index 00000000..17e50d77 --- /dev/null +++ b/metal/app.go @@ -0,0 +1,75 @@ +package metal + +import ( + "fmt" + "github.com/oullin/database" + "github.com/oullin/database/repository" + "github.com/oullin/env" + "github.com/oullin/pkg" + "github.com/oullin/pkg/auth" + "github.com/oullin/pkg/http/middleware" + "github.com/oullin/pkg/llogs" + baseHttp "net/http" +) + +type App struct { + router *Router + sentry *pkg.Sentry + logs *llogs.Driver + validator *pkg.Validator + env *env.Environment + db *database.Connection +} + +func MakeApp(env *env.Environment, validator *pkg.Validator) (*App, error) { + tokenHandler, err := auth.MakeTokensHandler( + []byte(env.App.MasterKey), + ) + + if err != nil { + return nil, fmt.Errorf("bootstrapping error > could not create a token handler: %w", err) + } + + db := MakeDbConnection(env) + + app := App{ + env: env, + validator: validator, + logs: MakeLogs(env), + sentry: MakeSentry(env), + db: db, + } + + router := Router{ + Env: env, + Db: db, + Mux: baseHttp.NewServeMux(), + Pipeline: middleware.Pipeline{ + Env: env, + ApiKeys: &repository.ApiKeys{DB: db}, + TokenHandler: tokenHandler, + }, + } + + app.SetRouter(router) + + return &app, nil +} + +func (a *App) Boot() { + if a == nil || a.router == nil { + panic("bootstrapping error > Invalid setup") + } + + router := *a.router + + router.Profile() + router.Experience() + router.Projects() + router.Social() + router.Talks() + router.Education() + router.Recommendations() + router.Posts() + router.Categories() +} diff --git a/metal/factory.go b/metal/factory.go new file mode 100644 index 00000000..c1b02439 --- /dev/null +++ b/metal/factory.go @@ -0,0 +1,128 @@ +package metal + +import ( + "github.com/getsentry/sentry-go" + sentryhttp "github.com/getsentry/sentry-go/http" + "github.com/oullin/database" + "github.com/oullin/env" + "github.com/oullin/pkg" + "github.com/oullin/pkg/llogs" + "log" + "strconv" + "time" +) + +func MakeSentry(env *env.Environment) *pkg.Sentry { + cOptions := sentry.ClientOptions{ + Dsn: env.Sentry.DSN, + Debug: true, + } + + if err := sentry.Init(cOptions); err != nil { + log.Fatalf("sentry.Init: %s", err) + } + + defer sentry.Flush(2 * time.Second) + + options := sentryhttp.Options{} + handler := sentryhttp.New(options) + + return &pkg.Sentry{ + Handler: handler, + Options: &options, + Env: env, + } +} + +func MakeDbConnection(env *env.Environment) *database.Connection { + dbConn, err := database.MakeConnection(env) + + if err != nil { + panic("Sql: error connecting to PostgreSQL: " + err.Error()) + } + + return dbConn +} + +func MakeLogs(env *env.Environment) *llogs.Driver { + lDriver, err := llogs.MakeFilesLogs(env) + + if err != nil { + panic("logs: error opening logs file: " + err.Error()) + } + + return &lDriver +} + +func MakeEnv(validate *pkg.Validator) *env.Environment { + errorSuffix := "Environment: " + + port, _ := strconv.Atoi(env.GetEnvVar("ENV_DB_PORT")) + + app := env.AppEnvironment{ + Name: env.GetEnvVar("ENV_APP_NAME"), + Type: env.GetEnvVar("ENV_APP_ENV_TYPE"), + MasterKey: env.GetEnvVar("ENV_APP_MASTER_KEY"), + } + + db := env.DBEnvironment{ + UserName: env.GetSecretOrEnv("pg_username", "ENV_DB_USER_NAME"), + UserPassword: env.GetSecretOrEnv("pg_password", "ENV_DB_USER_PASSWORD"), + DatabaseName: env.GetSecretOrEnv("pg_dbname", "ENV_DB_DATABASE_NAME"), + Port: port, + Host: env.GetEnvVar("ENV_DB_HOST"), + DriverName: database.DriverName, + SSLMode: env.GetEnvVar("ENV_DB_SSL_MODE"), + TimeZone: env.GetEnvVar("ENV_DB_TIMEZONE"), + } + + logsCreds := env.LogsEnvironment{ + Level: env.GetEnvVar("ENV_APP_LOG_LEVEL"), + Dir: env.GetEnvVar("ENV_APP_LOGS_DIR"), + DateFormat: env.GetEnvVar("ENV_APP_LOGS_DATE_FORMAT"), + } + + net := env.NetEnvironment{ + HttpHost: env.GetEnvVar("ENV_HTTP_HOST"), + HttpPort: env.GetEnvVar("ENV_HTTP_PORT"), + } + + sentryEnvironment := env.SentryEnvironment{ + DSN: env.GetEnvVar("ENV_SENTRY_DSN"), + CSP: env.GetEnvVar("ENV_SENTRY_CSP"), + } + + if _, err := validate.Rejects(app); err != nil { + panic(errorSuffix + "invalid [APP] model: " + validate.GetErrorsAsJson()) + } + + if _, err := validate.Rejects(db); err != nil { + panic(errorSuffix + "invalid [Sql] model: " + validate.GetErrorsAsJson()) + } + + if _, err := validate.Rejects(logsCreds); err != nil { + panic(errorSuffix + "invalid [logs Creds] model: " + validate.GetErrorsAsJson()) + } + + if _, err := validate.Rejects(net); err != nil { + panic(errorSuffix + "invalid [NETWORK] model: " + validate.GetErrorsAsJson()) + } + + if _, err := validate.Rejects(sentryEnvironment); err != nil { + panic(errorSuffix + "invalid [SENTRY] model: " + validate.GetErrorsAsJson()) + } + + blog := &env.Environment{ + App: app, + DB: db, + Logs: logsCreds, + Network: net, + Sentry: sentryEnvironment, + } + + if _, err := validate.Rejects(blog); err != nil { + panic(errorSuffix + "invalid blog [ENVIRONMENT] model: " + validate.GetErrorsAsJson()) + } + + return blog +} diff --git a/metal/helpers.go b/metal/helpers.go new file mode 100644 index 00000000..f6c60e72 --- /dev/null +++ b/metal/helpers.go @@ -0,0 +1,53 @@ +package metal + +import ( + "github.com/oullin/database" + "github.com/oullin/env" + baseHttp "net/http" +) + +func (a *App) SetRouter(router Router) { + a.router = &router +} + +func (a *App) CloseLogs() { + if a.logs == nil { + return + } + + driver := *a.logs + driver.Close() +} + +func (a *App) CloseDB() { + if a.db == nil { + return + } + + driver := *a.db + driver.Close() +} + +func (a *App) IsLocal() bool { + return a.env.App.IsLocal() +} + +func (a *App) IsProduction() bool { + return a.env.App.IsProduction() +} + +func (a *App) GetEnv() *env.Environment { + return a.env +} + +func (a *App) GetDB() *database.Connection { + return a.db +} + +func (a *App) GetMux() *baseHttp.ServeMux { + if a.router == nil { + return nil + } + + return a.router.Mux +} diff --git a/metal/ignite.go b/metal/ignite.go new file mode 100644 index 00000000..b998914a --- /dev/null +++ b/metal/ignite.go @@ -0,0 +1,15 @@ +package metal + +import ( + "github.com/joho/godotenv" + "github.com/oullin/env" + "github.com/oullin/pkg" +) + +func Ignite(envPath string, validate *pkg.Validator) *env.Environment { + if err := godotenv.Load(envPath); err != nil { + panic("failed to read the .env file/values: " + err.Error()) + } + + return MakeEnv(validate) +} diff --git a/metal/metal_test.go b/metal/metal_test.go new file mode 100644 index 00000000..c7d28a5f --- /dev/null +++ b/metal/metal_test.go @@ -0,0 +1,272 @@ +package metal + +import ( + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/oullin/database" + "github.com/oullin/database/repository" + "github.com/oullin/pkg" + "github.com/oullin/pkg/auth" + "github.com/oullin/pkg/http/middleware" + "github.com/oullin/pkg/llogs" +) + +func validEnvVars(t *testing.T) { + t.Setenv("ENV_APP_NAME", "guss") + t.Setenv("ENV_APP_ENV_TYPE", "local") + t.Setenv("ENV_APP_MASTER_KEY", "12345678901234567890123456789012") + t.Setenv("ENV_DB_USER_NAME", "usernamefoo") + t.Setenv("ENV_DB_USER_PASSWORD", "passwordfoo") + t.Setenv("ENV_DB_DATABASE_NAME", "dbnamefoo") + t.Setenv("ENV_DB_PORT", "5432") + t.Setenv("ENV_DB_HOST", "localhost") + t.Setenv("ENV_DB_SSL_MODE", "require") + t.Setenv("ENV_DB_TIMEZONE", "UTC") + t.Setenv("ENV_APP_LOG_LEVEL", "debug") + t.Setenv("ENV_APP_LOGS_DIR", "logs_%s.log") + t.Setenv("ENV_APP_LOGS_DATE_FORMAT", "2006_01_02") + t.Setenv("ENV_HTTP_HOST", "localhost") + t.Setenv("ENV_HTTP_PORT", "8080") + t.Setenv("ENV_SENTRY_DSN", "dsn") + t.Setenv("ENV_SENTRY_CSP", "csp") +} + +func TestMakeEnv(t *testing.T) { + validEnvVars(t) + + env := MakeEnv(pkg.GetDefaultValidator()) + + if env.App.Name != "guss" { + t.Fatalf("env not loaded") + } +} + +func TestIgnite(t *testing.T) { + content := "ENV_APP_NAME=guss\n" + + "ENV_APP_ENV_TYPE=local\n" + + "ENV_APP_MASTER_KEY=12345678901234567890123456789012\n" + + "ENV_DB_USER_NAME=usernamefoo\n" + + "ENV_DB_USER_PASSWORD=passwordfoo\n" + + "ENV_DB_DATABASE_NAME=dbnamefoo\n" + + "ENV_DB_PORT=5432\n" + + "ENV_DB_HOST=localhost\n" + + "ENV_DB_SSL_MODE=require\n" + + "ENV_DB_TIMEZONE=UTC\n" + + "ENV_APP_LOG_LEVEL=debug\n" + + "ENV_APP_LOGS_DIR=logs_%s.log\n" + + "ENV_APP_LOGS_DATE_FORMAT=2006_01_02\n" + + "ENV_HTTP_HOST=localhost\n" + + "ENV_HTTP_PORT=8080\n" + + "ENV_SENTRY_DSN=dsn\n" + + "ENV_SENTRY_CSP=csp\n" + + f, err := os.CreateTemp("", "envfile") + + if err != nil { + t.Fatalf("temp file err: %v", err) + } + + defer os.Remove(f.Name()) + f.WriteString(content) + f.Close() + + env := Ignite(f.Name(), pkg.GetDefaultValidator()) + + if env.Network.HttpPort != "8080" { + t.Fatalf("env not loaded") + } +} + +func TestAppBootNil(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic") + } + }() + + var a *App + a.Boot() +} + +func TestAppHelpers(t *testing.T) { + app := &App{} + + mux := http.NewServeMux() + r := Router{Mux: mux} + + app.SetRouter(r) + + if app.GetMux() != mux { + t.Fatalf("mux not set") + } + + app.CloseLogs() + app.CloseDB() + + if app.GetEnv() != nil { + t.Fatalf("expected nil env") + } + + if app.GetDB() != nil { + t.Fatalf("expected nil db") + } +} + +func TestAppBootRoutes(t *testing.T) { + validEnvVars(t) + + env := MakeEnv(pkg.GetDefaultValidator()) + + key, err := auth.GenerateAESKey() + + if err != nil { + t.Fatalf("key err: %v", err) + } + + handler, err := auth.MakeTokensHandler(key) + + if err != nil { + t.Fatalf("handler err: %v", err) + } + + router := Router{ + Env: env, + Mux: http.NewServeMux(), + Pipeline: middleware.Pipeline{ + Env: env, + ApiKeys: &repository.ApiKeys{DB: &database.Connection{}}, + TokenHandler: handler, + }, + Db: &database.Connection{}, + } + + app := &App{} + + app.SetRouter(router) + + app.Boot() + + routes := []struct { + method string + path string + }{ + {"GET", "/profile"}, + {"GET", "/experience"}, + {"GET", "/projects"}, + {"GET", "/social"}, + {"GET", "/talks"}, + {"GET", "/education"}, + {"GET", "/recommendations"}, + {"POST", "/posts"}, + {"GET", "/posts/slug"}, + {"GET", "/categories"}, + } + + for _, rt := range routes { + req := httptest.NewRequest(rt.method, rt.path, nil) + h, pattern := app.GetMux().Handler(req) + + if pattern == "" || h == nil { + t.Fatalf("route missing %s %s", rt.method, rt.path) + } + } +} + +func TestMakeLogs(t *testing.T) { + // Create a temporary directory for logs + logDir := "/tmp/logs" + err := os.MkdirAll(logDir, 0755) + if err != nil { + t.Fatalf("failed to create log directory: %v", err) + } + defer os.RemoveAll(logDir) // Clean up after test + + validEnvVars(t) + t.Setenv("ENV_APP_LOGS_DIR", logDir+"/log-%s.txt") + + env := MakeEnv(pkg.GetDefaultValidator()) + + d := MakeLogs(env) + driver := *d + fl := driver.(llogs.FilesLogs) + + if !strings.HasPrefix(fl.DefaultPath(), logDir) { + t.Fatalf("wrong log dir") + } + + if !fl.Close() { + t.Fatalf("close failed") + } +} + +func TestMakeDbConnectionPanic(t *testing.T) { + validEnvVars(t) + t.Setenv("ENV_DB_PORT", "1") + t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") + + env := MakeEnv(pkg.GetDefaultValidator()) + + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic") + } + }() + + MakeDbConnection(env) +} + +func TestMakeAppPanic(t *testing.T) { + validEnvVars(t) + t.Setenv("ENV_DB_PORT", "1") + t.Setenv("ENV_APP_LOGS_DIR", "/tmp/log-%s.txt") + t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") + + env := MakeEnv(pkg.GetDefaultValidator()) + + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic") + } + }() + + MakeApp(env, pkg.GetDefaultValidator()) +} + +func TestMakeSentry(t *testing.T) { + validEnvVars(t) + t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") + + env := MakeEnv(pkg.GetDefaultValidator()) + + s := MakeSentry(env) + + if s == nil || s.Handler == nil || s.Options == nil { + t.Fatalf("sentry setup failed") + } +} + +func TestCloseLogs(t *testing.T) { + + validEnvVars(t) + t.Setenv("ENV_APP_LOGS_DIR", "/tmp/logs/log-%s.txt") + t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") + + env := MakeEnv(pkg.GetDefaultValidator()) + + l := MakeLogs(env) + app := &App{logs: l} + + app.CloseLogs() +} + +func TestGetMuxNil(t *testing.T) { + app := &App{} + + if app.GetMux() != nil { + t.Fatalf("expected nil mux") + } +} diff --git a/metal/router.go b/metal/router.go new file mode 100644 index 00000000..9339dfdb --- /dev/null +++ b/metal/router.go @@ -0,0 +1,122 @@ +package metal + +import ( + "github.com/oullin/database" + "github.com/oullin/database/repository" + "github.com/oullin/env" + "github.com/oullin/handler" + "github.com/oullin/pkg/http" + "github.com/oullin/pkg/http/middleware" + baseHttp "net/http" +) + +type Router struct { + Env *env.Environment + Mux *baseHttp.ServeMux + Pipeline middleware.Pipeline + Db *database.Connection +} + +func (r *Router) PipelineFor(apiHandler http.ApiHandler) baseHttp.HandlerFunc { + tokenMiddleware := middleware.MakeTokenMiddleware( + r.Pipeline.TokenHandler, + r.Pipeline.ApiKeys, + ) + + return http.MakeApiHandler( + r.Pipeline.Chain( + apiHandler, + tokenMiddleware.Handle, + ), + ) +} + +func (r *Router) Posts() { + repo := repository.Posts{DB: r.Db} + abstract := handler.MakePostsHandler(&repo) + + index := r.PipelineFor(abstract.Index) + show := r.PipelineFor(abstract.Show) + + r.Mux.HandleFunc("POST /posts", index) + r.Mux.HandleFunc("GET /posts/{slug}", show) +} + +func (r *Router) Categories() { + repo := repository.Categories{DB: r.Db} + abstract := handler.MakeCategoriesHandler(&repo) + + index := r.PipelineFor(abstract.Index) + + r.Mux.HandleFunc("GET /categories", index) +} + +func (r *Router) Profile() { + abstract := handler.MakeProfileHandler("./storage/fixture/profile.json") + + resolver := r.PipelineFor( + abstract.Handle, + ) + + r.Mux.HandleFunc("GET /profile", resolver) +} + +func (r *Router) Experience() { + abstract := handler.MakeExperienceHandler("./storage/fixture/experience.json") + + resolver := r.PipelineFor( + abstract.Handle, + ) + + r.Mux.HandleFunc("GET /experience", resolver) +} + +func (r *Router) Projects() { + abstract := handler.MakeProjectsHandler("./storage/fixture/projects.json") + + resolver := r.PipelineFor( + abstract.Handle, + ) + + r.Mux.HandleFunc("GET /projects", resolver) +} + +func (r *Router) Social() { + abstract := handler.MakeSocialHandler("./storage/fixture/social.json") + + resolver := r.PipelineFor( + abstract.Handle, + ) + + r.Mux.HandleFunc("GET /social", resolver) +} + +func (r *Router) Talks() { + abstract := handler.MakeTalksHandler("./storage/fixture/talks.json") + + resolver := r.PipelineFor( + abstract.Handle, + ) + + r.Mux.HandleFunc("GET /talks", resolver) +} + +func (r *Router) Education() { + abstract := handler.MakeEducationHandler("./storage/fixture/education.json") + + resolver := r.PipelineFor( + abstract.Handle, + ) + + r.Mux.HandleFunc("GET /education", resolver) +} + +func (r *Router) Recommendations() { + abstract := handler.MakeRecommendationsHandler("./storage/fixture/recommendations.json") + + resolver := r.PipelineFor( + abstract.Handle, + ) + + r.Mux.HandleFunc("GET /recommendations", resolver) +} From 8a2a050b8b5823e7c5a9517faf32b4df1868e1b4 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 14:44:20 +0800 Subject: [PATCH 02/21] extract kernel package --- .gitignore | 4 + bin/.gitkeep | 0 boost/boost_test.go | 276 ------------------ boost/helpers.go | 53 ---- cli/main.go | 6 +- database/seeder/main.go | 13 +- main.go | 8 +- metal/app.go | 75 ----- metal/factory.go | 128 -------- metal/ignite.go | 15 - {boost => metal/kernel}/app.go | 5 +- {boost => metal/kernel}/factory.go | 9 +- metal/{ => kernel}/helpers.go | 2 +- {boost => metal/kernel}/ignite.go | 2 +- .../{metal_test.go => kernel/kernel_test.go} | 2 +- {boost => metal/kernel}/router.go | 2 +- metal/router.go | 122 -------- 17 files changed, 30 insertions(+), 692 deletions(-) create mode 100644 bin/.gitkeep delete mode 100644 boost/boost_test.go delete mode 100644 boost/helpers.go delete mode 100644 metal/app.go delete mode 100644 metal/factory.go delete mode 100644 metal/ignite.go rename {boost => metal/kernel}/app.go (98%) rename {boost => metal/kernel}/factory.go (99%) rename metal/{ => kernel}/helpers.go (97%) rename {boost => metal/kernel}/ignite.go (95%) rename metal/{metal_test.go => kernel/kernel_test.go} (99%) rename {boost => metal/kernel}/router.go (99%) delete mode 100644 metal/router.go diff --git a/.gitignore b/.gitignore index 15344de3..ce45f30f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ tmp database/infra/data +# --- [API]: Bin +bin/*.* +!bin/.gitkeep + # --- [API]: Storage storage/logs/*.* storage/media/*.* diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/boost/boost_test.go b/boost/boost_test.go deleted file mode 100644 index 0f5202d1..00000000 --- a/boost/boost_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package boost - -import ( - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/oullin/database" - "github.com/oullin/database/repository" - "github.com/oullin/pkg" - "github.com/oullin/pkg/auth" - "github.com/oullin/pkg/http/middleware" - "github.com/oullin/pkg/llogs" -) - -func validEnvVars(t *testing.T) { - t.Setenv("ENV_APP_NAME", "guss") - t.Setenv("ENV_APP_ENV_TYPE", "local") - t.Setenv("ENV_APP_MASTER_KEY", "12345678901234567890123456789012") - t.Setenv("ENV_DB_USER_NAME", "usernamefoo") - t.Setenv("ENV_DB_USER_PASSWORD", "passwordfoo") - t.Setenv("ENV_DB_DATABASE_NAME", "dbnamefoo") - t.Setenv("ENV_DB_PORT", "5432") - t.Setenv("ENV_DB_HOST", "localhost") - t.Setenv("ENV_DB_SSL_MODE", "require") - t.Setenv("ENV_DB_TIMEZONE", "UTC") - t.Setenv("ENV_APP_LOG_LEVEL", "debug") - t.Setenv("ENV_APP_LOGS_DIR", "logs_%s.log") - t.Setenv("ENV_APP_LOGS_DATE_FORMAT", "2006_01_02") - t.Setenv("ENV_HTTP_HOST", "localhost") - t.Setenv("ENV_HTTP_PORT", "8080") - t.Setenv("ENV_SENTRY_DSN", "dsn") - t.Setenv("ENV_SENTRY_CSP", "csp") -} - -func TestMakeEnv(t *testing.T) { - validEnvVars(t) - - env := MakeEnv(pkg.GetDefaultValidator()) - - if env.App.Name != "guss" { - t.Fatalf("env not loaded") - } -} - -func TestIgnite(t *testing.T) { - content := "ENV_APP_NAME=guss\n" + - "ENV_APP_ENV_TYPE=local\n" + - "ENV_APP_MASTER_KEY=12345678901234567890123456789012\n" + - "ENV_DB_USER_NAME=usernamefoo\n" + - "ENV_DB_USER_PASSWORD=passwordfoo\n" + - "ENV_DB_DATABASE_NAME=dbnamefoo\n" + - "ENV_DB_PORT=5432\n" + - "ENV_DB_HOST=localhost\n" + - "ENV_DB_SSL_MODE=require\n" + - "ENV_DB_TIMEZONE=UTC\n" + - "ENV_APP_LOG_LEVEL=debug\n" + - "ENV_APP_LOGS_DIR=logs_%s.log\n" + - "ENV_APP_LOGS_DATE_FORMAT=2006_01_02\n" + - "ENV_HTTP_HOST=localhost\n" + - "ENV_HTTP_PORT=8080\n" + - "ENV_SENTRY_DSN=dsn\n" + - "ENV_SENTRY_CSP=csp\n" - - f, err := os.CreateTemp("", "envfile") - - if err != nil { - t.Fatalf("temp file err: %v", err) - } - - defer os.Remove(f.Name()) - f.WriteString(content) - f.Close() - - env := Ignite(f.Name(), pkg.GetDefaultValidator()) - - if env.Network.HttpPort != "8080" { - t.Fatalf("env not loaded") - } -} - -func TestAppBootNil(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Fatalf("expected panic") - } - }() - - var a *App - a.Boot() -} - -func TestAppHelpers(t *testing.T) { - app := &App{} - - mux := http.NewServeMux() - r := Router{Mux: mux} - - app.SetRouter(r) - - if app.GetMux() != mux { - t.Fatalf("mux not set") - } - - app.CloseLogs() - app.CloseDB() - - if app.GetEnv() != nil { - t.Fatalf("expected nil env") - } - - if app.GetDB() != nil { - t.Fatalf("expected nil db") - } -} - -func TestAppBootRoutes(t *testing.T) { - validEnvVars(t) - - env := MakeEnv(pkg.GetDefaultValidator()) - - key, err := auth.GenerateAESKey() - - if err != nil { - t.Fatalf("key err: %v", err) - } - - handler, err := auth.MakeTokensHandler(key) - - if err != nil { - t.Fatalf("handler err: %v", err) - } - - router := Router{ - Env: env, - Mux: http.NewServeMux(), - Pipeline: middleware.Pipeline{ - Env: env, - ApiKeys: &repository.ApiKeys{DB: &database.Connection{}}, - TokenHandler: handler, - }, - Db: &database.Connection{}, - } - - app := &App{} - - app.SetRouter(router) - - app.Boot() - - routes := []struct { - method string - path string - }{ - {"GET", "/profile"}, - {"GET", "/experience"}, - {"GET", "/projects"}, - {"GET", "/social"}, - {"GET", "/talks"}, - {"GET", "/education"}, - {"GET", "/recommendations"}, - {"POST", "/posts"}, - {"GET", "/posts/slug"}, - {"GET", "/categories"}, - } - - for _, rt := range routes { - req := httptest.NewRequest(rt.method, rt.path, nil) - h, pattern := app.GetMux().Handler(req) - - if pattern == "" || h == nil { - t.Fatalf("route missing %s %s", rt.method, rt.path) - } - } -} - -func TestMakeLogs(t *testing.T) { - dir, err := os.MkdirTemp("", "logdir") - - if err != nil { - t.Fatalf("tmpdir err: %v", err) - } - - validEnvVars(t) - t.Setenv("ENV_APP_LOGS_DIR", filepath.Join(dir, "log-%s.txt")) - - env := MakeEnv(pkg.GetDefaultValidator()) - - d := MakeLogs(env) - driver := *d - fl := driver.(llogs.FilesLogs) - - if !strings.HasPrefix(fl.DefaultPath(), dir) { - t.Fatalf("wrong log dir") - } - - if !fl.Close() { - t.Fatalf("close failed") - } -} - -func TestMakeDbConnectionPanic(t *testing.T) { - validEnvVars(t) - t.Setenv("ENV_DB_PORT", "1") - t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") - - env := MakeEnv(pkg.GetDefaultValidator()) - - defer func() { - if r := recover(); r == nil { - t.Fatalf("expected panic") - } - }() - - MakeDbConnection(env) -} - -func TestMakeAppPanic(t *testing.T) { - validEnvVars(t) - t.Setenv("ENV_DB_PORT", "1") - t.Setenv("ENV_APP_LOGS_DIR", "/tmp/log-%s.txt") - t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") - - env := MakeEnv(pkg.GetDefaultValidator()) - - defer func() { - if r := recover(); r == nil { - t.Fatalf("expected panic") - } - }() - - MakeApp(env, pkg.GetDefaultValidator()) -} - -func TestMakeSentry(t *testing.T) { - validEnvVars(t) - t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") - - env := MakeEnv(pkg.GetDefaultValidator()) - - s := MakeSentry(env) - - if s == nil || s.Handler == nil || s.Options == nil { - t.Fatalf("sentry setup failed") - } -} - -func TestCloseLogs(t *testing.T) { - dir, err := os.MkdirTemp("", "logdir") - - if err != nil { - t.Fatalf("tmpdir err: %v", err) - } - - validEnvVars(t) - t.Setenv("ENV_APP_LOGS_DIR", filepath.Join(dir, "log-%s.txt")) - t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") - - env := MakeEnv(pkg.GetDefaultValidator()) - - l := MakeLogs(env) - app := &App{logs: l} - - app.CloseLogs() -} - -func TestGetMuxNil(t *testing.T) { - app := &App{} - - if app.GetMux() != nil { - t.Fatalf("expected nil mux") - } -} diff --git a/boost/helpers.go b/boost/helpers.go deleted file mode 100644 index 33b8187a..00000000 --- a/boost/helpers.go +++ /dev/null @@ -1,53 +0,0 @@ -package boost - -import ( - "github.com/oullin/database" - "github.com/oullin/env" - baseHttp "net/http" -) - -func (a *App) SetRouter(router Router) { - a.router = &router -} - -func (a *App) CloseLogs() { - if a.logs == nil { - return - } - - driver := *a.logs - driver.Close() -} - -func (a *App) CloseDB() { - if a.db == nil { - return - } - - driver := *a.db - driver.Close() -} - -func (a *App) IsLocal() bool { - return a.env.App.IsLocal() -} - -func (a *App) IsProduction() bool { - return a.env.App.IsProduction() -} - -func (a *App) GetEnv() *env.Environment { - return a.env -} - -func (a *App) GetDB() *database.Connection { - return a.db -} - -func (a *App) GetMux() *baseHttp.ServeMux { - if a.router == nil { - return nil - } - - return a.router.Mux -} diff --git a/cli/main.go b/cli/main.go index beded669..2a1e235b 100644 --- a/cli/main.go +++ b/cli/main.go @@ -2,12 +2,12 @@ package main import ( "fmt" - "github.com/oullin/metal" "github.com/oullin/cli/accounts" "github.com/oullin/cli/panel" "github.com/oullin/cli/posts" "github.com/oullin/database" "github.com/oullin/env" + "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/oullin/pkg/auth" "github.com/oullin/pkg/cli" @@ -17,10 +17,10 @@ var environment *env.Environment var dbConn *database.Connection func init() { - secrets := metal.Ignite("./../.env", pkg.GetDefaultValidator()) + secrets := kernel.Ignite("./../.env", pkg.GetDefaultValidator()) environment = secrets - dbConn = metal.MakeDbConnection(environment) + dbConn = kernel.MakeDbConnection(environment) } func main() { diff --git a/database/seeder/main.go b/database/seeder/main.go index b269d625..55b421df 100644 --- a/database/seeder/main.go +++ b/database/seeder/main.go @@ -1,20 +1,21 @@ package main import ( - "github.com/oullin/metal" + "sync" + "time" + "github.com/oullin/database" "github.com/oullin/database/seeder/seeds" "github.com/oullin/env" + "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/oullin/pkg/cli" - "sync" - "time" ) var environment *env.Environment func init() { - secrets := metal.Ignite("./.env", pkg.GetDefaultValidator()) + secrets := kernel.Ignite("./.env", pkg.GetDefaultValidator()) environment = secrets } @@ -22,8 +23,8 @@ func init() { func main() { cli.ClearScreen() - dbConnection := metal.MakeDbConnection(environment) - logs := metal.MakeLogs(environment) + dbConnection := kernel.MakeDbConnection(environment) + logs := kernel.MakeLogs(environment) defer (*logs).Close() defer (*dbConnection).Close() diff --git a/main.go b/main.go index 823614d7..09e3daf6 100644 --- a/main.go +++ b/main.go @@ -3,19 +3,19 @@ package main import ( "fmt" _ "github.com/lib/pq" - "github.com/oullin/metal" + "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/rs/cors" "log/slog" baseHttp "net/http" ) -var app *metal.App +var app *kernel.App func init() { validate := pkg.GetDefaultValidator() - secrets := metal.Ignite("./.env", validate) - application, err := metal.MakeApp(secrets, validate) + secrets := kernel.Ignite("./.env", validate) + application, err := kernel.MakeApp(secrets, validate) if err != nil { panic(fmt.Sprintf("init: Error creating application: %s", err)) diff --git a/metal/app.go b/metal/app.go deleted file mode 100644 index 17e50d77..00000000 --- a/metal/app.go +++ /dev/null @@ -1,75 +0,0 @@ -package metal - -import ( - "fmt" - "github.com/oullin/database" - "github.com/oullin/database/repository" - "github.com/oullin/env" - "github.com/oullin/pkg" - "github.com/oullin/pkg/auth" - "github.com/oullin/pkg/http/middleware" - "github.com/oullin/pkg/llogs" - baseHttp "net/http" -) - -type App struct { - router *Router - sentry *pkg.Sentry - logs *llogs.Driver - validator *pkg.Validator - env *env.Environment - db *database.Connection -} - -func MakeApp(env *env.Environment, validator *pkg.Validator) (*App, error) { - tokenHandler, err := auth.MakeTokensHandler( - []byte(env.App.MasterKey), - ) - - if err != nil { - return nil, fmt.Errorf("bootstrapping error > could not create a token handler: %w", err) - } - - db := MakeDbConnection(env) - - app := App{ - env: env, - validator: validator, - logs: MakeLogs(env), - sentry: MakeSentry(env), - db: db, - } - - router := Router{ - Env: env, - Db: db, - Mux: baseHttp.NewServeMux(), - Pipeline: middleware.Pipeline{ - Env: env, - ApiKeys: &repository.ApiKeys{DB: db}, - TokenHandler: tokenHandler, - }, - } - - app.SetRouter(router) - - return &app, nil -} - -func (a *App) Boot() { - if a == nil || a.router == nil { - panic("bootstrapping error > Invalid setup") - } - - router := *a.router - - router.Profile() - router.Experience() - router.Projects() - router.Social() - router.Talks() - router.Education() - router.Recommendations() - router.Posts() - router.Categories() -} diff --git a/metal/factory.go b/metal/factory.go deleted file mode 100644 index c1b02439..00000000 --- a/metal/factory.go +++ /dev/null @@ -1,128 +0,0 @@ -package metal - -import ( - "github.com/getsentry/sentry-go" - sentryhttp "github.com/getsentry/sentry-go/http" - "github.com/oullin/database" - "github.com/oullin/env" - "github.com/oullin/pkg" - "github.com/oullin/pkg/llogs" - "log" - "strconv" - "time" -) - -func MakeSentry(env *env.Environment) *pkg.Sentry { - cOptions := sentry.ClientOptions{ - Dsn: env.Sentry.DSN, - Debug: true, - } - - if err := sentry.Init(cOptions); err != nil { - log.Fatalf("sentry.Init: %s", err) - } - - defer sentry.Flush(2 * time.Second) - - options := sentryhttp.Options{} - handler := sentryhttp.New(options) - - return &pkg.Sentry{ - Handler: handler, - Options: &options, - Env: env, - } -} - -func MakeDbConnection(env *env.Environment) *database.Connection { - dbConn, err := database.MakeConnection(env) - - if err != nil { - panic("Sql: error connecting to PostgreSQL: " + err.Error()) - } - - return dbConn -} - -func MakeLogs(env *env.Environment) *llogs.Driver { - lDriver, err := llogs.MakeFilesLogs(env) - - if err != nil { - panic("logs: error opening logs file: " + err.Error()) - } - - return &lDriver -} - -func MakeEnv(validate *pkg.Validator) *env.Environment { - errorSuffix := "Environment: " - - port, _ := strconv.Atoi(env.GetEnvVar("ENV_DB_PORT")) - - app := env.AppEnvironment{ - Name: env.GetEnvVar("ENV_APP_NAME"), - Type: env.GetEnvVar("ENV_APP_ENV_TYPE"), - MasterKey: env.GetEnvVar("ENV_APP_MASTER_KEY"), - } - - db := env.DBEnvironment{ - UserName: env.GetSecretOrEnv("pg_username", "ENV_DB_USER_NAME"), - UserPassword: env.GetSecretOrEnv("pg_password", "ENV_DB_USER_PASSWORD"), - DatabaseName: env.GetSecretOrEnv("pg_dbname", "ENV_DB_DATABASE_NAME"), - Port: port, - Host: env.GetEnvVar("ENV_DB_HOST"), - DriverName: database.DriverName, - SSLMode: env.GetEnvVar("ENV_DB_SSL_MODE"), - TimeZone: env.GetEnvVar("ENV_DB_TIMEZONE"), - } - - logsCreds := env.LogsEnvironment{ - Level: env.GetEnvVar("ENV_APP_LOG_LEVEL"), - Dir: env.GetEnvVar("ENV_APP_LOGS_DIR"), - DateFormat: env.GetEnvVar("ENV_APP_LOGS_DATE_FORMAT"), - } - - net := env.NetEnvironment{ - HttpHost: env.GetEnvVar("ENV_HTTP_HOST"), - HttpPort: env.GetEnvVar("ENV_HTTP_PORT"), - } - - sentryEnvironment := env.SentryEnvironment{ - DSN: env.GetEnvVar("ENV_SENTRY_DSN"), - CSP: env.GetEnvVar("ENV_SENTRY_CSP"), - } - - if _, err := validate.Rejects(app); err != nil { - panic(errorSuffix + "invalid [APP] model: " + validate.GetErrorsAsJson()) - } - - if _, err := validate.Rejects(db); err != nil { - panic(errorSuffix + "invalid [Sql] model: " + validate.GetErrorsAsJson()) - } - - if _, err := validate.Rejects(logsCreds); err != nil { - panic(errorSuffix + "invalid [logs Creds] model: " + validate.GetErrorsAsJson()) - } - - if _, err := validate.Rejects(net); err != nil { - panic(errorSuffix + "invalid [NETWORK] model: " + validate.GetErrorsAsJson()) - } - - if _, err := validate.Rejects(sentryEnvironment); err != nil { - panic(errorSuffix + "invalid [SENTRY] model: " + validate.GetErrorsAsJson()) - } - - blog := &env.Environment{ - App: app, - DB: db, - Logs: logsCreds, - Network: net, - Sentry: sentryEnvironment, - } - - if _, err := validate.Rejects(blog); err != nil { - panic(errorSuffix + "invalid blog [ENVIRONMENT] model: " + validate.GetErrorsAsJson()) - } - - return blog -} diff --git a/metal/ignite.go b/metal/ignite.go deleted file mode 100644 index b998914a..00000000 --- a/metal/ignite.go +++ /dev/null @@ -1,15 +0,0 @@ -package metal - -import ( - "github.com/joho/godotenv" - "github.com/oullin/env" - "github.com/oullin/pkg" -) - -func Ignite(envPath string, validate *pkg.Validator) *env.Environment { - if err := godotenv.Load(envPath); err != nil { - panic("failed to read the .env file/values: " + err.Error()) - } - - return MakeEnv(validate) -} diff --git a/boost/app.go b/metal/kernel/app.go similarity index 98% rename from boost/app.go rename to metal/kernel/app.go index 34f760c9..4aef19bf 100644 --- a/boost/app.go +++ b/metal/kernel/app.go @@ -1,7 +1,9 @@ -package boost +package kernel import ( "fmt" + baseHttp "net/http" + "github.com/oullin/database" "github.com/oullin/database/repository" "github.com/oullin/env" @@ -9,7 +11,6 @@ import ( "github.com/oullin/pkg/auth" "github.com/oullin/pkg/http/middleware" "github.com/oullin/pkg/llogs" - baseHttp "net/http" ) type App struct { diff --git a/boost/factory.go b/metal/kernel/factory.go similarity index 99% rename from boost/factory.go rename to metal/kernel/factory.go index e173042a..1c3e03fa 100644 --- a/boost/factory.go +++ b/metal/kernel/factory.go @@ -1,15 +1,16 @@ -package boost +package kernel import ( + "log" + "strconv" + "time" + "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/oullin/database" "github.com/oullin/env" "github.com/oullin/pkg" "github.com/oullin/pkg/llogs" - "log" - "strconv" - "time" ) func MakeSentry(env *env.Environment) *pkg.Sentry { diff --git a/metal/helpers.go b/metal/kernel/helpers.go similarity index 97% rename from metal/helpers.go rename to metal/kernel/helpers.go index f6c60e72..a65b1a69 100644 --- a/metal/helpers.go +++ b/metal/kernel/helpers.go @@ -1,4 +1,4 @@ -package metal +package kernel import ( "github.com/oullin/database" diff --git a/boost/ignite.go b/metal/kernel/ignite.go similarity index 95% rename from boost/ignite.go rename to metal/kernel/ignite.go index c3e72402..264479e7 100644 --- a/boost/ignite.go +++ b/metal/kernel/ignite.go @@ -1,4 +1,4 @@ -package boost +package kernel import ( "github.com/joho/godotenv" diff --git a/metal/metal_test.go b/metal/kernel/kernel_test.go similarity index 99% rename from metal/metal_test.go rename to metal/kernel/kernel_test.go index c7d28a5f..676e2cca 100644 --- a/metal/metal_test.go +++ b/metal/kernel/kernel_test.go @@ -1,4 +1,4 @@ -package metal +package kernel import ( "net/http" diff --git a/boost/router.go b/metal/kernel/router.go similarity index 99% rename from boost/router.go rename to metal/kernel/router.go index 95c88a91..8c4c2b7d 100644 --- a/boost/router.go +++ b/metal/kernel/router.go @@ -1,4 +1,4 @@ -package boost +package kernel import ( "github.com/oullin/database" diff --git a/metal/router.go b/metal/router.go deleted file mode 100644 index 9339dfdb..00000000 --- a/metal/router.go +++ /dev/null @@ -1,122 +0,0 @@ -package metal - -import ( - "github.com/oullin/database" - "github.com/oullin/database/repository" - "github.com/oullin/env" - "github.com/oullin/handler" - "github.com/oullin/pkg/http" - "github.com/oullin/pkg/http/middleware" - baseHttp "net/http" -) - -type Router struct { - Env *env.Environment - Mux *baseHttp.ServeMux - Pipeline middleware.Pipeline - Db *database.Connection -} - -func (r *Router) PipelineFor(apiHandler http.ApiHandler) baseHttp.HandlerFunc { - tokenMiddleware := middleware.MakeTokenMiddleware( - r.Pipeline.TokenHandler, - r.Pipeline.ApiKeys, - ) - - return http.MakeApiHandler( - r.Pipeline.Chain( - apiHandler, - tokenMiddleware.Handle, - ), - ) -} - -func (r *Router) Posts() { - repo := repository.Posts{DB: r.Db} - abstract := handler.MakePostsHandler(&repo) - - index := r.PipelineFor(abstract.Index) - show := r.PipelineFor(abstract.Show) - - r.Mux.HandleFunc("POST /posts", index) - r.Mux.HandleFunc("GET /posts/{slug}", show) -} - -func (r *Router) Categories() { - repo := repository.Categories{DB: r.Db} - abstract := handler.MakeCategoriesHandler(&repo) - - index := r.PipelineFor(abstract.Index) - - r.Mux.HandleFunc("GET /categories", index) -} - -func (r *Router) Profile() { - abstract := handler.MakeProfileHandler("./storage/fixture/profile.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /profile", resolver) -} - -func (r *Router) Experience() { - abstract := handler.MakeExperienceHandler("./storage/fixture/experience.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /experience", resolver) -} - -func (r *Router) Projects() { - abstract := handler.MakeProjectsHandler("./storage/fixture/projects.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /projects", resolver) -} - -func (r *Router) Social() { - abstract := handler.MakeSocialHandler("./storage/fixture/social.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /social", resolver) -} - -func (r *Router) Talks() { - abstract := handler.MakeTalksHandler("./storage/fixture/talks.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /talks", resolver) -} - -func (r *Router) Education() { - abstract := handler.MakeEducationHandler("./storage/fixture/education.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /education", resolver) -} - -func (r *Router) Recommendations() { - abstract := handler.MakeRecommendationsHandler("./storage/fixture/recommendations.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /recommendations", resolver) -} From b342508cdc819feab851cf80d81f5410428bd662 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 14:47:22 +0800 Subject: [PATCH 03/21] wip --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 26baa423..a2e56387 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,10 +38,10 @@ jobs: go test -coverpkg=./... ./pkg/... -coverprofile=coverage-pkg-${{ matrix.go-version }}.out go tool cover -func=coverage-pkg-${{ matrix.go-version }}.out | tail -n 1 - - name: Run boost & env tests + - name: Run kernel & env tests run: | - go test -coverpkg=./... ./boost ./env -coverprofile=coverage-boost-env-${{ matrix.go-version }}.out - go tool cover -func=coverage-boost-env-${{ matrix.go-version }}.out | tail -n 1 + go test -coverpkg=./... ./metal/kernel ./env -coverprofile=coverage-metal-env-${{ matrix.go-version }}.out + go tool cover -func=coverage-metal-env-${{ matrix.go-version }}.out | tail -n 1 - name: Run handlers tests run: | From f79366a6f68ac5ed2483472039d3588a40011e00 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 14:49:57 +0800 Subject: [PATCH 04/21] version --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a2e56387..18480b37 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: ['1.24.5', '1.24.4'] + go-version: ['1.24.6', '1.24'] steps: - uses: actions/setup-go@v5 From f68ca5813d8f3f996b0d32c0ede42b247e7b25ea Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 14:51:50 +0800 Subject: [PATCH 05/21] wip --- .github/workflows/tests.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18480b37..37c66dda 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,12 +2,15 @@ name: Tests on: pull_request: - types: [ready_for_review, synchronize, labeled] + types: [opened, reopened, ready_for_review, synchronize, labeled] + push: + branches: [main, master] jobs: test: - if: github.event.pull_request.draft == false || - (github.event.action == 'labeled' && github.event.label.name == 'testing') + if: (github.event_name == 'push') || + (github.event_name == 'pull_request' && (github.event.pull_request.draft == false || + (github.event.action == 'labeled' && github.event.label.name == 'testing'))) runs-on: ubuntu-latest strategy: matrix: From 0adba747bcb77660f9fdae7aeb7e232b04d0b632 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 14:59:51 +0800 Subject: [PATCH 06/21] fix fallen tests --- pkg/llogs/files_logs.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/llogs/files_logs.go b/pkg/llogs/files_logs.go index ec9ff41d..44dadecd 100644 --- a/pkg/llogs/files_logs.go +++ b/pkg/llogs/files_logs.go @@ -5,6 +5,7 @@ import ( "github.com/oullin/env" "log/slog" "os" + "strings" "time" ) @@ -20,6 +21,13 @@ func MakeFilesLogs(env *env.Environment) (Driver, error) { manager.env = env manager.path = manager.DefaultPath() + + // Create directory if it doesn't exist + dir := manager.path[:strings.LastIndex(manager.path, "/")] + if err := os.MkdirAll(dir, 0755); err != nil { + return FilesLogs{}, fmt.Errorf("failed to create log directory: %w", err) + } + resource, err := os.OpenFile(manager.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { From 192b37483df5117aa07968b6ac6b9fa0aca00bf1 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 15:03:32 +0800 Subject: [PATCH 07/21] versions --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 37c66dda..0ebc5cac 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: ['1.24.6', '1.24'] + go-version: ['1.24.6', '1.24.5'] steps: - uses: actions/setup-go@v5 From c7d63ffc420955780d3710f771e69f161f59d1e0 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 15:12:48 +0800 Subject: [PATCH 08/21] move cli package --- Makefile | 4 +++- config/makefile/cli.mk | 4 ++++ {cli => metal/cli}/accounts/factory.go | 0 {cli => metal/cli}/accounts/factory_test.go | 2 +- {cli => metal/cli}/accounts/handler.go | 0 {cli => metal/cli}/accounts/handler_test.go | 2 +- {cli => metal/cli}/clitest/helpers.go | 0 {cli => metal/cli}/clitest/helpers_test.go | 0 {cli => metal/cli}/main.go | 8 ++++---- {cli => metal/cli}/panel/menu.go | 2 +- {cli => metal/cli}/panel/menu_test.go | 0 {cli => metal/cli}/posts/factory.go | 0 {cli => metal/cli}/posts/handler.go | 0 {cli => metal/cli}/posts/handler_test.go | 2 +- {cli => metal/cli}/posts/input.go | 0 15 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 config/makefile/cli.mk rename {cli => metal/cli}/accounts/factory.go (100%) rename {cli => metal/cli}/accounts/factory_test.go (95%) rename {cli => metal/cli}/accounts/handler.go (100%) rename {cli => metal/cli}/accounts/handler_test.go (96%) rename {cli => metal/cli}/clitest/helpers.go (100%) rename {cli => metal/cli}/clitest/helpers_test.go (100%) rename {cli => metal/cli}/main.go (94%) rename {cli => metal/cli}/panel/menu.go (99%) rename {cli => metal/cli}/panel/menu_test.go (100%) rename {cli => metal/cli}/posts/factory.go (100%) rename {cli => metal/cli}/posts/handler.go (100%) rename {cli => metal/cli}/posts/handler_test.go (99%) rename {cli => metal/cli}/posts/input.go (100%) diff --git a/Makefile b/Makefile index 280c7585..dac565bf 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,7 @@ include ./config/makefile/app.mk include ./config/makefile/logs.mk include ./config/makefile/build.mk include ./config/makefile/infra.mk +include ./config/makefile/cli.mk # -------------------------------------------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------------------------------------------- # @@ -53,7 +54,8 @@ help: @printf " $(BOLD)$(GREEN)audit$(NC) : Run code audits and checks.\n" @printf " $(BOLD)$(GREEN)watch$(NC) : Start a file watcher process.\n" @printf " $(BOLD)$(GREEN)format$(NC) : Automatically format code.\n" - @printf " $(BOLD)$(GREEN)run-cli$(NC) : Run the API's cli interface.\n\n" + @printf " $(BOLD)$(GREEN)run-cli$(NC) : Run the API's cli interface.\n" + @printf " $(BOLD)$(GREEN)run-main$(NC) : Run the main application.\n\n" @printf "$(BOLD)$(BLUE)Build Commands:$(NC)\n" @printf " $(BOLD)$(GREEN)build-local$(NC) : Build the main application for development.\n" diff --git a/config/makefile/cli.mk b/config/makefile/cli.mk new file mode 100644 index 00000000..53e771c0 --- /dev/null +++ b/config/makefile/cli.mk @@ -0,0 +1,4 @@ +.PHONY: run-cli + +run-cli: + cd metal/cli && go run main.go diff --git a/cli/accounts/factory.go b/metal/cli/accounts/factory.go similarity index 100% rename from cli/accounts/factory.go rename to metal/cli/accounts/factory.go diff --git a/cli/accounts/factory_test.go b/metal/cli/accounts/factory_test.go similarity index 95% rename from cli/accounts/factory_test.go rename to metal/cli/accounts/factory_test.go index db5a87e8..8bf58a68 100644 --- a/cli/accounts/factory_test.go +++ b/metal/cli/accounts/factory_test.go @@ -1,7 +1,7 @@ package accounts import ( - "github.com/oullin/cli/clitest" + "github.com/oullin/metal/cli/clitest" "testing" "github.com/oullin/database" diff --git a/cli/accounts/handler.go b/metal/cli/accounts/handler.go similarity index 100% rename from cli/accounts/handler.go rename to metal/cli/accounts/handler.go diff --git a/cli/accounts/handler_test.go b/metal/cli/accounts/handler_test.go similarity index 96% rename from cli/accounts/handler_test.go rename to metal/cli/accounts/handler_test.go index 5d54e302..5c2222b4 100644 --- a/cli/accounts/handler_test.go +++ b/metal/cli/accounts/handler_test.go @@ -3,7 +3,7 @@ package accounts import ( "testing" - "github.com/oullin/cli/clitest" + "github.com/oullin/metal/cli/clitest" "github.com/oullin/database" ) diff --git a/cli/clitest/helpers.go b/metal/cli/clitest/helpers.go similarity index 100% rename from cli/clitest/helpers.go rename to metal/cli/clitest/helpers.go diff --git a/cli/clitest/helpers_test.go b/metal/cli/clitest/helpers_test.go similarity index 100% rename from cli/clitest/helpers_test.go rename to metal/cli/clitest/helpers_test.go diff --git a/cli/main.go b/metal/cli/main.go similarity index 94% rename from cli/main.go rename to metal/cli/main.go index 2a1e235b..84d45837 100644 --- a/cli/main.go +++ b/metal/cli/main.go @@ -2,9 +2,9 @@ package main import ( "fmt" - "github.com/oullin/cli/accounts" - "github.com/oullin/cli/panel" - "github.com/oullin/cli/posts" + "github.com/oullin/metal/cli/accounts" + "github.com/oullin/metal/cli/panel" + "github.com/oullin/metal/cli/posts" "github.com/oullin/database" "github.com/oullin/env" "github.com/oullin/metal/kernel" @@ -17,7 +17,7 @@ var environment *env.Environment var dbConn *database.Connection func init() { - secrets := kernel.Ignite("./../.env", pkg.GetDefaultValidator()) + secrets := kernel.Ignite("./../../.env", pkg.GetDefaultValidator()) environment = secrets dbConn = kernel.MakeDbConnection(environment) diff --git a/cli/panel/menu.go b/metal/cli/panel/menu.go similarity index 99% rename from cli/panel/menu.go rename to metal/cli/panel/menu.go index 78d7e42c..43c12315 100644 --- a/cli/panel/menu.go +++ b/metal/cli/panel/menu.go @@ -3,7 +3,7 @@ package panel import ( "bufio" "fmt" - "github.com/oullin/cli/posts" + "github.com/oullin/metal/cli/posts" "github.com/oullin/pkg" "github.com/oullin/pkg/auth" "github.com/oullin/pkg/cli" diff --git a/cli/panel/menu_test.go b/metal/cli/panel/menu_test.go similarity index 100% rename from cli/panel/menu_test.go rename to metal/cli/panel/menu_test.go diff --git a/cli/posts/factory.go b/metal/cli/posts/factory.go similarity index 100% rename from cli/posts/factory.go rename to metal/cli/posts/factory.go diff --git a/cli/posts/handler.go b/metal/cli/posts/handler.go similarity index 100% rename from cli/posts/handler.go rename to metal/cli/posts/handler.go diff --git a/cli/posts/handler_test.go b/metal/cli/posts/handler_test.go similarity index 99% rename from cli/posts/handler_test.go rename to metal/cli/posts/handler_test.go index 67567e84..eda09840 100644 --- a/cli/posts/handler_test.go +++ b/metal/cli/posts/handler_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/google/uuid" - "github.com/oullin/cli/clitest" + "github.com/oullin/metal/cli/clitest" "github.com/oullin/database" "github.com/oullin/pkg" "github.com/oullin/pkg/markdown" diff --git a/cli/posts/input.go b/metal/cli/posts/input.go similarity index 100% rename from cli/posts/input.go rename to metal/cli/posts/input.go From 1242f1234cd3b7cc31d840ed52233094bca9af86 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 15:26:46 +0800 Subject: [PATCH 09/21] env package --- Makefile | 1 + config/makefile/app.mk | 5 ++++- database/connection.go | 2 +- database/connection_test.go | 2 +- database/repository/repository_test.go | 2 +- database/repository/users.go | 2 +- database/seeder/main.go | 2 +- database/seeder/seeds/factory.go | 2 +- database/seeder/seeds/seeder_test.go | 2 +- database/truncate.go | 2 +- handler/tests/db.go | 2 +- metal/cli/accounts/factory.go | 2 +- metal/cli/clitest/helpers.go | 2 +- metal/cli/main.go | 2 +- {env => metal/env}/app.go | 0 {env => metal/env}/db.go | 0 {env => metal/env}/env.go | 0 {env => metal/env}/env_test.go | 0 {env => metal/env}/logs.go | 0 {env => metal/env}/network.go | 0 {env => metal/env}/sentry.go | 0 metal/kernel/app.go | 2 +- metal/kernel/factory.go | 2 +- metal/kernel/helpers.go | 2 +- metal/kernel/ignite.go | 2 +- metal/kernel/router.go | 2 +- pkg/http/middleware/pipeline.go | 2 +- pkg/llogs/files_logs.go | 2 +- pkg/llogs/files_logs_test.go | 2 +- pkg/media/media_test.go | 13 ++++++++++--- pkg/sentry.go | 2 +- 31 files changed, 36 insertions(+), 25 deletions(-) rename {env => metal/env}/app.go (100%) rename {env => metal/env}/db.go (100%) rename {env => metal/env}/env.go (100%) rename {env => metal/env}/env_test.go (100%) rename {env => metal/env}/logs.go (100%) rename {env => metal/env}/network.go (100%) rename {env => metal/env}/sentry.go (100%) diff --git a/Makefile b/Makefile index dac565bf..b52ed3b9 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,7 @@ help: @printf " $(BOLD)$(GREEN)format$(NC) : Automatically format code.\n" @printf " $(BOLD)$(GREEN)run-cli$(NC) : Run the API's cli interface.\n" @printf " $(BOLD)$(GREEN)run-main$(NC) : Run the main application.\n\n" + @printf " $(BOLD)$(GREEN)test-all$(NC) : Run all the application tests.\n\n" @printf "$(BOLD)$(BLUE)Build Commands:$(NC)\n" @printf " $(BOLD)$(GREEN)build-local$(NC) : Build the main application for development.\n" diff --git a/config/makefile/app.mk b/config/makefile/app.mk index 4bca6116..b52d5714 100644 --- a/config/makefile/app.mk +++ b/config/makefile/app.mk @@ -1,4 +1,4 @@ -.PHONY: fresh destroy audit watch format run-cli validate-caddy +.PHONY: fresh destroy audit watch format run-cli validate-caddy test-all APP_CADDY_CONFIG_PROD_FILE ?= caddy/Caddyfile.prod APP_CADDY_CONFIG_LOCAL_FILE ?= caddy/Caddyfile.local @@ -56,6 +56,9 @@ run-cli: DB_SECRET_DBNAME="$(DB_SECRET_DBNAME)" \ docker compose run --rm api-runner go run ./cli/main.go +test-all: + go test ./... + # --- Mac: # Needs to be locally installed: https://formulae.brew.sh/formula/caddy validate-caddy: diff --git a/database/connection.go b/database/connection.go index 2550f5d2..4a5cf7b8 100644 --- a/database/connection.go +++ b/database/connection.go @@ -3,7 +3,7 @@ package database import ( "database/sql" "fmt" - "github.com/oullin/env" + "github.com/oullin/metal/env" "gorm.io/driver/postgres" "gorm.io/gorm" "log/slog" diff --git a/database/connection_test.go b/database/connection_test.go index f6fab3ad..06aa475b 100644 --- a/database/connection_test.go +++ b/database/connection_test.go @@ -11,7 +11,7 @@ import ( "github.com/oullin/database" "github.com/oullin/database/repository" - "github.com/oullin/env" + "github.com/oullin/metal/env" ) func TestApiKeysWithTestContainer(t *testing.T) { diff --git a/database/repository/repository_test.go b/database/repository/repository_test.go index a72c6142..d01cdc20 100644 --- a/database/repository/repository_test.go +++ b/database/repository/repository_test.go @@ -12,7 +12,7 @@ import ( "github.com/oullin/database" "github.com/oullin/database/repository" - "github.com/oullin/env" + "github.com/oullin/metal/env" ) func setupDB(t *testing.T) *database.Connection { diff --git a/database/repository/users.go b/database/repository/users.go index 50502e0e..32c1c7f3 100644 --- a/database/repository/users.go +++ b/database/repository/users.go @@ -2,7 +2,7 @@ package repository import ( "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/pkg/gorm" "strings" ) diff --git a/database/seeder/main.go b/database/seeder/main.go index 55b421df..395da0ac 100644 --- a/database/seeder/main.go +++ b/database/seeder/main.go @@ -6,7 +6,7 @@ import ( "github.com/oullin/database" "github.com/oullin/database/seeder/seeds" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/oullin/pkg/cli" diff --git a/database/seeder/seeds/factory.go b/database/seeder/seeds/factory.go index 97acb9cd..dcaddc26 100644 --- a/database/seeder/seeds/factory.go +++ b/database/seeder/seeds/factory.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/google/uuid" "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" "math/rand" "time" ) diff --git a/database/seeder/seeds/seeder_test.go b/database/seeder/seeds/seeder_test.go index a5bf6152..2ada2684 100644 --- a/database/seeder/seeds/seeder_test.go +++ b/database/seeder/seeds/seeder_test.go @@ -10,7 +10,7 @@ import ( "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" ) func testConnection(t *testing.T, e *env.Environment) *database.Connection { diff --git a/database/truncate.go b/database/truncate.go index 32293738..30a3ca09 100644 --- a/database/truncate.go +++ b/database/truncate.go @@ -3,7 +3,7 @@ package database import ( "errors" "fmt" - "github.com/oullin/env" + "github.com/oullin/metal/env" ) type Truncate struct { diff --git a/handler/tests/db.go b/handler/tests/db.go index fe9ec89c..1e356825 100644 --- a/handler/tests/db.go +++ b/handler/tests/db.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" ) diff --git a/metal/cli/accounts/factory.go b/metal/cli/accounts/factory.go index 8f9752fb..e340b637 100644 --- a/metal/cli/accounts/factory.go +++ b/metal/cli/accounts/factory.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/oullin/database" "github.com/oullin/database/repository" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/pkg/auth" ) diff --git a/metal/cli/clitest/helpers.go b/metal/cli/clitest/helpers.go index e21e56a7..21c41581 100644 --- a/metal/cli/clitest/helpers.go +++ b/metal/cli/clitest/helpers.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" ) diff --git a/metal/cli/main.go b/metal/cli/main.go index 84d45837..75e7bf10 100644 --- a/metal/cli/main.go +++ b/metal/cli/main.go @@ -6,7 +6,7 @@ import ( "github.com/oullin/metal/cli/panel" "github.com/oullin/metal/cli/posts" "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/oullin/pkg/auth" diff --git a/env/app.go b/metal/env/app.go similarity index 100% rename from env/app.go rename to metal/env/app.go diff --git a/env/db.go b/metal/env/db.go similarity index 100% rename from env/db.go rename to metal/env/db.go diff --git a/env/env.go b/metal/env/env.go similarity index 100% rename from env/env.go rename to metal/env/env.go diff --git a/env/env_test.go b/metal/env/env_test.go similarity index 100% rename from env/env_test.go rename to metal/env/env_test.go diff --git a/env/logs.go b/metal/env/logs.go similarity index 100% rename from env/logs.go rename to metal/env/logs.go diff --git a/env/network.go b/metal/env/network.go similarity index 100% rename from env/network.go rename to metal/env/network.go diff --git a/env/sentry.go b/metal/env/sentry.go similarity index 100% rename from env/sentry.go rename to metal/env/sentry.go diff --git a/metal/kernel/app.go b/metal/kernel/app.go index 4aef19bf..31de48b5 100644 --- a/metal/kernel/app.go +++ b/metal/kernel/app.go @@ -6,7 +6,7 @@ import ( "github.com/oullin/database" "github.com/oullin/database/repository" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/pkg" "github.com/oullin/pkg/auth" "github.com/oullin/pkg/http/middleware" diff --git a/metal/kernel/factory.go b/metal/kernel/factory.go index 1c3e03fa..6b4dbcda 100644 --- a/metal/kernel/factory.go +++ b/metal/kernel/factory.go @@ -8,7 +8,7 @@ import ( "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/pkg" "github.com/oullin/pkg/llogs" ) diff --git a/metal/kernel/helpers.go b/metal/kernel/helpers.go index a65b1a69..246733fc 100644 --- a/metal/kernel/helpers.go +++ b/metal/kernel/helpers.go @@ -2,7 +2,7 @@ package kernel import ( "github.com/oullin/database" - "github.com/oullin/env" + "github.com/oullin/metal/env" baseHttp "net/http" ) diff --git a/metal/kernel/ignite.go b/metal/kernel/ignite.go index 264479e7..6bf2f8a8 100644 --- a/metal/kernel/ignite.go +++ b/metal/kernel/ignite.go @@ -2,7 +2,7 @@ package kernel import ( "github.com/joho/godotenv" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/pkg" ) diff --git a/metal/kernel/router.go b/metal/kernel/router.go index 8c4c2b7d..3d827eb6 100644 --- a/metal/kernel/router.go +++ b/metal/kernel/router.go @@ -3,7 +3,7 @@ package kernel import ( "github.com/oullin/database" "github.com/oullin/database/repository" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/handler" "github.com/oullin/pkg/http" "github.com/oullin/pkg/http/middleware" diff --git a/pkg/http/middleware/pipeline.go b/pkg/http/middleware/pipeline.go index 505a9a1b..81a9f5d4 100644 --- a/pkg/http/middleware/pipeline.go +++ b/pkg/http/middleware/pipeline.go @@ -2,7 +2,7 @@ package middleware import ( "github.com/oullin/database/repository" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/pkg/auth" "github.com/oullin/pkg/http" ) diff --git a/pkg/llogs/files_logs.go b/pkg/llogs/files_logs.go index 44dadecd..8fa4257b 100644 --- a/pkg/llogs/files_logs.go +++ b/pkg/llogs/files_logs.go @@ -2,7 +2,7 @@ package llogs import ( "fmt" - "github.com/oullin/env" + "github.com/oullin/metal/env" "log/slog" "os" "strings" diff --git a/pkg/llogs/files_logs_test.go b/pkg/llogs/files_logs_test.go index 2b769bce..03489568 100644 --- a/pkg/llogs/files_logs_test.go +++ b/pkg/llogs/files_logs_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/oullin/env" + "github.com/oullin/metal/env" ) func TestFilesLogs(t *testing.T) { diff --git a/pkg/media/media_test.go b/pkg/media/media_test.go index a3233322..d2c32ceb 100644 --- a/pkg/media/media_test.go +++ b/pkg/media/media_test.go @@ -95,11 +95,18 @@ func TestGetPostsImagesDir(t *testing.T) { } func TestGetStorageDir(t *testing.T) { - dir := setupTempDir(t) + setupTempDir(t) p := GetStorageDir() - if !strings.HasPrefix(p, dir) { - t.Fatalf("unexpected storage dir") + // Get the current working directory + dir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + expected := filepath.Join(dir, StorageDir) + if p != expected { + t.Fatalf("unexpected storage dir, got: %s, want: %s", p, expected) } } diff --git a/pkg/sentry.go b/pkg/sentry.go index d34f4956..f083486c 100644 --- a/pkg/sentry.go +++ b/pkg/sentry.go @@ -2,7 +2,7 @@ package pkg import ( sentryhttp "github.com/getsentry/sentry-go/http" - "github.com/oullin/env" + "github.com/oullin/metal/env" ) type Sentry struct { From f3299324b03e78ce883035ed3a4123ae15799530 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 15:35:58 +0800 Subject: [PATCH 10/21] flow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0ebc5cac..8ded7528 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -43,7 +43,7 @@ jobs: - name: Run kernel & env tests run: | - go test -coverpkg=./... ./metal/kernel ./env -coverprofile=coverage-metal-env-${{ matrix.go-version }}.out + go test -coverpkg=./... ./metal/kernel ./metal/env -coverprofile=coverage-metal-env-${{ matrix.go-version }}.out go tool cover -func=coverage-metal-env-${{ matrix.go-version }}.out | tail -n 1 - name: Run handlers tests From 441f0793531689445cbed27e2a993bfd7e4faeb5 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 15:38:50 +0800 Subject: [PATCH 11/21] wip --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8ded7528..7eb67d3c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,7 @@ jobs: - name: Run CLI tests run: | - go test -coverpkg=./... ./cli/... -coverprofile=coverage-cli-${{ matrix.go-version }}.out + go test -coverpkg=./... ./metal/cli/... -coverprofile=coverage-cli-${{ matrix.go-version }}.out go tool cover -func=coverage-cli-${{ matrix.go-version }}.out | tail -n 1 - name: Merge coverage reports From 1471f48f672539ce058ede1183bc3a3434ec48e0 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:07:11 +0800 Subject: [PATCH 12/21] bugs fix --- .github/workflows/gofmt.yml | 6 ++++ database/seeder/main.go | 2 +- main.go | 3 ++ metal/kernel/app.go | 2 +- metal/kernel/factory.go | 12 +++---- metal/kernel/helpers.go | 9 +++-- metal/kernel/kernel_test.go | 50 +++++++++++++++++++------- metal/kernel/router.go | 71 +++++++++++-------------------------- 8 files changed, 79 insertions(+), 76 deletions(-) diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index 852a259e..99bf9982 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -27,3 +27,9 @@ jobs: with: commit_message: apply coding style fixes commit_options: '--no-verify' + file_pattern: . + repository: . + commit_user_name: GitHub Actions + commit_user_email: actions@github.com + commit_author: gocanto + branch: ${{ github.head_ref }} diff --git a/database/seeder/main.go b/database/seeder/main.go index 395da0ac..154ea5b7 100644 --- a/database/seeder/main.go +++ b/database/seeder/main.go @@ -26,7 +26,7 @@ func main() { dbConnection := kernel.MakeDbConnection(environment) logs := kernel.MakeLogs(environment) - defer (*logs).Close() + defer logs.Close() defer (*dbConnection).Close() // [1] --- Create the Seeder Runner. diff --git a/main.go b/main.go index 09e3daf6..c9d57617 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,13 @@ package main import ( "fmt" _ "github.com/lib/pq" + "github.com/getsentry/sentry-go" "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/rs/cors" "log/slog" baseHttp "net/http" + "time" ) var app *kernel.App @@ -27,6 +29,7 @@ func init() { func main() { defer app.CloseDB() defer app.CloseLogs() + defer sentry.Flush(2 * time.Second) app.Boot() diff --git a/metal/kernel/app.go b/metal/kernel/app.go index 31de48b5..b3d20138 100644 --- a/metal/kernel/app.go +++ b/metal/kernel/app.go @@ -16,7 +16,7 @@ import ( type App struct { router *Router sentry *pkg.Sentry - logs *llogs.Driver + logs llogs.Driver validator *pkg.Validator env *env.Environment db *database.Connection diff --git a/metal/kernel/factory.go b/metal/kernel/factory.go index 6b4dbcda..34ed096e 100644 --- a/metal/kernel/factory.go +++ b/metal/kernel/factory.go @@ -3,7 +3,6 @@ package kernel import ( "log" "strconv" - "time" "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" @@ -23,8 +22,6 @@ func MakeSentry(env *env.Environment) *pkg.Sentry { log.Fatalf("sentry.Init: %s", err) } - defer sentry.Flush(2 * time.Second) - options := sentryhttp.Options{} handler := sentryhttp.New(options) @@ -45,20 +42,23 @@ func MakeDbConnection(env *env.Environment) *database.Connection { return dbConn } -func MakeLogs(env *env.Environment) *llogs.Driver { +func MakeLogs(env *env.Environment) llogs.Driver { lDriver, err := llogs.MakeFilesLogs(env) if err != nil { panic("logs: error opening logs file: " + err.Error()) } - return &lDriver + return lDriver } func MakeEnv(validate *pkg.Validator) *env.Environment { errorSuffix := "Environment: " - port, _ := strconv.Atoi(env.GetEnvVar("ENV_DB_PORT")) + port, err := strconv.Atoi(env.GetEnvVar("ENV_DB_PORT")) + if err != nil { + panic(errorSuffix + "invalid value for ENV_DB_PORT: " + err.Error()) + } app := env.AppEnvironment{ Name: env.GetEnvVar("ENV_APP_NAME"), diff --git a/metal/kernel/helpers.go b/metal/kernel/helpers.go index 246733fc..a5bc1565 100644 --- a/metal/kernel/helpers.go +++ b/metal/kernel/helpers.go @@ -1,9 +1,10 @@ package kernel import ( + baseHttp "net/http" + "github.com/oullin/database" "github.com/oullin/metal/env" - baseHttp "net/http" ) func (a *App) SetRouter(router Router) { @@ -15,8 +16,7 @@ func (a *App) CloseLogs() { return } - driver := *a.logs - driver.Close() + a.logs.Close() } func (a *App) CloseDB() { @@ -24,8 +24,7 @@ func (a *App) CloseDB() { return } - driver := *a.db - driver.Close() + a.db.Close() } func (a *App) IsLocal() bool { diff --git a/metal/kernel/kernel_test.go b/metal/kernel/kernel_test.go index 676e2cca..43b8f71c 100644 --- a/metal/kernel/kernel_test.go +++ b/metal/kernel/kernel_test.go @@ -177,24 +177,25 @@ func TestAppBootRoutes(t *testing.T) { } func TestMakeLogs(t *testing.T) { - // Create a temporary directory for logs - logDir := "/tmp/logs" - err := os.MkdirAll(logDir, 0755) + // Create a temporary directory with a lowercase path + tempDir := getLowerTempDir(t) + // Ensure the directory exists + err := os.MkdirAll(tempDir, 0755) if err != nil { t.Fatalf("failed to create log directory: %v", err) } - defer os.RemoveAll(logDir) // Clean up after test + // Clean up after tests + defer os.RemoveAll(tempDir) validEnvVars(t) - t.Setenv("ENV_APP_LOGS_DIR", logDir+"/log-%s.txt") + t.Setenv("ENV_APP_LOGS_DIR", tempDir+"/log-%s.txt") env := MakeEnv(pkg.GetDefaultValidator()) - d := MakeLogs(env) - driver := *d + driver := MakeLogs(env) fl := driver.(llogs.FilesLogs) - if !strings.HasPrefix(fl.DefaultPath(), logDir) { + if !strings.HasPrefix(fl.DefaultPath(), tempDir) { t.Fatalf("wrong log dir") } @@ -220,9 +221,19 @@ func TestMakeDbConnectionPanic(t *testing.T) { } func TestMakeAppPanic(t *testing.T) { + // Create a temporary directory with a lowercase path + tempDir := getLowerTempDir(t) + // Ensure the directory exists + err := os.MkdirAll(tempDir, 0755) + if err != nil { + t.Fatalf("failed to create log directory: %v", err) + } + // Clean up after tests + defer os.RemoveAll(tempDir) + validEnvVars(t) t.Setenv("ENV_DB_PORT", "1") - t.Setenv("ENV_APP_LOGS_DIR", "/tmp/log-%s.txt") + t.Setenv("ENV_APP_LOGS_DIR", tempDir+"/log-%s.txt") t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") env := MakeEnv(pkg.GetDefaultValidator()) @@ -249,16 +260,31 @@ func TestMakeSentry(t *testing.T) { } } +// getLowerTempDir returns a lowercase version of t.TempDir() +func getLowerTempDir(t *testing.T) string { + // Create a temporary directory in /tmp which should be lowercase + return "/tmp/testlogs" + strings.ToLower(strings.ReplaceAll(t.Name(), "/", "_")) +} + func TestCloseLogs(t *testing.T) { + // Create a temporary directory with a lowercase path + tempDir := getLowerTempDir(t) + // Ensure the directory exists + err := os.MkdirAll(tempDir, 0755) + if err != nil { + t.Fatalf("failed to create log directory: %v", err) + } + // Clean up after tests + defer os.RemoveAll(tempDir) validEnvVars(t) - t.Setenv("ENV_APP_LOGS_DIR", "/tmp/logs/log-%s.txt") + t.Setenv("ENV_APP_LOGS_DIR", tempDir+"/log-%s.txt") t.Setenv("ENV_SENTRY_DSN", "https://public@o0.ingest.sentry.io/0") env := MakeEnv(pkg.GetDefaultValidator()) - l := MakeLogs(env) - app := &App{logs: l} + logs := MakeLogs(env) + app := &App{logs: logs} app.CloseLogs() } diff --git a/metal/kernel/router.go b/metal/kernel/router.go index 3d827eb6..93778ac0 100644 --- a/metal/kernel/router.go +++ b/metal/kernel/router.go @@ -1,15 +1,26 @@ package kernel import ( + baseHttp "net/http" + "github.com/oullin/database" "github.com/oullin/database/repository" - "github.com/oullin/metal/env" "github.com/oullin/handler" + "github.com/oullin/metal/env" "github.com/oullin/pkg/http" "github.com/oullin/pkg/http/middleware" - baseHttp "net/http" ) +type StaticRouteResource interface { + Handle(baseHttp.ResponseWriter, *baseHttp.Request) *http.ApiError +} + +func addStaticRoute[H StaticRouteResource](r *Router, path, file string, maker func(string) H) { + abstract := maker(file) + resolver := r.PipelineFor(abstract.Handle) + r.Mux.HandleFunc("GET "+path, resolver) +} + type Router struct { Env *env.Environment Mux *baseHttp.ServeMux @@ -52,71 +63,29 @@ func (r *Router) Categories() { } func (r *Router) Profile() { - abstract := handler.MakeProfileHandler("./storage/fixture/profile.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /profile", resolver) + addStaticRoute(r, "/profile", "./storage/fixture/profile.json", handler.MakeProfileHandler) } func (r *Router) Experience() { - abstract := handler.MakeExperienceHandler("./storage/fixture/experience.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /experience", resolver) + addStaticRoute(r, "/experience", "./storage/fixture/experience.json", handler.MakeExperienceHandler) } func (r *Router) Projects() { - abstract := handler.MakeProjectsHandler("./storage/fixture/projects.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /projects", resolver) + addStaticRoute(r, "/projects", "./storage/fixture/projects.json", handler.MakeProjectsHandler) } func (r *Router) Social() { - abstract := handler.MakeSocialHandler("./storage/fixture/social.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /social", resolver) + addStaticRoute(r, "/social", "./storage/fixture/social.json", handler.MakeSocialHandler) } func (r *Router) Talks() { - abstract := handler.MakeTalksHandler("./storage/fixture/talks.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /talks", resolver) + addStaticRoute(r, "/talks", "./storage/fixture/talks.json", handler.MakeTalksHandler) } func (r *Router) Education() { - abstract := handler.MakeEducationHandler("./storage/fixture/education.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /education", resolver) + addStaticRoute(r, "/education", "./storage/fixture/education.json", handler.MakeEducationHandler) } func (r *Router) Recommendations() { - abstract := handler.MakeRecommendationsHandler("./storage/fixture/recommendations.json") - - resolver := r.PipelineFor( - abstract.Handle, - ) - - r.Mux.HandleFunc("GET /recommendations", resolver) + addStaticRoute(r, "/recommendations", "./storage/fixture/recommendations.json", handler.MakeRecommendationsHandler) } From c5dcb8919084e6ae0c6d1d37afed7b49482e5871 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:13:11 +0800 Subject: [PATCH 13/21] sentry recomendations --- sentry_analysis.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 sentry_analysis.md diff --git a/sentry_analysis.md b/sentry_analysis.md new file mode 100644 index 00000000..1e92b6be --- /dev/null +++ b/sentry_analysis.md @@ -0,0 +1,37 @@ +# Sentry Implementation Analysis + +## Current Implementation Status + +The current Sentry implementation in this application is **incomplete and ineffective** for properly reporting anomalies. While the basic infrastructure for Sentry is present, it's not actually being used to capture and report errors. + +### What's Present: +1. **Basic Initialization**: Sentry is initialized in `MakeSentry()` with a DSN from environment variables. +2. **HTTP Handler Creation**: A Sentry HTTP handler is created but never integrated into the request pipeline. +3. **Flush Call**: There's a `sentry.Flush()` call in main.go to ensure events are sent before shutdown. + +### Critical Issues: +1. **No Error Reporting**: There are no calls to `sentry.CaptureException()`, `sentry.CaptureMessage()`, or similar methods anywhere in the codebase. +2. **No Middleware Integration**: The created Sentry HTTP handler is never used in the HTTP request pipeline. +3. **No Context Enrichment**: No user, request, or environment context is added to Sentry events. +4. **No Panic Recovery**: Panics are used throughout the codebase but not captured by Sentry. +5. **Debug Mode Always On**: Sentry is initialized with Debug=true, which is not appropriate for production. + +## Recommendations + +To properly implement Sentry for effective anomaly reporting: + +1. **Integrate the HTTP Handler**: Wrap the application's HTTP handler with the Sentry handler to automatically capture HTTP errors and panics. + +2. **Add Recovery Middleware**: Create middleware to recover from panics and report them to Sentry. + +3. **Report API Errors to Sentry**: Modify the MakeApiHandler function to report errors to Sentry. + +4. **Add Context to Sentry Events**: Enrich Sentry events with user and request information. + +5. **Configure Sentry Properly**: Update the Sentry initialization to use appropriate settings (disable debug mode in production). + +6. **Explicit Error Reporting**: Add explicit error reporting in critical sections of the code. + +## Conclusion + +The current Sentry implementation will not properly report anomalies in the application. It has been initialized but not properly integrated into the error handling flow. Implementing the recommendations above would significantly improve the application's error monitoring capabilities. From c37ffeb088753d89f71cc215abd30e6638cb4a05 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:17:05 +0800 Subject: [PATCH 14/21] makefile --- Makefile | 16 ++++++++-------- {config => metal}/makefile/app.mk | 0 {config => metal}/makefile/build.mk | 0 {config => metal}/makefile/cli.mk | 0 {config => metal}/makefile/db.mk | 0 {config => metal}/makefile/env.mk | 0 {config => metal}/makefile/helpers.mk | 0 {config => metal}/makefile/infra.mk | 0 {config => metal}/makefile/logs.mk | 0 9 files changed, 8 insertions(+), 8 deletions(-) rename {config => metal}/makefile/app.mk (100%) rename {config => metal}/makefile/build.mk (100%) rename {config => metal}/makefile/cli.mk (100%) rename {config => metal}/makefile/db.mk (100%) rename {config => metal}/makefile/env.mk (100%) rename {config => metal}/makefile/helpers.mk (100%) rename {config => metal}/makefile/infra.mk (100%) rename {config => metal}/makefile/logs.mk (100%) diff --git a/Makefile b/Makefile index b52ed3b9..9726a256 100644 --- a/Makefile +++ b/Makefile @@ -33,14 +33,14 @@ VERSION := $(shell git describe --tags 2>/dev/null | cut -c 2-) # -------------------------------------------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------------------------------------------- # -include ./config/makefile/helpers.mk -include ./config/makefile/env.mk -include ./config/makefile/db.mk -include ./config/makefile/app.mk -include ./config/makefile/logs.mk -include ./config/makefile/build.mk -include ./config/makefile/infra.mk -include ./config/makefile/cli.mk +include ./metal/makefile/helpers.mk +include ./metal/makefile/env.mk +include ./metal/makefile/db.mk +include ./metal/makefile/app.mk +include ./metal/makefile/logs.mk +include ./metal/makefile/build.mk +include ./metal/makefile/infra.mk +include ./metal/makefile/cli.mk # -------------------------------------------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------------------------------------------- # diff --git a/config/makefile/app.mk b/metal/makefile/app.mk similarity index 100% rename from config/makefile/app.mk rename to metal/makefile/app.mk diff --git a/config/makefile/build.mk b/metal/makefile/build.mk similarity index 100% rename from config/makefile/build.mk rename to metal/makefile/build.mk diff --git a/config/makefile/cli.mk b/metal/makefile/cli.mk similarity index 100% rename from config/makefile/cli.mk rename to metal/makefile/cli.mk diff --git a/config/makefile/db.mk b/metal/makefile/db.mk similarity index 100% rename from config/makefile/db.mk rename to metal/makefile/db.mk diff --git a/config/makefile/env.mk b/metal/makefile/env.mk similarity index 100% rename from config/makefile/env.mk rename to metal/makefile/env.mk diff --git a/config/makefile/helpers.mk b/metal/makefile/helpers.mk similarity index 100% rename from config/makefile/helpers.mk rename to metal/makefile/helpers.mk diff --git a/config/makefile/infra.mk b/metal/makefile/infra.mk similarity index 100% rename from config/makefile/infra.mk rename to metal/makefile/infra.mk diff --git a/config/makefile/logs.mk b/metal/makefile/logs.mk similarity index 100% rename from config/makefile/logs.mk rename to metal/makefile/logs.mk From 779e4238a5bb3e4c962d8e64b69d5bddff2e1cc4 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:29:09 +0800 Subject: [PATCH 15/21] format --- database/connection.go | 12 ++++++------ go.mod | 2 +- pkg/http/handler.go | 2 +- pkg/validator.go | 5 +++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/database/connection.go b/database/connection.go index 4a5cf7b8..96703095 100644 --- a/database/connection.go +++ b/database/connection.go @@ -49,24 +49,24 @@ func (c *Connection) Close() bool { func (c *Connection) Ping() { var driver *sql.DB - fmt.Println("\n---------") + slog.Info("Database ping started", "separator", "---------") if conn, err := c.driver.DB(); err != nil { - fmt.Println(fmt.Sprintf("error retrieving the db driver: %v", err.Error())) + slog.Error("Error retrieving the db driver", "error", err.Error()) return } else { driver = conn - fmt.Println(fmt.Sprintf("db driver adquired: %T", driver)) + slog.Info("Database driver acquired", "type", fmt.Sprintf("%T", driver)) } if err := driver.Ping(); err != nil { - slog.Error("error pinging the db driver: " + err.Error()) + slog.Error("Error pinging the db driver", "error", err.Error()) } - fmt.Println(fmt.Sprintf("db driver is healthy: %+v", driver.Stats())) + slog.Info("Database driver is healthy", "stats", driver.Stats()) - fmt.Println("---------") + slog.Info("Database ping completed", "separator", "---------") } func (c *Connection) Sql() *gorm.DB { diff --git a/go.mod b/go.mod index de94d0f1..fbcbb591 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/oullin -go 1.24 +go 1.24.5 require ( github.com/getsentry/sentry-go v0.35.0 diff --git a/pkg/http/handler.go b/pkg/http/handler.go index 1777da54..78951482 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -9,7 +9,7 @@ import ( func MakeApiHandler(fn ApiHandler) baseHttp.HandlerFunc { return func(w baseHttp.ResponseWriter, r *baseHttp.Request) { if err := fn(w, r); err != nil { - slog.Error("API Error: %s, Status: %d", err.Message, err.Status) + slog.Error("API Error", "message", err.Message, "status", err.Status) w.Header().Set("Content-Type", "application/json") w.WriteHeader(err.Status) diff --git a/pkg/validator.go b/pkg/validator.go index 7662c505..8feeb0ef 100644 --- a/pkg/validator.go +++ b/pkg/validator.go @@ -4,9 +4,10 @@ import ( "encoding/json" "errors" "fmt" - "github.com/go-playground/validator/v10" "strings" "sync" + + "github.com/go-playground/validator/v10" ) type Validator struct { @@ -86,7 +87,7 @@ func (v *Validator) parseError(validateErrs validator.ValidationErrors) { case "email": e = fmt.Errorf("field '%s' must be a valid email address", field) case "eth_addr": - e = fmt.Errorf("Field '%s' must be a valid Ethereum address", field) + e = fmt.Errorf("field '%s' must be a valid Ethereum address", field) case "len": e = fmt.Errorf("field '%s' must be exactly %v characters long", field, current.Param()) default: From 846c176dcdb0beea9d83be7d54ebdb126bbc02f9 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:36:34 +0800 Subject: [PATCH 16/21] wip --- .github/workflows/gofmt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index 99bf9982..44d81b84 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -6,7 +6,7 @@ jobs: test: strategy: matrix: - go-version: [1.24.x] + go-version: [1.24.5] os: [ubuntu-latest] runs-on: ${{ matrix.os }} From b46a168326d979eadaede64ad324fd51e87de928 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:41:50 +0800 Subject: [PATCH 17/21] wip --- .github/workflows/gofmt.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index 44d81b84..bf7782ae 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -1,7 +1,7 @@ -on: [pull_request] - name: Go Fmt +on: [pull_request] + jobs: test: strategy: @@ -12,12 +12,16 @@ jobs: runs-on: ${{ matrix.os }} steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v4 - name: Run Formatter run: gofmt -w -s . @@ -32,4 +36,5 @@ jobs: commit_user_name: GitHub Actions commit_user_email: actions@github.com commit_author: gocanto + # Use the PR head ref which is available in pull_request events branch: ${{ github.head_ref }} From 6cd0d193535855cfaa8bc476c07219220bc1c9be Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:42:51 +0800 Subject: [PATCH 18/21] wip --- .github/workflows/gofmt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index bf7782ae..0bfb45af 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -3,7 +3,7 @@ name: Go Fmt on: [pull_request] jobs: - test: + gofmt: strategy: matrix: go-version: [1.24.5] From a3f5f988f0440b4a8fe183922bc2e6d431b58126 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 16:49:25 +0800 Subject: [PATCH 19/21] wip --- .github/workflows/gofmt.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index 0bfb45af..14a94bd9 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -2,6 +2,9 @@ name: Go Fmt on: [pull_request] +permissions: + contents: write + jobs: gofmt: strategy: @@ -27,7 +30,7 @@ jobs: run: gofmt -w -s . - name: Commit Changes - uses: stefanzweifel/git-auto-commit-action@v4.0.0 + uses: stefanzweifel/git-auto-commit-action@v6.0.1 with: commit_message: apply coding style fixes commit_options: '--no-verify' @@ -36,5 +39,4 @@ jobs: commit_user_name: GitHub Actions commit_user_email: actions@github.com commit_author: gocanto - # Use the PR head ref which is available in pull_request events branch: ${{ github.head_ref }} From a2a13e1ac35fe39d477355a90f2b1784e94d20b5 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 17:15:29 +0800 Subject: [PATCH 20/21] tweaks --- .github/workflows/gofmt.yml | 62 ++++++++++++++++++++++++++++-- .gitignore | 2 +- Makefile | 3 -- main.go | 2 +- metal/cli/accounts/handler_test.go | 2 +- metal/cli/main.go | 4 +- metal/cli/posts/handler_test.go | 2 +- metal/makefile/app.mk | 2 +- metal/makefile/cli.mk | 4 -- pkg/llogs/files_logs.go | 4 +- 10 files changed, 67 insertions(+), 20 deletions(-) delete mode 100644 metal/makefile/cli.mk diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index 14a94bd9..dcbd2df6 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -1,9 +1,15 @@ name: Go Fmt -on: [pull_request] +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + push: + branches: + - main permissions: contents: write + pull-requests: write jobs: gofmt: @@ -18,7 +24,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} + ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} fetch-depth: 0 - name: Install Go @@ -29,7 +35,14 @@ jobs: - name: Run Formatter run: gofmt -w -s . - - name: Commit Changes + - name: Check for formatting changes (dry run) + if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'testing') }} + run: | + git diff --exit-code || echo "::warning::Formatting changes would be applied in normal mode" + continue-on-error: true + + - name: Commit Changes to PR + if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'testing') }} uses: stefanzweifel/git-auto-commit-action@v6.0.1 with: commit_message: apply coding style fixes @@ -37,6 +50,47 @@ jobs: file_pattern: . repository: . commit_user_name: GitHub Actions - commit_user_email: actions@github.com + commit_user_email: ${{ secrets.GUS_GH_EMAIL }} commit_author: gocanto branch: ${{ github.head_ref }} + + - name: Create branch for formatting changes + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + run: | + # Check if there are any changes after formatting + if ! git diff --quiet; then + # Create a new branch with timestamp + BRANCH_NAME="gofmt-fixes-$(date +%Y%m%d-%H%M%S)" + git checkout -b $BRANCH_NAME + + # Configure git + git config user.name "GitHub Actions" + git config user.email "${{ secrets.GUS_GH_EMAIL }}" + + # Commit changes + git add . + git commit -m "Apply Go formatting fixes" + + # Push the branch + git push origin $BRANCH_NAME + + # Set output for next step + echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV + echo "HAS_CHANGES=true" >> $GITHUB_ENV + else + echo "No formatting changes needed" + echo "HAS_CHANGES=false" >> $GITHUB_ENV + fi + + - name: Create Pull Request + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && env.HAS_CHANGES == 'true' }} + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Apply Go formatting fixes + title: "Auto: Apply Go formatting fixes" + body: | + This PR was automatically created by the Go Fmt GitHub Action. + It applies Go formatting standards to the codebase. + branch: ${{ env.BRANCH_NAME }} + base: main diff --git a/.gitignore b/.gitignore index ce45f30f..b5598bc7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ tmp database/infra/data # --- [API]: Bin -bin/*.* +bin/* !bin/.gitkeep # --- [API]: Storage diff --git a/Makefile b/Makefile index 9726a256..4e6221e5 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,6 @@ include ./metal/makefile/app.mk include ./metal/makefile/logs.mk include ./metal/makefile/build.mk include ./metal/makefile/infra.mk -include ./metal/makefile/cli.mk # -------------------------------------------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------------------------------------------- # @@ -54,8 +53,6 @@ help: @printf " $(BOLD)$(GREEN)audit$(NC) : Run code audits and checks.\n" @printf " $(BOLD)$(GREEN)watch$(NC) : Start a file watcher process.\n" @printf " $(BOLD)$(GREEN)format$(NC) : Automatically format code.\n" - @printf " $(BOLD)$(GREEN)run-cli$(NC) : Run the API's cli interface.\n" - @printf " $(BOLD)$(GREEN)run-main$(NC) : Run the main application.\n\n" @printf " $(BOLD)$(GREEN)test-all$(NC) : Run all the application tests.\n\n" @printf "$(BOLD)$(BLUE)Build Commands:$(NC)\n" diff --git a/main.go b/main.go index c9d57617..7a3596cd 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,8 @@ package main import ( "fmt" - _ "github.com/lib/pq" "github.com/getsentry/sentry-go" + _ "github.com/lib/pq" "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/rs/cors" diff --git a/metal/cli/accounts/handler_test.go b/metal/cli/accounts/handler_test.go index 5c2222b4..ac53ce0d 100644 --- a/metal/cli/accounts/handler_test.go +++ b/metal/cli/accounts/handler_test.go @@ -3,8 +3,8 @@ package accounts import ( "testing" - "github.com/oullin/metal/cli/clitest" "github.com/oullin/database" + "github.com/oullin/metal/cli/clitest" ) func setupAccountHandler(t *testing.T) *Handler { diff --git a/metal/cli/main.go b/metal/cli/main.go index 75e7bf10..1bd6e34a 100644 --- a/metal/cli/main.go +++ b/metal/cli/main.go @@ -2,10 +2,10 @@ package main import ( "fmt" + "github.com/oullin/database" "github.com/oullin/metal/cli/accounts" "github.com/oullin/metal/cli/panel" "github.com/oullin/metal/cli/posts" - "github.com/oullin/database" "github.com/oullin/metal/env" "github.com/oullin/metal/kernel" "github.com/oullin/pkg" @@ -17,7 +17,7 @@ var environment *env.Environment var dbConn *database.Connection func init() { - secrets := kernel.Ignite("./../../.env", pkg.GetDefaultValidator()) + secrets := kernel.Ignite("./.env", pkg.GetDefaultValidator()) environment = secrets dbConn = kernel.MakeDbConnection(environment) diff --git a/metal/cli/posts/handler_test.go b/metal/cli/posts/handler_test.go index eda09840..27dd024b 100644 --- a/metal/cli/posts/handler_test.go +++ b/metal/cli/posts/handler_test.go @@ -9,8 +9,8 @@ import ( "time" "github.com/google/uuid" - "github.com/oullin/metal/cli/clitest" "github.com/oullin/database" + "github.com/oullin/metal/cli/clitest" "github.com/oullin/pkg" "github.com/oullin/pkg/markdown" ) diff --git a/metal/makefile/app.mk b/metal/makefile/app.mk index b52d5714..a08c6fa5 100644 --- a/metal/makefile/app.mk +++ b/metal/makefile/app.mk @@ -54,7 +54,7 @@ run-cli: DB_SECRET_USERNAME="$(DB_SECRET_USERNAME)" \ DB_SECRET_PASSWORD="$(DB_SECRET_PASSWORD)" \ DB_SECRET_DBNAME="$(DB_SECRET_DBNAME)" \ - docker compose run --rm api-runner go run ./cli/main.go + docker compose run --rm api-runner go run ./metal/cli/main.go test-all: go test ./... diff --git a/metal/makefile/cli.mk b/metal/makefile/cli.mk deleted file mode 100644 index 53e771c0..00000000 --- a/metal/makefile/cli.mk +++ /dev/null @@ -1,4 +0,0 @@ -.PHONY: run-cli - -run-cli: - cd metal/cli && go run main.go diff --git a/pkg/llogs/files_logs.go b/pkg/llogs/files_logs.go index 8fa4257b..550a97b4 100644 --- a/pkg/llogs/files_logs.go +++ b/pkg/llogs/files_logs.go @@ -5,7 +5,7 @@ import ( "github.com/oullin/metal/env" "log/slog" "os" - "strings" + "path/filepath" "time" ) @@ -23,7 +23,7 @@ func MakeFilesLogs(env *env.Environment) (Driver, error) { manager.path = manager.DefaultPath() // Create directory if it doesn't exist - dir := manager.path[:strings.LastIndex(manager.path, "/")] + dir := filepath.Dir(manager.path) if err := os.MkdirAll(dir, 0755); err != nil { return FilesLogs{}, fmt.Errorf("failed to create log directory: %w", err) } From 0dc58acbb983aa8dbbc7eecf7762d5f2e31eadbf Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 7 Aug 2025 17:31:18 +0800 Subject: [PATCH 21/21] formatter --- .github/workflows/gofmt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index dcbd2df6..4c297bf2 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -84,7 +84,7 @@ jobs: - name: Create Pull Request if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && env.HAS_CHANGES == 'true' }} - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: Apply Go formatting fixes