Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
AUTH_COOKIE_SAMESITE=lax
AUTH_COOKIE_DOMAIN=
AUTH_COOKIE_SECURE=
INVOICE_INTAKE_BASE_URL=http://0.0.0.0:8000
INVOICE_INTAKE_API_KEY=sk_test_vez
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ Health check:
curl http://localhost:8080/health
```

## Invoice Intake Configuration

The invoice review handoff route proxies to the separate `invoice-intake` service. Configure these variables in `bool-api` before using `/api/v1/invoice-intake/jobs/:decodeJobId/handoff`:

```bash
INVOICE_INTAKE_BASE_URL=http://localhost:8081
INVOICE_INTAKE_API_KEY=dev-secret
```

`INVOICE_INTAKE_BASE_URL` must point to the running invoice-intake service. `INVOICE_INTAKE_API_KEY` must match the API key configured in that service. Restart `bool-api` after changing either value so the new configuration is loaded.

## Database Migrations

BOOL uses versioned SQL migrations in `migrations/*.up.sql`, applied through Bun's migration runner. The API can run pending migrations before the Gin server starts:
Expand Down Expand Up @@ -956,4 +967,4 @@ In-app notifications drive user attention for actionable events (status changes,

**Rule:** Notifications = user attention. Audit logs = historical record. Analytics = behaviour/stats.

See `internal/notifications/messages.go`, `internal/services/notification_helpers.go`, and `internal/services/procurement_request.go` for the current implementation. The frontend notification bell renders the server-provided localized `title`/`body`.
See `internal/notifications/messages.go`, `internal/services/notification_helpers.go`, and `internal/services/procurement_request.go` for the current implementation. The frontend notification bell renders the server-provided localized `title`/`body`.
2 changes: 1 addition & 1 deletion cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func main() {
router.Use(gin.Recovery())
router.Use(middleware.CORSMiddleware(cfg.CORSAllowedOrigins))
cookieOptions := handlers.NewAuthCookieOptions(cfg.AuthCookieSameSite, cfg.AuthCookieDomain, cfg.AuthCookieSecure)
routes.RegisterPublicRoutes(router, db, redisClient, translator, logger, cfg.AppEnv, cfg.JWTSecret, cfg.AllowDevAuthHeaders, authEmailSender, cookieOptions)
routes.RegisterPublicRoutes(router, db, redisClient, translator, logger, cfg.AppEnv, cfg.JWTSecret, cfg.AllowDevAuthHeaders, authEmailSender, cookieOptions, cfg.InvoiceIntakeBaseURL, cfg.InvoiceIntakeAPIKey)

if err := router.Run(cfg.HTTPAddr); err != nil {
logger.Fatal("server stopped", bootstrap.ZapError(err))
Expand Down
68 changes: 36 additions & 32 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,25 @@ import (

// Config holds application settings loaded from environment and config files.
type Config struct {
AppName string
AppEnv string
HTTPAddr string
DatabaseURL string
TestDatabaseURL string
RedisURL string
RedisAddr string
RedisEnabled bool
JWTSecret string
SendGridAPIKey string
AuthEmailFrom string
AllowDevAuthHeaders bool
AutoRunMigrations bool
CORSAllowedOrigins []string
AuthCookieSameSite string
AuthCookieDomain string
AuthCookieSecure *bool
AppName string
AppEnv string
HTTPAddr string
DatabaseURL string
TestDatabaseURL string
RedisURL string
RedisAddr string
RedisEnabled bool
JWTSecret string
SendGridAPIKey string
AuthEmailFrom string
AllowDevAuthHeaders bool
AutoRunMigrations bool
CORSAllowedOrigins []string
AuthCookieSameSite string
AuthCookieDomain string
AuthCookieSecure *bool
InvoiceIntakeBaseURL string
InvoiceIntakeAPIKey string
}

// Load initializes Viper and returns application configuration.
Expand All @@ -37,21 +39,23 @@ func Load() (*Config, error) {
_ = viper.ReadInConfig()

cfg := &Config{
AppName: viper.GetString("APP_NAME"),
AppEnv: viper.GetString("APP_ENV"),
HTTPAddr: viper.GetString("HTTP_ADDR"),
DatabaseURL: viper.GetString("DATABASE_URL"),
TestDatabaseURL: viper.GetString("TEST_DATABASE_URL"),
RedisURL: viper.GetString("REDIS_URL"),
RedisAddr: viper.GetString("REDIS_ADDR"),
RedisEnabled: viper.GetBool("REDIS_ENABLED"),
JWTSecret: viper.GetString("JWT_SECRET"),
SendGridAPIKey: viper.GetString("SENDGRID_API_KEY"),
AuthEmailFrom: viper.GetString("AUTH_EMAIL_FROM"),
AllowDevAuthHeaders: viper.GetBool("ALLOW_DEV_AUTH_HEADERS"),
CORSAllowedOrigins: stringList("CORS_ALLOWED_ORIGINS"),
AuthCookieSameSite: strings.ToLower(strings.TrimSpace(viper.GetString("AUTH_COOKIE_SAMESITE"))),
AuthCookieDomain: strings.TrimSpace(viper.GetString("AUTH_COOKIE_DOMAIN")),
AppName: viper.GetString("APP_NAME"),
AppEnv: viper.GetString("APP_ENV"),
HTTPAddr: viper.GetString("HTTP_ADDR"),
DatabaseURL: viper.GetString("DATABASE_URL"),
TestDatabaseURL: viper.GetString("TEST_DATABASE_URL"),
RedisURL: viper.GetString("REDIS_URL"),
RedisAddr: viper.GetString("REDIS_ADDR"),
RedisEnabled: viper.GetBool("REDIS_ENABLED"),
JWTSecret: viper.GetString("JWT_SECRET"),
SendGridAPIKey: viper.GetString("SENDGRID_API_KEY"),
AuthEmailFrom: viper.GetString("AUTH_EMAIL_FROM"),
AllowDevAuthHeaders: viper.GetBool("ALLOW_DEV_AUTH_HEADERS"),
CORSAllowedOrigins: stringList("CORS_ALLOWED_ORIGINS"),
AuthCookieSameSite: strings.ToLower(strings.TrimSpace(viper.GetString("AUTH_COOKIE_SAMESITE"))),
AuthCookieDomain: strings.TrimSpace(viper.GetString("AUTH_COOKIE_DOMAIN")),
InvoiceIntakeBaseURL: strings.TrimRight(strings.TrimSpace(viper.GetString("INVOICE_INTAKE_BASE_URL")), "/"),
InvoiceIntakeAPIKey: strings.TrimSpace(viper.GetString("INVOICE_INTAKE_API_KEY")),
}

// Auto-run migrations by default in non-production (safe because of the
Expand Down
2 changes: 2 additions & 0 deletions config/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ func setDefaults() {
viper.SetDefault("CORS_ALLOWED_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000")
viper.SetDefault("AUTH_COOKIE_SAMESITE", "lax")
viper.SetDefault("AUTH_COOKIE_DOMAIN", "")
viper.SetDefault("INVOICE_INTAKE_BASE_URL", "http://127.0.0.1:8000")
viper.SetDefault("INVOICE_INTAKE_API_KEY", "sk_test_vez")
}
Loading