Go REST API (Gin + GORM) for distributing short messages during time-sensitive events. Bridges messages to Telegram, Mastodon, Bluesky, and Signal.
- Language: Go (see
go.modfor version) - Web framework: Gin
- ORM: GORM (SQLite, PostgreSQL, MySQL)
- CLI: Cobra
| Task | Command |
|---|---|
| Build | go build |
| Test all | go test ./... |
| Test with coverage | go test -coverprofile=coverage.txt -covermode=atomic ./... |
| Single test suite | go test -run TestTickerTestSuite ./internal/api/... |
| Single subtest | go test -run TestTickerTestSuite/TestGetTickers ./internal/api/... |
| Single simple test | go test -run TestContains ./internal/util/... |
| Lint | golangci-lint run --timeout 10m |
| Format | gofmt -w . and goimports -w . |
| Tidy deps | go mod tidy |
| Generate mocks | mockery (configured in .mockery.yml) |
All application code lives in internal/:
cmd/- CLI entry points (Cobra)internal/api/- HTTP handlers (Gin), middleware, response typesinternal/api/middleware/- One subpackage per middlewareinternal/api/response/- Response DTOs and serializersinternal/bridge/- Message bridging (Telegram, Mastodon, Bluesky, Signal)internal/storage/- Data models, GORM storage interface + implementation, migrationsinternal/config/- YAML + env var configurationinternal/cache/- In-memory cacheinternal/logger/- Structured logging (logrus)testdata/- Test fixtures
Two groups separated by a blank line:
- Standard library
- Everything else (third-party and internal mixed, alphabetically sorted)
Use import aliases only for naming collisions.
- Files:
snake_case.go, tests colocated assnake_case_test.go - Handlers: HTTP verb prefix:
GetTickers,PostTicker,PutTicker,DeleteTicker - Constructors:
NewTicker(),NewSqlStorage(),NewCache() - Short vars in narrow scope:
cfor*gin.Context,hfor handler,sfor suite/storage,errfor errors - Request types: suffix
Param(TickerParam,MessageParam) - Response types: separate structs in
internal/api/response/ - Constants: typed, PascalCase, grouped in
constblocks
- Early return on error (guard clause pattern)
- Lowercase error messages, no trailing punctuation
- Translate errors to structured API responses:
response.ErrorResponse(code, msg) - Log with context:
log.WithError(err).WithField("key", val).Error("description") - Don't log AND return the same error — choose one
gofmt/goimportsfor all code- Keep happy path left-aligned, return early to reduce nesting
- Favor clarity and simplicity over cleverness
type TickerTestSuite struct {
suite.Suite
w *httptest.ResponseRecorder
ctx *gin.Context
store *storage.MockStorage
}
func (s *TickerTestSuite) SetupTest() { /* reset state per test */ }
func (s *TickerTestSuite) TestGetTickers() {
s.Run("when not authorized", func() { /* ... */ })
s.Run("happy path", func() { /* ... */ })
}
func TestTickerTestSuite(t *testing.T) {
suite.Run(t, new(TickerTestSuite))
}func TestContains(t *testing.T) {
assert.True(t, Contains([]int{1, 2}, 1))
assert.False(t, Contains([]int{1, 2}, 3))
}- Mockery generates mocks from interfaces (config:
.mockery.yml) - Pattern:
s.store.On("Method", mock.Anything).Return(val).Once() - Always call
s.store.AssertExpectations(s.T())at end of subtests - gock for HTTP mocking of external API calls
- Subtest names: human-readable scenarios (
"when storage returns error","happy path")
Central handler struct holds all dependencies:
type handler struct {
config config.Config
storage storage.Storage
bridges bridge.Bridges
cache *cache.Cache
realtime *realtime.Engine
}Handler methods follow this flow:
- Extract entity from gin context (via middleware prefetch)
- Validate/bind request body
- Perform storage operation
- Return
response.SuccessResponse(...)orresponse.ErrorResponse(...)
- Logrus with per-package logger:
var log = logger.GetWithPackage("api") - Structured fields:
.WithError(err),.WithField("key", val) - Levels:
Error,Warn,Info,Fatal
Use Gitmoji in commit messages and PR titles:
- ✨ New feature
- 🐛 Bug fix
- ♻️ Refactor
- ✅ Add/update tests
- ⬆️ Upgrade dependencies
- 📝 Documentation
- 🧹 Chore/maintenance
- Config:
.golangci.yml(v2 format) - Tests are excluded from linting
- Exclusion presets: comments, common-false-positives, legacy, std-error-handling