- config/db/storage/auth/router/perms: eQMS 규약 미러링, 권한 2-tier (관리자 전체 / 구성원 본인·신청만), oauth2-proxy 헤더 인증 + DEV_AUTH mock - 모델: 구성원/부서, 근무(출퇴근·휴가·공가·초과), 프로젝트(회사/제품/버전· 작업자portion·담당자·태스크·계약·첨부·분할입금), 인센티브(설정·단계· 유저배분·분기정산), 회계(거래·세금) - internal/worktime: 근로기준법 월 집계 엔진 - internal/incentive: BE/non-BE × 계약금/중도금/잔금 3단계 계산 + 시뮬레이션 - 시드 데이터, Go 멀티스테이지 Dockerfile - ADMIN_GROUPS 기본값 'admin' (전 내부 앱 공통 그룹) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ce4023ca59
commit
f83724b995
@@ -0,0 +1,121 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config holds all runtime configuration loaded from environment variables.
|
||||
//
|
||||
// Variable names follow the cluster/CI contract documented in .env.sample
|
||||
// (PGHOST/PGUSER/…, AWS_ACCESS_KEY_ID/…, S3_ENDPOINT/S3_BUCKET/S3_PREFIX) so the
|
||||
// same binary runs unchanged in the Special Partners infra. Local docker-compose
|
||||
// supplies the same variables. This mirrors the sister eQMS (Mallard) service.
|
||||
type Config struct {
|
||||
Port string
|
||||
DatabaseURL string
|
||||
S3Endpoint string
|
||||
S3PublicEndpoint string
|
||||
S3Region string
|
||||
S3Bucket string
|
||||
S3Prefix string
|
||||
S3AccessKey string
|
||||
S3SecretKey string
|
||||
DevAuth bool
|
||||
SeedData bool
|
||||
// AdminGroups are the Keycloak groups whose members are super-admins
|
||||
// (manage everything across the company: incentive console, accounting,
|
||||
// approvals, all member/project data).
|
||||
AdminGroups []string
|
||||
}
|
||||
|
||||
func env(key, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// firstEnv returns the first non-empty env var among keys, else def.
|
||||
func firstEnv(def string, keys ...string) string {
|
||||
for _, k := range keys {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// Load reads configuration from the environment, applying sane local defaults
|
||||
// so `go run ./cmd/server` works without docker-compose.
|
||||
func Load() Config {
|
||||
return Config{
|
||||
Port: env("PORT", "8080"),
|
||||
DatabaseURL: databaseURL(),
|
||||
S3Endpoint: withScheme(env("S3_ENDPOINT", "http://localhost:9000")),
|
||||
S3PublicEndpoint: publicEndpoint(),
|
||||
S3Region: env("S3_REGION", "us-east-1"),
|
||||
S3Bucket: env("S3_BUCKET", "spin"),
|
||||
S3Prefix: env("S3_PREFIX", ""),
|
||||
// Cluster secrets arrive as AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY;
|
||||
// the S3_* aliases are kept for convenience/back-compat.
|
||||
S3AccessKey: firstEnv("minioadmin", "AWS_ACCESS_KEY_ID", "S3_ACCESS_KEY"),
|
||||
S3SecretKey: firstEnv("minioadmin", "AWS_SECRET_ACCESS_KEY", "S3_SECRET_KEY"),
|
||||
DevAuth: env("DEV_AUTH", "true") != "false",
|
||||
// Sample data is seeded only when SEED is truthy (default). Production
|
||||
// sets SEED=false so the cluster DB stays clean.
|
||||
SeedData: env("SEED", "true") != "false",
|
||||
// Super-admin Keycloak groups (comma-separated). Default: admin
|
||||
// (shared group name across all internal apps, not app-specific).
|
||||
AdminGroups: splitCSV(env("ADMIN_GROUPS", "admin")),
|
||||
}
|
||||
}
|
||||
|
||||
// splitCSV splits a comma-separated list, trimming whitespace and dropping
|
||||
// empty entries.
|
||||
func splitCSV(raw string) []string {
|
||||
var out []string
|
||||
for _, p := range strings.Split(raw, ",") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// databaseURL prefers an explicit DATABASE_URL (override), otherwise assembles a
|
||||
// DSN from the discrete PG* variables injected from the DB-creds Secret.
|
||||
func databaseURL() string {
|
||||
if v := os.Getenv("DATABASE_URL"); v != "" {
|
||||
return v
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: "postgres",
|
||||
User: url.UserPassword(env("PGUSER", "spin"), env("PGPASSWORD", "spin")),
|
||||
Host: env("PGHOST", "localhost") + ":" + env("PGPORT", "5432"),
|
||||
Path: "/" + env("PGDATABASE", "spin"),
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("sslmode", env("PGSSLMODE", "disable"))
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// publicEndpoint is the browser-reachable S3 host used for presigned URLs.
|
||||
// Falls back to the in-cluster endpoint when not separately provided.
|
||||
func publicEndpoint() string {
|
||||
if v := os.Getenv("S3_PUBLIC_ENDPOINT"); v != "" {
|
||||
return withScheme(v)
|
||||
}
|
||||
return withScheme(env("S3_ENDPOINT", "http://localhost:9000"))
|
||||
}
|
||||
|
||||
// withScheme guarantees an http(s):// prefix so the AWS SDK accepts the endpoint
|
||||
// (the .env.sample shows bare hosts like "localhost").
|
||||
func withScheme(s string) string {
|
||||
if s == "" || strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") {
|
||||
return s
|
||||
}
|
||||
return "http://" + s
|
||||
}
|
||||
Reference in New Issue
Block a user