diff --git a/.github/workflows/gofmt.yml b/.github/workflows/gofmt.yml index 852a259e..4c297bf2 100644 --- a/.github/workflows/gofmt.yml +++ b/.github/workflows/gofmt.yml @@ -1,29 +1,96 @@ -on: [pull_request] - name: Go Fmt +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + jobs: - test: + gofmt: strategy: matrix: - go-version: [1.24.x] + go-version: [1.24.5] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.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 . - - name: Commit Changes - uses: stefanzweifel/git-auto-commit-action@v4.0.0 + - 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 commit_options: '--no-verify' + file_pattern: . + repository: . + commit_user_name: GitHub Actions + 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@v6 + 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/.github/workflows/tests.yml b/.github/workflows/tests.yml index 26baa423..7eb67d3c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,16 +2,19 @@ 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: - go-version: ['1.24.5', '1.24.4'] + go-version: ['1.24.6', '1.24.5'] steps: - uses: actions/setup-go@v5 @@ -38,10 +41,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 ./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 run: | @@ -55,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 diff --git a/.gitignore b/.gitignore index 15344de3..b5598bc7 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/Makefile b/Makefile index 280c7585..4e6221e5 100644 --- a/Makefile +++ b/Makefile @@ -33,13 +33,13 @@ 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 ./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 # -------------------------------------------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------------------------------------------- # @@ -53,7 +53,7 @@ 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)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/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/database/connection.go b/database/connection.go index 2550f5d2..96703095 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" @@ -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/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 de915045..154ea5b7 100644 --- a/database/seeder/main.go +++ b/database/seeder/main.go @@ -1,20 +1,21 @@ package main import ( - "github.com/oullin/boost" + "sync" + "time" + "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" - "sync" - "time" ) var environment *env.Environment func init() { - secrets := boost.Ignite("./.env", pkg.GetDefaultValidator()) + secrets := kernel.Ignite("./.env", pkg.GetDefaultValidator()) environment = secrets } @@ -22,10 +23,10 @@ func init() { func main() { cli.ClearScreen() - dbConnection := boost.MakeDbConnection(environment) - logs := boost.MakeLogs(environment) + 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/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/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/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/main.go b/main.go index f099a36f..7a3596cd 100644 --- a/main.go +++ b/main.go @@ -2,20 +2,22 @@ package main import ( "fmt" + "github.com/getsentry/sentry-go" _ "github.com/lib/pq" - "github.com/oullin/boost" + "github.com/oullin/metal/kernel" "github.com/oullin/pkg" "github.com/rs/cors" "log/slog" baseHttp "net/http" + "time" ) -var app *boost.App +var app *kernel.App func init() { validate := pkg.GetDefaultValidator() - secrets := boost.Ignite("./.env", validate) - application, err := boost.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)) @@ -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/cli/accounts/factory.go b/metal/cli/accounts/factory.go similarity index 95% rename from cli/accounts/factory.go rename to metal/cli/accounts/factory.go index 8f9752fb..e340b637 100644 --- a/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/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..ac53ce0d 100644 --- a/cli/accounts/handler_test.go +++ b/metal/cli/accounts/handler_test.go @@ -3,8 +3,8 @@ package accounts import ( "testing" - "github.com/oullin/cli/clitest" "github.com/oullin/database" + "github.com/oullin/metal/cli/clitest" ) func setupAccountHandler(t *testing.T) *Handler { diff --git a/cli/clitest/helpers.go b/metal/cli/clitest/helpers.go similarity index 98% rename from cli/clitest/helpers.go rename to metal/cli/clitest/helpers.go index e21e56a7..21c41581 100644 --- a/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/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 91% rename from cli/main.go rename to metal/cli/main.go index e36a349e..1bd6e34a 100644 --- a/cli/main.go +++ b/metal/cli/main.go @@ -2,12 +2,12 @@ package main import ( "fmt" - "github.com/oullin/boost" - "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/cli/accounts" + "github.com/oullin/metal/cli/panel" + "github.com/oullin/metal/cli/posts" + "github.com/oullin/metal/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 := boost.Ignite("./../.env", pkg.GetDefaultValidator()) + secrets := kernel.Ignite("./.env", pkg.GetDefaultValidator()) environment = secrets - dbConn = boost.MakeDbConnection(environment) + dbConn = kernel.MakeDbConnection(environment) } func main() { 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..27dd024b 100644 --- a/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/cli/clitest" "github.com/oullin/database" + "github.com/oullin/metal/cli/clitest" "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 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/boost/app.go b/metal/kernel/app.go similarity index 95% rename from boost/app.go rename to metal/kernel/app.go index 34f760c9..b3d20138 100644 --- a/boost/app.go +++ b/metal/kernel/app.go @@ -1,21 +1,22 @@ -package boost +package kernel import ( "fmt" + baseHttp "net/http" + "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" "github.com/oullin/pkg/llogs" - baseHttp "net/http" ) 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/boost/factory.go b/metal/kernel/factory.go similarity index 92% rename from boost/factory.go rename to metal/kernel/factory.go index e173042a..34ed096e 100644 --- a/boost/factory.go +++ b/metal/kernel/factory.go @@ -1,15 +1,15 @@ -package boost +package kernel import ( + "log" + "strconv" + "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" - "log" - "strconv" - "time" ) func MakeSentry(env *env.Environment) *pkg.Sentry { @@ -22,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) @@ -44,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/boost/helpers.go b/metal/kernel/helpers.go similarity index 85% rename from boost/helpers.go rename to metal/kernel/helpers.go index 33b8187a..a5bc1565 100644 --- a/boost/helpers.go +++ b/metal/kernel/helpers.go @@ -1,9 +1,10 @@ -package boost +package kernel import ( - "github.com/oullin/database" - "github.com/oullin/env" baseHttp "net/http" + + "github.com/oullin/database" + "github.com/oullin/metal/env" ) 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/boost/ignite.go b/metal/kernel/ignite.go similarity index 85% rename from boost/ignite.go rename to metal/kernel/ignite.go index c3e72402..6bf2f8a8 100644 --- a/boost/ignite.go +++ b/metal/kernel/ignite.go @@ -1,8 +1,8 @@ -package boost +package kernel import ( "github.com/joho/godotenv" - "github.com/oullin/env" + "github.com/oullin/metal/env" "github.com/oullin/pkg" ) diff --git a/boost/boost_test.go b/metal/kernel/kernel_test.go similarity index 79% rename from boost/boost_test.go rename to metal/kernel/kernel_test.go index 0f5202d1..43b8f71c 100644 --- a/boost/boost_test.go +++ b/metal/kernel/kernel_test.go @@ -1,10 +1,9 @@ -package boost +package kernel import ( "net/http" "net/http/httptest" "os" - "path/filepath" "strings" "testing" @@ -178,22 +177,25 @@ func TestAppBootRoutes(t *testing.T) { } func TestMakeLogs(t *testing.T) { - dir, err := os.MkdirTemp("", "logdir") - + // 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("tmpdir err: %v", err) + 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", filepath.Join(dir, "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(), dir) { + if !strings.HasPrefix(fl.DefaultPath(), tempDir) { t.Fatalf("wrong log dir") } @@ -219,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()) @@ -248,21 +260,31 @@ func TestMakeSentry(t *testing.T) { } } -func TestCloseLogs(t *testing.T) { - dir, err := os.MkdirTemp("", "logdir") +// 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("tmpdir err: %v", err) + 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", filepath.Join(dir, "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/boost/router.go b/metal/kernel/router.go similarity index 51% rename from boost/router.go rename to metal/kernel/router.go index 95c88a91..93778ac0 100644 --- a/boost/router.go +++ b/metal/kernel/router.go @@ -1,15 +1,26 @@ -package boost +package kernel import ( + baseHttp "net/http" + "github.com/oullin/database" "github.com/oullin/database/repository" - "github.com/oullin/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) } diff --git a/config/makefile/app.mk b/metal/makefile/app.mk similarity index 92% rename from config/makefile/app.mk rename to metal/makefile/app.mk index 4bca6116..a08c6fa5 100644 --- a/config/makefile/app.mk +++ b/metal/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 @@ -54,7 +54,10 @@ 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 ./... # --- Mac: # Needs to be locally installed: https://formulae.brew.sh/formula/caddy 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/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 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/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 ec9ff41d..550a97b4 100644 --- a/pkg/llogs/files_logs.go +++ b/pkg/llogs/files_logs.go @@ -2,9 +2,10 @@ package llogs import ( "fmt" - "github.com/oullin/env" + "github.com/oullin/metal/env" "log/slog" "os" + "path/filepath" "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 := filepath.Dir(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 { 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 { 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: 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.