From dbe5cab56e6ce16b523f275e666f380953b55023 Mon Sep 17 00:00:00 2001 From: thuanle Date: Mon, 27 Apr 2026 22:34:56 +0700 Subject: [PATCH 1/2] ci: add Gitea Actions workflow with gofmt and test jobs Add CI workflow at .gitea/workflows/ci.yml triggered on pull_request with two jobs: fmt (gofmt check) and test (go test ./...). Fix gofmt issues (import ordering) in 7 files so CI passes from the start. Fixes #7 Co-Authored-By: Claude Opus 4.7 --- .gitea/workflows/ci.yml | 29 +++++++++++++++++++++++++++++ internal/ai_client/client.go | 12 ++++++------ internal/ai_client/client_test.go | 6 +++--- internal/api/router.go | 2 +- internal/config/config.go | 22 +++++++++++----------- internal/database/models.go | 28 ++++++++++++++-------------- internal/mail/imap.go | 2 +- internal/mail/smtp.go | 2 +- 8 files changed, 66 insertions(+), 37 deletions(-) create mode 100644 .gitea/workflows/ci.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..3857aaa --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + pull_request: + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - run: | + out=$(gofmt -l .) + if [ -n "$out" ]; then + echo "Files not formatted:" + echo "$out" + exit 1 + fi + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - run: go test ./... diff --git a/internal/ai_client/client.go b/internal/ai_client/client.go index 1e1705c..c395f06 100644 --- a/internal/ai_client/client.go +++ b/internal/ai_client/client.go @@ -9,10 +9,10 @@ import ( "strings" "time" + "gorm.io/gorm" "thuanle.me/claw-email-bridge/internal/config" "thuanle.me/claw-email-bridge/internal/database" "thuanle.me/claw-email-bridge/internal/logging" - "gorm.io/gorm" ) // Retry backoff schedule: 1s, 5s, 15s. @@ -20,11 +20,11 @@ var retryBackoffs = []time.Duration{1 * time.Second, 5 * time.Second, 15 * time. // openClawRequest is the payload sent to OpenClaw API. type openClawRequest struct { - Input string `json:"input"` - SessionID string `json:"session_id"` - History []historyEntry `json:"history,omitempty"` - CallbackURL string `json:"callback_url"` - Metadata map[string]string `json:"metadata"` + Input string `json:"input"` + SessionID string `json:"session_id"` + History []historyEntry `json:"history,omitempty"` + CallbackURL string `json:"callback_url"` + Metadata map[string]string `json:"metadata"` } type historyEntry struct { diff --git a/internal/ai_client/client_test.go b/internal/ai_client/client_test.go index 2a11683..6172e3e 100644 --- a/internal/ai_client/client_test.go +++ b/internal/ai_client/client_test.go @@ -130,9 +130,9 @@ func TestDispatch_UsesCallbackBaseURL(t *testing.T) { defer mockServer.Close() cfg := &config.Config{ - OpenClawURL: mockServer.URL, - ListenAddr: ":9999", - CallbackBaseURL: "https://bridge.example.com/", + OpenClawURL: mockServer.URL, + ListenAddr: ":9999", + CallbackBaseURL: "https://bridge.example.com/", } task := &database.Task{ diff --git a/internal/api/router.go b/internal/api/router.go index b8a3d1e..b290c94 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -5,9 +5,9 @@ import ( "net/http" "github.com/gin-gonic/gin" + "gorm.io/gorm" "thuanle.me/claw-email-bridge/internal/database" "thuanle.me/claw-email-bridge/internal/logging" - "gorm.io/gorm" ) // CallbackRequest is the payload received from OpenClaw. diff --git a/internal/config/config.go b/internal/config/config.go index 7cbe3d1..13e8d1d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -102,17 +102,17 @@ func defaultCallbackBaseURL(listenAddr string) string { // validate checks that all required configuration values are present. func (c *Config) validate() error { required := map[string]string{ - "IMAP_HOST": c.IMAPHost, - "IMAP_PORT": c.IMAPPort, - "IMAP_USER": c.IMAPUser, - "IMAP_PASS": c.IMAPPass, - "SMTP_HOST": c.SMTPHost, - "SMTP_PORT": c.SMTPPort, - "SMTP_USER": c.SMTPUser, - "SMTP_PASS": c.SMTPPass, - "OPENCLAW_URL": c.OpenClawURL, - "BRIDGE_CALLBACK_TOKEN": c.BridgeCallbackToken, - "SYSTEM_EMAIL": c.SystemEmail, + "IMAP_HOST": c.IMAPHost, + "IMAP_PORT": c.IMAPPort, + "IMAP_USER": c.IMAPUser, + "IMAP_PASS": c.IMAPPass, + "SMTP_HOST": c.SMTPHost, + "SMTP_PORT": c.SMTPPort, + "SMTP_USER": c.SMTPUser, + "SMTP_PASS": c.SMTPPass, + "OPENCLAW_URL": c.OpenClawURL, + "BRIDGE_CALLBACK_TOKEN": c.BridgeCallbackToken, + "SYSTEM_EMAIL": c.SystemEmail, } var missing []string diff --git a/internal/database/models.go b/internal/database/models.go index 5fe3db1..f779396 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -6,28 +6,28 @@ import ( // Task status constants matching the requirement's state machine. const ( - StatusIgnored = "IGNORED" - StatusReceived = "RECEIVED" - StatusAIProcessing = "AI_PROCESSING" - StatusCallbackDone = "CALLBACK_DONE" - StatusSMTPRetrying = "SMTP_RETRYING" - StatusCompleted = "COMPLETED" - StatusFailed = "FAILED" + StatusIgnored = "IGNORED" + StatusReceived = "RECEIVED" + StatusAIProcessing = "AI_PROCESSING" + StatusCallbackDone = "CALLBACK_DONE" + StatusSMTPRetrying = "SMTP_RETRYING" + StatusCompleted = "COMPLETED" + StatusFailed = "FAILED" ) // Task represents a single email processing unit tracked through the pipeline. type Task struct { - ID uint `gorm:"primaryKey;autoIncrement"` - TaskUUID string `gorm:"uniqueIndex;not null"` - ThreadID string `gorm:"index"` - MessageID string `gorm:"index"` + ID uint `gorm:"primaryKey;autoIncrement"` + TaskUUID string `gorm:"uniqueIndex;not null"` + ThreadID string `gorm:"index"` + MessageID string `gorm:"index"` Sender string Subject string BodyPlain string AIResponse string - Status string `gorm:"index;not null"` - AttemptOpenClaw int `gorm:"default:0"` - AttemptSMTP int `gorm:"default:0"` + Status string `gorm:"index;not null"` + AttemptOpenClaw int `gorm:"default:0"` + AttemptSMTP int `gorm:"default:0"` LastError *string NextRetryAt *time.Time CreatedAt time.Time diff --git a/internal/mail/imap.go b/internal/mail/imap.go index 8c9880d..3536af5 100644 --- a/internal/mail/imap.go +++ b/internal/mail/imap.go @@ -17,10 +17,10 @@ import ( "github.com/emersion/go-imap/v2/imapclient" "github.com/google/uuid" "golang.org/x/net/proxy" + "gorm.io/gorm" "thuanle.me/claw-email-bridge/internal/config" "thuanle.me/claw-email-bridge/internal/database" "thuanle.me/claw-email-bridge/internal/logging" - "gorm.io/gorm" ) // IMAPWatcher monitors an IMAP mailbox via IDLE and processes new emails. diff --git a/internal/mail/smtp.go b/internal/mail/smtp.go index 138bd7b..9e9b628 100644 --- a/internal/mail/smtp.go +++ b/internal/mail/smtp.go @@ -6,10 +6,10 @@ import ( "strings" "time" + "gorm.io/gorm" "thuanle.me/claw-email-bridge/internal/config" "thuanle.me/claw-email-bridge/internal/database" "thuanle.me/claw-email-bridge/internal/logging" - "gorm.io/gorm" ) // Retry backoff schedule: 1s, 5s, 15s. -- 2.54.0 From a6734c126d264aa482c0fd748f5682a51ff270d8 Mon Sep 17 00:00:00 2001 From: claudecode <38+claudecode@noreply.localhost> Date: Mon, 27 Apr 2026 22:41:46 +0700 Subject: [PATCH 2/2] ci: use 'linux' runner label instead of 'ubuntu-latest' Gitea runners use 'linux' label, not GitHub's 'ubuntu-latest'. --- .gitea/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 3857aaa..c5d28e4 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -5,7 +5,7 @@ on: jobs: fmt: - runs-on: ubuntu-latest + runs-on: linux steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 @@ -20,7 +20,7 @@ jobs: fi test: - runs-on: ubuntu-latest + runs-on: linux steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 -- 2.54.0