Skip to content

Banx17/stellabill-backend

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

127 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stellabill Backend

Go (Gin) API backend for Stellabill - subscription and billing plans API. This repo is backend-only; a separate frontend consumes these APIs.


Table of contents


Tech stack

  • Language: Go 1.22+
  • Framework: Gin
  • Database: PostgreSQL with Outbox Pattern for reliable event publishing
  • Config: Environment variables (no config files required for default dev)

What this backend provides (for the frontend)

This service is the backend only. A separate frontend (or any client) can:

  • Health check - GET /api/health to verify the API is up.
  • Plans - GET /api/plans to list billing plans (id, name, amount, currency, interval, description). Currently returns an empty list; DB integration is planned.
  • Subscriptions - GET /api/subscriptions to list subscriptions and GET /api/subscriptions/:id to fetch one. Responses include plan_id, customer, status, amount, interval, next_billing. Currently placeholder/mock data; DB integration is planned.

CORS is enabled for all origins in development so a frontend on another port or domain can call these endpoints.


Background Worker

The backend includes a production-ready background worker system for automated billing job scheduling and execution.

Key Features

  • Job Scheduling: Schedule billing operations (charges, invoices, reminders) with configurable execution times
  • Distributed Locking: Prevents duplicate processing when running multiple worker instances
  • Retry Policy: Automatic retry with exponential backoff (1s, 4s, 9s) for failed jobs
  • Dead-Letter Queue: Failed jobs after max attempts are moved for manual review
  • Graceful Shutdown: Workers complete in-flight jobs before shutting down
  • Metrics Tracking: Monitor job processing statistics (processed, succeeded, failed, dead-lettered)
  • Concurrent Workers: Multiple workers can run safely without duplicate processing

Documentation

  • internal/worker/README.md - Complete worker documentation
  • internal/worker/INTEGRATION.md - Integration guide with examples
  • internal/worker/SECURITY.md - Security analysis and threat model
  • WORKER_IMPLEMENTATION.md - Implementation summary

Quick Example

store := worker.NewMemoryStore()
executor := worker.NewBillingExecutor()
config := worker.DefaultConfig()

w := worker.NewWorker(store, executor, config)
w.Start()
defer w.Stop()

scheduler := worker.NewScheduler(store)
job, _ := scheduler.ScheduleCharge("sub-123", time.Now(), 3)

Local setup

Prerequisites

  • Go 1.22 or later
  • Git (for cloning and contributing)
  • PostgreSQL (optional for now; app runs without it using default config; DB will be used when persistence is added)

1. Clone the repository

git clone https://github.com/YOUR_ORG/stellabill-backend.git
cd stellabill-backend

2. Install dependencies

go mod download

3. (Optional) Environment variables

Create a .env file in the project root (do not commit it; it is in .gitignore):

# Optional - defaults shown
ENV=development
PORT=8080
DATABASE_URL=postgres://localhost/stellarbill?sslmode=disable
JWT_SECRET=change-me-in-production
ADMIN_TOKEN=change-me-admin-token
AUDIT_HMAC_SECRET=stellarbill-dev-audit
AUDIT_LOG_PATH=audit.log

Or export them in your shell. The app will run with the defaults if you do not set anything.

4. Run the server

go run ./cmd/server

Server listens on http://localhost:8080 (or the port you set via PORT).

5. Verify

curl http://localhost:8080/api/health
# Expected: {"service":"stellarbill-backend","status":"ok","outbox":{"pending_events":0,"dispatcher_running":true,"database_health":"healthy"}}

curl http://localhost:8080/api/outbox/stats
# Expected: {"pending_events":0,"dispatcher_running":true,"database_health":"healthy"}

curl -X POST http://localhost:8080/api/outbox/test
# Expected: {"message":"Test event published successfully","event_type":"test.event"}

Configuration

Variable Default Description
ENV development Environment (e.g. production)
PORT 8080 HTTP server port
DATABASE_URL postgres://localhost/stellarbill?sslmode=disable PostgreSQL connection string
JWT_SECRET change-me-in-production Secret for JWT (change in prod)
FF_DEFAULT_ENABLED false Default state for unknown flags
FF_LOG_DISABLED true Log when flags block requests
FF_CONFIG_FILE "" Path to feature flags config file

Feature Flags Configuration

Feature flags can be configured using environment variables in several ways:

1. Individual Flags (Recommended)

Use the FF_ prefix for individual flags:

# Enable/disable specific features
FF_SUBSCRIPTIONS_ENABLED=true
FF_PLANS_ENABLED=false
FF_NEW_BILLING_FLOW=true
FF_ADVANCED_ANALYTICS=false

2. JSON Configuration

Use the FEATURE_FLAGS environment variable for bulk configuration:

export FEATURE_FLAGS='{"subscriptions_enabled": true, "plans_enabled": true, "new_billing_flow": false}'

3. Priority Order

The system uses the following priority (highest to lowest):

  1. FF_* individual environment variables
  2. FEATURE_FLAGS JSON configuration
  3. Default flag values

Available Feature Flags

Flag Name Default Description
subscriptions_enabled true Enable subscription management endpoints
plans_enabled true Enable billing plans endpoints
new_billing_flow false Enable new billing flow feature
advanced_analytics false Enable advanced analytics endpoints

In production, set these via your host's environment or secrets manager; do not commit secrets.


Using Feature Flags in Code

import "stellarbill-backend/internal/middleware"
import "stellarbill-backend/internal/featureflags"

// Method 1: Middleware (recommended for endpoints)
router.GET("/feature", middleware.FeatureFlag("my_feature"), handler)

// Method 2: With default value
router.GET("/feature", middleware.FeatureFlagWithDefault("my_feature", true), handler)

// Method 3: Direct check in code
if featureflags.IsEnabled("my_feature") {
    // Feature code here
}

// Method 4: Multiple flags requirement
router.GET("/feature", middleware.RequireAllFeatureFlags("flag1", "flag2"), handler)
router.GET("/feature", middleware.RequireAnyFeatureFlags("flag1", "flag2"), handler)

API reference

Base URL (local): http://localhost:8080

Method Path Feature Flag Required Description
GET /api/health None Health check
GET /api/plans plans_enabled (default: true) List billing plans
GET /api/subscriptions subscriptions_enabled (default: true) List subscriptions
GET /api/subscriptions/:id subscriptions_enabled (default: true) Get one subscription
GET /api/billing/new-flow new_billing_flow (default: false) New billing flow feature
GET /api/analytics/advanced advanced_analytics AND subscriptions_enabled Advanced analytics

All JSON responses. CORS allowed for * origin with common methods and headers.

Feature Flag Responses: When a feature flag blocks a request, the API returns:

{
  "error": "feature_unavailable",
  "message": "This feature is currently unavailable",
  "feature_flag": "flag_name"
}

Database migrations

Migrations live in migrations/ and are applied with:

go run ./cmd/migrate up

See docs/migrations.md for conventions and a production runbook.


CI / Quality gates

Every push and pull request runs the following checks automatically via GitHub Actions (.github/workflows/ci.yml):

Step Command
Build go build ./...
Vet go vet ./...
Test + coverage go test ./internal/... -covermode=atomic -coverpkg=./internal/...
Coverage threshold ./scripts/check-coverage.sh coverage.out 95 (≥ 95 % on internal/)

Coverage artifacts (coverage.out) are uploaded and retained for 14 days on every run.

Run checks locally before opening a PR

# 1. Build
go build ./...

# 2. Vet
go vet ./...

# 3. Test with coverage (internal packages only — cmd/server is the process entrypoint)
go test ./internal/... \
  -covermode=atomic \
  -coverpkg=./internal/... \
  -coverprofile=coverage.out \
  -count=1 \
  -timeout=60s

# 4. Enforce the 95 % threshold
./scripts/check-coverage.sh coverage.out 95

# 5. (Optional) Browse the HTML report
go tool cover -html=coverage.out

Why ./internal/... and not ./...?
cmd/server/main.go is the process entry point (main()). Go cannot instrument it as a unit-testable package, so it always reports 0 % and would drag the total below the threshold. All business logic lives in internal/, which is what the threshold enforces.

Security note: Never commit .env, JWT secrets, or database credentials. The CI workflow contains no secrets; configure them via your host's environment or a secrets manager.


Middleware order

Recommended order for the HTTP chain:

  1. recovery
  2. request-id
  3. logging
  4. cors
  5. rate-limit
  6. auth for protected routes only

Why this order:

  • recovery wraps the full chain so panics from downstream middleware and handlers are converted into structured 500 responses.
  • request-id runs early so every response and log line can carry the same correlation ID.
  • logging runs before short-circuiting middleware so failed auth, rate-limit, and panic-recovery responses are still logged.
  • cors handles preflight OPTIONS requests before rate limiting or auth rejects them.
  • rate-limit runs before auth on protected routes to reduce brute-force pressure on authentication logic.
  • auth should be attached only to protected groups so public endpoints like /api/health can remain reachable.

Behavior verified by tests:

  • Middleware entry and unwind order.
  • Request ID propagation across middleware and handlers.
  • Expected short-circuit responses for preflight, auth failures, rate limiting, and panic recovery.

Security notes:

  • X-Request-ID input is sanitized before reuse in logs and responses.
  • The in-memory rate limiter is process-local and keyed by client IP, so deployments behind proxies should ensure trusted forwarding headers are configured correctly.
  • CORS is currently configured as *; production deployments should replace that with an explicit frontend origin.
  • The sample auth middleware validates a bearer token against the configured secret and is intended as a lightweight guard for protected groups until full JWT validation is introduced.

Dependency Injection

Handlers are constructed with explicit dependencies instead of reaching into package-level state. That keeps startup wiring easy to review and makes unit tests cheap to write because services can be replaced with focused mocks.

Current boundaries:

  • internal/services defines the interfaces and default placeholder implementations used by the API.
  • internal/handlers validates constructor input and translates service results into HTTP responses.
  • internal/routes requires an injected handler bundle and returns an error on nil wiring instead of registering a partially working router.
  • cmd/server is responsible for composing concrete services and failing fast if startup wiring is incomplete.

Security notes:

  • Constructor validation prevents nil dependencies from reaching request handling paths, which avoids panic-driven denial of service during misconfigured startup.
  • Route registration returns errors for missing router or handler wiring so invalid startup state fails closed.
  • Service interfaces keep handlers decoupled from future storage implementations, making authorization and data-access checks easier to test in isolation.

Audit logging

  • Tamper-evident chain: Each audit entry is HMAC-signed with AUDIT_HMAC_SECRET and linked to the previous hash (chain-of-trust). Breaking or removing a line invalidates later hashes.
  • What gets logged: actor, action, target, outcome, request method/path, client IP, and any supplied metadata (e.g., attempts, reasons).
  • Redaction: Sensitive fields such as tokens, passwords, secrets, Authorization headers, and values that look like bearer/basic credentials are stored as [REDACTED].
  • Sink: Default sink writes JSON Lines to AUDIT_LOG_PATH (default audit.log). File permissions are 0600 on creation.
  • Admin example: POST /api/admin/purge demonstrates a sensitive operation. Success, partial success (?partial=1), denied access, and retry attempts are all audit-logged.
  • Auth failures: 401/403 responses are automatically logged via middleware, with headers redacted.

Testing

go test ./... -cover

Tests include redaction coverage, hash chaining, admin action logging, and middleware auth-failure logging. Coverage currently exceeds 95%.


Contributing (open source)

We welcome contributions from the community. Below is a short guide to get you from "first look" to "merged change".

Code of conduct

  • Be respectful and inclusive.
  • Focus on constructive feedback and clear, factual communication.

How to contribute

  1. Open an issue
    • Bug: describe what you did, what you expected, and what happened.
    • Feature: describe the goal and why it helps.
  2. Fork and clone
    • Fork the repo on GitHub, then clone your fork locally.
  3. Create a branch
    git checkout -b fix/your-fix   # or feature/your-feature
  4. Make changes
    • Follow existing style (format with go fmt).
    • Keep commits logical and messages clear (e.g. "Add validation for plan ID").
  5. Run checks
    go build ./...
    go vet ./...
    go fmt ./...
    Add or run tests if the project has them.
  6. Commit
    • Prefer small, atomic commits (one logical change per commit).
  7. Push and open a PR
    git push origin fix/your-fix
    • Open a Pull Request against the main branch.
    • Fill in the PR template (if any).
    • Link related issues.
    • Describe what you changed and why.
  8. Review
    • Address review comments. Maintainers will merge when everything looks good.

Development workflow

  • Use the Local setup steps to run the server.
  • Change code, restart the server (or use a tool like air for live reload if the project adds it).
  • Test with curl or the frontend that consumes this API.

Project standards

  • Go: go fmt, go vet, no unnecessary dependencies.
  • APIs: Keep JSON shape stable; document breaking changes in PRs.
  • Secrets: Never commit .env, keys, or passwords.

Project layout

stellabill-backend/
├── .github/
│   └── workflows/
│       └── ci.yml           # CI: build, vet, test, coverage threshold
├── cmd/
│   └── server/
│       └── main.go          # Entry point, Gin router, server start
├── docs/
│   ├── outbox-pattern.md    # Outbox pattern documentation
│   └── security-notes.md    # Security considerations
├── internal/
│   ├── config/
│   │   └── config.go        # Loads ENV, PORT, DATABASE_URL, JWT_SECRET, feature flags
│   ├── featureflags/
│   │   ├── featureflags.go   # Feature flag management system
│   │   └── featureflags_test.go # Unit tests for feature flags
│   ├── middleware/
│   │   ├── featureflags.go   # Feature flag middleware for endpoint gating
│   │   └── featureflags_test.go # Middleware tests
│   ├── handlers/
│   │   ├── health.go        # GET /api/health (includes outbox status)
│   │   ├── plans.go         # GET /api/plans
│   │   └── subscriptions.go # GET /api/subscriptions, /api/subscriptions/:id
│   ├── routes/
│       └── routes.go        # Registers routes and CORS middleware
│   └── worker/
│       ├── job.go           # Job model and JobStore interface
│       ├── store_memory.go  # In-memory JobStore implementation
│       ├── worker.go        # Background worker with scheduler loop
│       ├── executor.go      # Billing job executor
│       ├── scheduler.go     # Job scheduling utilities
│       ├── *_test.go        # Comprehensive test suite (95%+ coverage)
│       ├── README.md        # Worker documentation
│       ├── SECURITY.md      # Security analysis and threat model
│       └── INTEGRATION.md   # Integration guide with examples
├── go.mod
├── go.sum
├── .gitignore
├── README.md
└── WORKER_IMPLEMENTATION.md # Implementation summary

Security Considerations

Feature Flags Security

  • Environment Variables: Feature flags are configured via environment variables, which are secure and not committed to version control
  • Default Behavior: Unknown flags default to false for security (fail-safe)
  • No Dynamic Loading: Flags are loaded at startup only, preventing runtime injection attacks
  • Thread Safety: All flag operations are thread-safe with proper mutex locking
  • Validation: Invalid flag values are safely ignored and logged

Best Practices

  1. Production Flags: Always set explicit flag values in production; don't rely on defaults
  2. Secret Management: Use your cloud provider's secret manager for sensitive flag configurations
  3. Monitoring: Monitor flag usage and access patterns
  4. Audit Trail: Flag changes are tracked with timestamps for auditing
  5. Testing: Test both enabled and disabled states in your test suite

Testing Security

The feature flag system includes comprehensive tests covering:

  • Concurrent access and race conditions
  • Invalid input handling
  • Memory leak prevention
  • Environment variable injection attempts
  • Edge cases and error conditions

Run tests with: go test ./...


License

See the LICENSE file in the repository (if present). If none, assume proprietary until stated otherwise.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Go 97.3%
  • Shell 1.7%
  • Other 1.0%