2026-01-29 22:32:18 +00:00
|
|
|
# =============================================================================
|
|
|
|
|
# VEZA BACKEND API - ENVIRONMENT TEMPLATE
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# This is a template file. Copy to .env and fill in actual values.
|
|
|
|
|
# DO NOT commit .env with real secrets to Git!
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
# --- ENVIRONMENT ---
|
|
|
|
|
# Options: development, staging, production
|
|
|
|
|
APP_ENV=development
|
|
|
|
|
APP_PORT=8080
|
|
|
|
|
LOG_LEVEL=info
|
|
|
|
|
|
2026-02-11 21:19:09 +00:00
|
|
|
# --- DOMAIN (single source of truth) ---
|
|
|
|
|
# All service URLs and CORS origins derive from this in development.
|
|
|
|
|
# Change this + /etc/hosts to switch domain.
|
|
|
|
|
APP_DOMAIN=veza.fr
|
|
|
|
|
|
2026-01-29 22:32:18 +00:00
|
|
|
# --- DATABASE (REQUIRED) ---
|
2026-02-11 21:19:09 +00:00
|
|
|
# PostgreSQL connection string (host ports when using docker-compose: 15432)
|
|
|
|
|
# In Docker: postgres:5432 | On host: veza.fr:15432
|
|
|
|
|
DATABASE_URL=postgres://veza:password@veza.fr:15432/veza?sslmode=disable
|
2026-02-14 21:50:23 +00:00
|
|
|
# Optional: Read replica for scaling read-heavy workloads (same format as DATABASE_URL)
|
|
|
|
|
# DATABASE_READ_URL=postgres://veza:password@veza-read-replica:5432/veza?sslmode=disable
|
2026-01-29 22:32:18 +00:00
|
|
|
DATABASE_MAX_OPEN_CONNS=25
|
|
|
|
|
DATABASE_MAX_IDLE_CONNS=5
|
|
|
|
|
DATABASE_CONN_MAX_LIFETIME=5m
|
|
|
|
|
|
2026-03-05 18:22:31 +00:00
|
|
|
# --- JWT & AUTHENTICATION (v0.9.1 RS256) ---
|
|
|
|
|
# PREFERRED: RS256 with RSA keys (generate with scripts/generate-jwt-keys.sh)
|
|
|
|
|
# JWT_PRIVATE_KEY_PATH=/path/to/jwt-private.pem
|
|
|
|
|
# JWT_PUBLIC_KEY_PATH=/path/to/jwt-public.pem
|
|
|
|
|
# FALLBACK (dev only): JWT_SECRET must be at least 32 characters
|
2026-01-29 22:32:18 +00:00
|
|
|
JWT_SECRET=dev-secret-key-minimum-32-characters-long-for-testing-only
|
|
|
|
|
JWT_ISSUER=veza-api
|
2026-03-05 18:22:31 +00:00
|
|
|
JWT_AUDIENCE=veza-platform
|
2026-01-29 22:32:18 +00:00
|
|
|
JWT_ACCESS_TOKEN_DURATION=15m
|
|
|
|
|
JWT_REFRESH_TOKEN_DURATION=30d
|
|
|
|
|
|
|
|
|
|
# --- COOKIES ---
|
|
|
|
|
# Set to true in production for HTTPS-only cookies
|
|
|
|
|
COOKIE_SECURE=false
|
|
|
|
|
COOKIE_SAME_SITE=lax
|
|
|
|
|
COOKIE_DOMAIN=
|
|
|
|
|
|
|
|
|
|
# --- CORS (REQUIRED) ---
|
|
|
|
|
# Comma-separated list of allowed origins
|
2026-02-11 21:19:09 +00:00
|
|
|
# Development: http://veza.fr:5173,http://veza.fr:3000 (or your APP_DOMAIN)
|
2026-01-29 22:32:18 +00:00
|
|
|
# Production: https://app.veza.com,https://www.veza.com
|
2026-02-11 21:19:09 +00:00
|
|
|
CORS_ALLOWED_ORIGINS=http://veza.fr:5173,http://veza.fr:3000
|
2026-01-29 22:32:18 +00:00
|
|
|
|
|
|
|
|
# --- REDIS (REQUIRED for CSRF, rate limiting, cache) ---
|
2026-02-11 21:19:09 +00:00
|
|
|
# Redis (host port when using docker-compose: 16379)
|
|
|
|
|
# In Docker: redis:6379 | On host: veza.fr:16379
|
|
|
|
|
REDIS_URL=redis://veza.fr:16379
|
|
|
|
|
REDIS_ADDR=veza.fr:6379
|
2026-01-29 22:32:18 +00:00
|
|
|
REDIS_PASSWORD=
|
|
|
|
|
REDIS_DB=0
|
|
|
|
|
|
2026-02-11 21:19:09 +00:00
|
|
|
# --- RABBITMQ ---
|
|
|
|
|
# Enable message queue for async events (use veza:password, host port 15672 for docker-compose)
|
|
|
|
|
# In Docker: amqp://veza:password@rabbitmq:5672/ | On host: amqp://veza:password@veza.fr:15672/
|
|
|
|
|
RABBITMQ_ENABLE=true
|
|
|
|
|
RABBITMQ_URL=amqp://veza:password@veza.fr:15672/
|
2026-01-29 22:32:18 +00:00
|
|
|
|
|
|
|
|
# --- SENTRY (OPTIONAL - Recommended for production) ---
|
|
|
|
|
# Error tracking and monitoring
|
|
|
|
|
SENTRY_DSN=
|
|
|
|
|
SENTRY_ENVIRONMENT=development
|
|
|
|
|
SENTRY_SAMPLE_RATE_ERRORS=1.0
|
|
|
|
|
SENTRY_SAMPLE_RATE_TRANSACTIONS=0.1
|
|
|
|
|
|
|
|
|
|
# --- RATE LIMITING ---
|
|
|
|
|
RATE_LIMIT_ENABLED=true
|
|
|
|
|
RATE_LIMIT_REQUESTS_PER_SECOND=100
|
|
|
|
|
|
|
|
|
|
# --- FILE UPLOADS ---
|
|
|
|
|
UPLOAD_DIR=./uploads
|
|
|
|
|
ENABLE_CLAMAV=false
|
|
|
|
|
CLAMAV_REQUIRED=false
|
|
|
|
|
|
2026-02-14 20:45:15 +00:00
|
|
|
# --- HYPERSWITCH (PAYMENTS - OPTIONAL) ---
|
|
|
|
|
# Required for real payment processing. Leave empty to use simulated payments.
|
|
|
|
|
HYPERSWITCH_ENABLED=false
|
|
|
|
|
HYPERSWITCH_URL=http://veza.fr:18081
|
|
|
|
|
# From Hyperswitch Control Center (app.hyperswitch.io) > Settings > Developers
|
|
|
|
|
HYPERSWITCH_API_KEY=
|
|
|
|
|
# For webhook signature verification
|
|
|
|
|
HYPERSWITCH_WEBHOOK_SECRET=
|
|
|
|
|
# Checkout success redirect (used in return_url)
|
|
|
|
|
CHECKOUT_SUCCESS_URL=http://veza.fr:5173/purchases
|
|
|
|
|
|
2026-02-23 21:09:23 +00:00
|
|
|
# --- STRIPE CONNECT (SELLER PAYOUT - OPTIONAL) ---
|
|
|
|
|
# Required for seller payout (balance, onboarding, transfers).
|
|
|
|
|
# Get keys from Stripe Dashboard > Connect > Settings
|
|
|
|
|
STRIPE_CONNECT_ENABLED=false
|
|
|
|
|
# Secret key for server-side Stripe API calls (sk_test_xxx or sk_live_xxx)
|
|
|
|
|
STRIPE_SECRET_KEY=
|
|
|
|
|
# Webhook secret for Connect events (whsec_xxx)
|
|
|
|
|
STRIPE_CONNECT_WEBHOOK_SECRET=
|
|
|
|
|
|
2026-01-29 22:32:18 +00:00
|
|
|
# --- EXTERNAL SERVICES (OPTIONAL) ---
|
2026-02-11 21:19:09 +00:00
|
|
|
STREAM_SERVER_URL=http://veza.fr:8082
|
|
|
|
|
# Must match stream server INTERNAL_API_KEY for /internal/jobs/transcode (P1.1.2)
|
|
|
|
|
STREAM_SERVER_INTERNAL_API_KEY=
|
|
|
|
|
CHAT_SERVER_URL=http://veza.fr:8081
|
|
|
|
|
|
|
|
|
|
# --- FRONTEND URL ---
|
|
|
|
|
# Used for password reset links, email templates, etc.
|
|
|
|
|
FRONTEND_URL=http://veza.fr:5173
|
|
|
|
|
|
|
|
|
|
# --- BASE URL ---
|
|
|
|
|
# Public base URL of this backend (used for OAuth callbacks, etc.)
|
|
|
|
|
BASE_URL=http://veza.fr:8080
|
2026-01-29 22:32:18 +00:00
|
|
|
|
chore(release): v1.0.5.1 — dev SMTP ergonomics hotfix
A fresh clone + `cp veza-backend-api/.env.template .env` + `make dev-full`
booted the backend with `SMTP_HOST=""` — `EmailService.sendEmail` short-
circuits to log-only when the host is empty, so `register` + `password
reset` produced users stuck with no way to verify (or recover) in dev,
and the smoke test caught MailHog empty despite the service being up.
- `.env.template` now ships MailHog-ready defaults (`localhost:1025`,
UI on `:8025`, `FROM_EMAIL=no-reply@veza.local`) so a bare clone +
copy gives a working register flow. Comment rewritten to point at
both the dev path and the prod override.
- Also exports duplicate variable names (`SMTP_USERNAME`, `SMTP_FROM`,
`SMTP_FROM_NAME`) read by `internal/email/sender.go`. The two email
services in-tree disagree on env schema (`SMTP_USER` vs
`SMTP_USERNAME`, `FROM_EMAIL` vs `SMTP_FROM`, `FROM_NAME` vs
`SMTP_FROM_NAME`); until v1.0.6 reconciles them, both sets are
populated so whichever path fires finds its names.
Pure config hotfix. No code change, no migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:16:54 +00:00
|
|
|
# --- EMAIL (REQUIRED for registration + password reset) ---
|
|
|
|
|
# Local dev: MailHog ships with `make infra-up-dev` — the defaults below
|
|
|
|
|
# point at it on localhost:1025, with the web UI at http://localhost:8025
|
|
|
|
|
# to inspect captured emails.
|
|
|
|
|
# Production: override with your SMTP provider (SendGrid, AWS SES, etc.).
|
|
|
|
|
#
|
refactor(backend,infra): unify SMTP env schema on canonical SMTP_* names
Third item of the v1.0.6 backlog. The v1.0.5.1 hotfix surfaced that two
email paths in-tree read *different* env vars for the same configuration:
internal/email/sender.go internal/services/email_service.go
SMTP_USERNAME SMTP_USER
SMTP_FROM FROM_EMAIL
SMTP_FROM_NAME FROM_NAME
The hotfix worked around it by exporting both sets in `.env.template`.
This commit reconciles them onto a single schema so the workaround can
go away.
Changes
* `internal/email/sender.go` is now the single loader. The canonical
names (`SMTP_USERNAME`, `SMTP_FROM`, `SMTP_FROM_NAME`) are read
first; the legacy names (`SMTP_USER`, `FROM_EMAIL`, `FROM_NAME`)
stay supported as a migration fallback that logs a structured
deprecation warning ("remove_in: v1.1.0"). Canonical always wins
over deprecated — no silent precedence flip.
* `NewSMTPEmailSender` callers keep working unchanged; a new
`LoadSMTPConfigFromEnvWithLogger(*zap.Logger)` variant lets callers
opt into the warning stream.
* `internal/services/email_service.go` drops its six inline
`os.Getenv` reads and delegates to the shared loader, so
`AuthService.Register` and `RequestPasswordReset` now see exactly
the same config as the async job worker.
* `.env.template`: the duplicate (SMTP_USER + FROM_EMAIL + FROM_NAME)
block added in v1.0.5.1 is removed — only the canonical SMTP_*
names ship for new contributors.
* `docker-compose.yml` (backend-api service): FROM_EMAIL / FROM_NAME
renamed to SMTP_FROM / SMTP_FROM_NAME to match the canonical schema.
* No Host/Port default injected in the loader. If SMTP_HOST is
empty, callers see Host=="" and log-only (historic dev behavior).
Dev defaults (MailHog localhost:1025) live in `.env.template`, so
a fresh clone still works; a misconfigured prod pod fails loud
instead of silently dialing localhost.
Tests
* 5 new Go tests in `internal/email/smtp_env_test.go`: empty-env
returns empty config; canonical names read directly; deprecated
names fall back (one warning per var); canonical wins over
deprecated silently; nil logger is allowed.
* Existing `TestLoadSMTPConfigFromEnv`, `TestSMTPEmailSender_Send`,
and every auth/services package remained green (40+ packages).
Import-cycle note: the loader deliberately lives in `internal/email`,
not `internal/config`, because `internal/config` already depends on
`internal/email` (wiring `EmailSender` at boot). Putting the loader in
`email` keeps the dependency flow one-way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:44:09 +00:00
|
|
|
# v1.0.6: unified on canonical SMTP_* names. The legacy SMTP_USER /
|
|
|
|
|
# FROM_EMAIL / FROM_NAME are still accepted as a deprecation fallback
|
|
|
|
|
# (backend logs a warning on use; removed in v1.1.0).
|
chore(release): v1.0.5.1 — dev SMTP ergonomics hotfix
A fresh clone + `cp veza-backend-api/.env.template .env` + `make dev-full`
booted the backend with `SMTP_HOST=""` — `EmailService.sendEmail` short-
circuits to log-only when the host is empty, so `register` + `password
reset` produced users stuck with no way to verify (or recover) in dev,
and the smoke test caught MailHog empty despite the service being up.
- `.env.template` now ships MailHog-ready defaults (`localhost:1025`,
UI on `:8025`, `FROM_EMAIL=no-reply@veza.local`) so a bare clone +
copy gives a working register flow. Comment rewritten to point at
both the dev path and the prod override.
- Also exports duplicate variable names (`SMTP_USERNAME`, `SMTP_FROM`,
`SMTP_FROM_NAME`) read by `internal/email/sender.go`. The two email
services in-tree disagree on env schema (`SMTP_USER` vs
`SMTP_USERNAME`, `FROM_EMAIL` vs `SMTP_FROM`, `FROM_NAME` vs
`SMTP_FROM_NAME`); until v1.0.6 reconciles them, both sets are
populated so whichever path fires finds its names.
Pure config hotfix. No code change, no migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:16:54 +00:00
|
|
|
SMTP_HOST=localhost
|
|
|
|
|
SMTP_PORT=1025
|
2026-01-29 22:32:18 +00:00
|
|
|
SMTP_USERNAME=
|
|
|
|
|
SMTP_PASSWORD=
|
chore(release): v1.0.5.1 — dev SMTP ergonomics hotfix
A fresh clone + `cp veza-backend-api/.env.template .env` + `make dev-full`
booted the backend with `SMTP_HOST=""` — `EmailService.sendEmail` short-
circuits to log-only when the host is empty, so `register` + `password
reset` produced users stuck with no way to verify (or recover) in dev,
and the smoke test caught MailHog empty despite the service being up.
- `.env.template` now ships MailHog-ready defaults (`localhost:1025`,
UI on `:8025`, `FROM_EMAIL=no-reply@veza.local`) so a bare clone +
copy gives a working register flow. Comment rewritten to point at
both the dev path and the prod override.
- Also exports duplicate variable names (`SMTP_USERNAME`, `SMTP_FROM`,
`SMTP_FROM_NAME`) read by `internal/email/sender.go`. The two email
services in-tree disagree on env schema (`SMTP_USER` vs
`SMTP_USERNAME`, `FROM_EMAIL` vs `SMTP_FROM`, `FROM_NAME` vs
`SMTP_FROM_NAME`); until v1.0.6 reconciles them, both sets are
populated so whichever path fires finds its names.
Pure config hotfix. No code change, no migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:16:54 +00:00
|
|
|
SMTP_FROM=no-reply@veza.local
|
|
|
|
|
SMTP_FROM_NAME=Veza (dev)
|
2026-01-29 22:32:18 +00:00
|
|
|
|
|
|
|
|
# --- MONITORING (OPTIONAL) ---
|
|
|
|
|
PROMETHEUS_URL=
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# VALIDATION RULES
|
|
|
|
|
# =============================================================================
|
|
|
|
|
#
|
|
|
|
|
# REQUIRED (app will not start without these):
|
|
|
|
|
# - DATABASE_URL
|
|
|
|
|
# - JWT_SECRET (min 32 chars)
|
|
|
|
|
# - REDIS_URL or REDIS_ADDR
|
|
|
|
|
# - CORS_ALLOWED_ORIGINS (can be empty for strict mode)
|
|
|
|
|
#
|
|
|
|
|
# RECOMMENDED for production:
|
|
|
|
|
# - SENTRY_DSN
|
|
|
|
|
# - COOKIE_SECURE=true
|
|
|
|
|
# - COOKIE_SAME_SITE=strict
|
|
|
|
|
#
|
|
|
|
|
# OPTIONAL:
|
|
|
|
|
# - RABBITMQ_* (if async events not used)
|
|
|
|
|
# - SMTP_* (if email not used)
|
|
|
|
|
# - CLAMAV_* (if file scanning not used)
|
|
|
|
|
#
|
|
|
|
|
# =============================================================================
|