Files
nilesh.pandClaude Opus 4.8 4d3eb18a6c Initial commit: TravelHub multi-tenant flight booking platform
Angular + Go + PostgreSQL sample with strict per-tenant isolation.

- Backend: Go API (basic auth, tenant-scoped repository layer, pgx pool)
- Frontend: Angular 22 SPA (search, checkout, bookings, dashboard)
- Database: schema, seed data and demo bookings migrations
- Tests: 311 across Go (integration), Angular (unit) and Playwright (e2e),
  plus a generated evidence report with PDF export
- Security: whitebox VAPT report under security/

Secrets (.env, tokens, DB credentials) are git-ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-13 11:38:01 +05:30

137 lines
3.2 KiB
Go

// Package config loads runtime settings from the environment, optionally
// seeded from a .env file sitting next to the binary or at the module root.
package config
import (
"bufio"
"fmt"
"net"
"net/url"
"os"
"path/filepath"
"strings"
)
// Config holds everything the API needs to start.
type Config struct {
DatabaseURL string
Port string
CORSOrigins []string
}
// Load reads .env (if present) into the process environment, then builds a
// Config. Real environment variables always win over .env entries, so
// container and CI overrides behave the way people expect.
func Load() (*Config, error) {
LoadDotEnv()
cfg := &Config{
Port: envOr("PORT", "8080"),
CORSOrigins: splitAndTrim(envOr("CORS_ORIGINS", "http://localhost:4200")),
}
if raw := os.Getenv("DATABASE_URL"); raw != "" {
cfg.DatabaseURL = raw
} else {
user := envOr("DB_USER", "postgres")
pass := os.Getenv("DB_PASSWORD")
host := envOr("DB_HOST", "localhost")
port := envOr("DB_PORT", "5432")
name := envOr("DB_NAME", "travelhub")
ssl := envOr("DB_SSLMODE", "disable")
if pass == "" {
return nil, fmt.Errorf("database password is not set: define DATABASE_URL or DB_PASSWORD (see .env.example)")
}
// url.UserPassword escapes credentials for us, which matters because
// passwords routinely contain @ : / and other reserved characters.
cfg.DatabaseURL = (&url.URL{
Scheme: "postgres",
User: url.UserPassword(user, pass),
Host: net.JoinHostPort(host, port),
Path: "/" + name,
RawQuery: "sslmode=" + ssl,
}).String()
}
return cfg, nil
}
// LoadDotEnv walks up from the working directory looking for a .env file and
// applies any keys that are not already set in the environment. Load calls it;
// it is exported so tests can pick up the same local credentials without
// building a whole Config.
func LoadDotEnv() {
dir, err := os.Getwd()
if err != nil {
return
}
for i := 0; i < 4; i++ {
path := filepath.Join(dir, ".env")
if applyEnvFile(path) {
return
}
parent := filepath.Dir(dir)
if parent == dir {
return
}
dir = parent
}
}
// applyEnvFile reads one .env file. It reports whether the file existed.
func applyEnvFile(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, found := strings.Cut(line, "=")
if !found {
continue
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
// Allow quoted values so passwords may contain leading/trailing spaces.
if len(value) >= 2 {
if (value[0] == '"' && value[len(value)-1] == '"') ||
(value[0] == '\'' && value[len(value)-1] == '\'') {
value = value[1 : len(value)-1]
}
}
if _, exists := os.LookupEnv(key); !exists {
os.Setenv(key, value)
}
}
return true
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func splitAndTrim(s string) []string {
parts := strings.Split(s, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}