thread-master/apps/backend/internal/config/config.go

240 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package config
import (
"os"
"strings"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/rest"
)
// Config — go-zero 風格RestConf + CacheRedis + Mongo URI.
type Config struct {
rest.RestConf
// CacheRedis is go-zero cache.CacheConf (feeds monc Redis 快取).
CacheRedis cache.CacheConf
Mongo struct {
URI string
Database string
}
Auth struct {
AccessSecret string
AccessExpire int64
RefreshSecret string
RefreshExpire int64
}
Platform struct {
// AIKey — legacy alias for XAIKey
AIKey string `json:",optional"`
// XAIKey — platform BYOK for xAI (api.x.ai)
XAIKey string `json:",optional"`
// OpenCodeKey — platform BYOK for OpenCode Go (opencode.ai/zen/go)
OpenCodeKey string `json:",optional"`
ExaKey string `json:",optional"`
ThreadsAppId string `json:",optional"`
ThreadsAppSecret string `json:",optional"`
}
Worker struct {
ID string `json:",default=worker-1"`
PollIntervalMs int `json:",default=2000"`
}
// Bcrypt cost (stand-alone Bcrypt.Cost)
Bcrypt struct {
Cost int `json:",default=10"`
}
// OAuth third-party (optional; empty → mock path in callback)
OAuth struct {
GoogleClientID string `json:",optional"`
LineClientID string `json:",optional"`
LineClientSecret string `json:",optional"`
LineRedirectURI string `json:",optional"`
}
// Mail — 驗證碼重設密碼寄信SMTP空 Host 則只打 log + 可回 mock_code
Mail struct {
Sender string `json:",optional"` // e.g. "Harbor Desk <noreply@example.com>"
SMTP struct {
Host string `json:",optional"`
Port int `json:",default=587"`
User string `json:",optional"`
Password string `json:",optional"`
} `json:",optional"`
// DevExposeCode: API 回 mock_code本機沒信箱時用。正式請 false。
DevExposeCode bool `json:",default=true"`
} `json:",optional"`
// PublicWebBase — 前端 origin用於重設密碼信內連結logo勿尾斜線
// 例http://127.0.0.1:5173 或 https://threads-tool-dev.30cm.net
PublicWebBase string `json:",optional,default=http://127.0.0.1:5173"`
// PublicAPIBase — 對外 API originThreads OAuth redirect_uri 必須 https 公開網址)
// 空則:若 PublicWebBase 為 https 則同 host否則回落 http://127.0.0.1:Port
// 例https://threads-tool-dev.30cm.net
PublicAPIBase string `json:",optional"`
// Brand — 郵件 chromehermesLogoURL 空則用 PublicWebBase + /brand-mark.jpg
Brand struct {
Name string `json:",optional,default=Harbor Desk"`
LogoURL string `json:",optional"` // absolute URL
Copyright string `json:",optional,default=© Harbor Desk"`
} `json:",optional"`
// ObjectStorage — 頭像等二進位;**僅 MinIO / S3**(不落本機磁碟)
ObjectStorage struct {
Endpoint string `json:",optional"` // 必填e.g. http://127.0.0.1:9000
Region string `json:",optional,default=us-east-1"`
Bucket string `json:",optional,default=haixun-assets"`
AccessKey string `json:",optional"`
SecretKey string `json:",optional"`
UsePathStyle bool `json:",optional,default=true"`
PublicBaseURL string `json:",optional"` // 公開讀取前綴,勿尾斜線
} `json:",optional"`
}
// ApplyEnv overlays secrets from environment.
func (c *Config) ApplyEnv() {
if v := os.Getenv("MONGO_URI"); v != "" {
c.Mongo.URI = v
}
if v := os.Getenv("MONGO_DATABASE"); v != "" {
c.Mongo.Database = v
}
if v := os.Getenv("REDIS_HOST"); v != "" && len(c.CacheRedis) > 0 {
c.CacheRedis[0].RedisConf.Host = v
}
if len(c.CacheRedis) > 0 {
// monc 快取 Redis 帳密(本機 infra 常開 requirepass
if v := os.Getenv("REDIS_PASSWORD"); v != "" {
c.CacheRedis[0].RedisConf.Pass = v
} else if v := os.Getenv("HAIXUN_REDIS_PASSWORD"); v != "" {
c.CacheRedis[0].RedisConf.Pass = v
}
if v := os.Getenv("HAIXUN_REDIS_ADDR"); v != "" {
c.CacheRedis[0].RedisConf.Host = v
}
}
if v := os.Getenv("AUTH_ACCESS_SECRET"); v != "" {
c.Auth.AccessSecret = v
}
if v := os.Getenv("AUTH_REFRESH_SECRET"); v != "" {
c.Auth.RefreshSecret = v
}
if v := os.Getenv("PLATFORM_AI_KEY"); v != "" {
c.Platform.AIKey = v
}
if v := firstEnv("PLATFORM_XAI_KEY", "XAI_API_KEY"); v != "" {
c.Platform.XAIKey = v
}
if v := firstEnv("PLATFORM_OPENCODE_KEY", "OPENCODE_API_KEY", "OPENCODE_GO_API_KEY"); v != "" {
c.Platform.OpenCodeKey = v
}
// legacy AIKey → XAI when XAI empty
if c.Platform.XAIKey == "" && c.Platform.AIKey != "" {
c.Platform.XAIKey = c.Platform.AIKey
}
if v := os.Getenv("PLATFORM_EXA_KEY"); v != "" {
c.Platform.ExaKey = v
}
if v := firstEnv("THREADS_APP_ID", "HAIXUN_THREADS_APP_ID"); v != "" {
c.Platform.ThreadsAppId = v
}
if v := firstEnv("THREADS_APP_SECRET", "HAIXUN_THREADS_APP_SECRET"); v != "" {
c.Platform.ThreadsAppSecret = v
}
if v := os.Getenv("WORKER_ID"); v != "" {
c.Worker.ID = v
}
if v := os.Getenv("PORT"); v != "" {
var p int
for _, ch := range v {
if ch < '0' || ch > '9' {
return
}
p = p*10 + int(ch-'0')
}
if p > 0 {
c.Port = p
}
}
// Mail / SMTPMAIL_* 優先HAIXUN_SMTP_* 與 old/infra 相容)
if v := firstEnv("MAIL_SENDER", "HAIXUN_SMTP_FROM"); v != "" {
c.Mail.Sender = v
}
if v := firstEnv("MAIL_SMTP_HOST", "HAIXUN_SMTP_HOST"); v != "" {
c.Mail.SMTP.Host = v
}
if v := firstEnv("MAIL_SMTP_USER", "HAIXUN_SMTP_USERNAME"); v != "" {
c.Mail.SMTP.User = v
}
if v := firstEnv("MAIL_SMTP_PASSWORD", "HAIXUN_SMTP_PASSWORD"); v != "" {
c.Mail.SMTP.Password = v
}
if v := firstEnv("MAIL_SMTP_PORT", "HAIXUN_SMTP_PORT"); v != "" {
if p := parsePort(v); p > 0 {
c.Mail.SMTP.Port = p
}
}
if v := os.Getenv("MAIL_DEV_EXPOSE_CODE"); v == "0" || v == "false" || v == "FALSE" {
c.Mail.DevExposeCode = false
} else if v == "1" || v == "true" || v == "TRUE" {
c.Mail.DevExposeCode = true
}
if v := firstEnv("PUBLIC_WEB_BASE", "HAIXUN_PUBLIC_WEB_BASE"); v != "" {
c.PublicWebBase = strings.TrimRight(v, "/")
}
if v := firstEnv("PUBLIC_API_BASE", "HAIXUN_PUBLIC_API_BASE"); v != "" {
c.PublicAPIBase = strings.TrimRight(v, "/")
}
if v := os.Getenv("MAIL_BRAND_NAME"); v != "" {
c.Brand.Name = v
}
if v := firstEnv("MAIL_LOGO_URL", "HAIXUN_MAIL_LOGO_URL"); v != "" {
c.Brand.LogoURL = v
}
if v := os.Getenv("MAIL_COPYRIGHT"); v != "" {
c.Brand.Copyright = v
}
// Object storage (HAIXUN_STORAGE_S3_* 與 old/infra 相容)
if v := firstEnv("OBJECT_STORAGE_ENDPOINT", "HAIXUN_STORAGE_S3_ENDPOINT"); v != "" {
c.ObjectStorage.Endpoint = v
}
if v := firstEnv("OBJECT_STORAGE_REGION", "HAIXUN_STORAGE_S3_REGION"); v != "" {
c.ObjectStorage.Region = v
}
if v := firstEnv("OBJECT_STORAGE_BUCKET", "HAIXUN_STORAGE_S3_BUCKET"); v != "" {
c.ObjectStorage.Bucket = v
}
if v := firstEnv("OBJECT_STORAGE_ACCESS_KEY", "HAIXUN_STORAGE_S3_ACCESS_KEY", "MINIO_ROOT_USER"); v != "" {
c.ObjectStorage.AccessKey = v
}
if v := firstEnv("OBJECT_STORAGE_SECRET_KEY", "HAIXUN_STORAGE_S3_SECRET_KEY", "MINIO_ROOT_PASSWORD"); v != "" {
c.ObjectStorage.SecretKey = v
}
if v := firstEnv("OBJECT_STORAGE_PUBLIC_BASE_URL", "HAIXUN_STORAGE_S3_PUBLIC_BASE_URL"); v != "" {
c.ObjectStorage.PublicBaseURL = strings.TrimRight(v, "/")
}
if v := os.Getenv("HAIXUN_STORAGE_S3_USE_PATH_STYLE"); v == "0" || v == "false" {
c.ObjectStorage.UsePathStyle = false
} else if v == "1" || v == "true" {
c.ObjectStorage.UsePathStyle = true
}
}
func firstEnv(keys ...string) string {
for _, k := range keys {
if v := os.Getenv(k); v != "" {
return v
}
}
return ""
}
func parsePort(v string) int {
var p int
for _, ch := range v {
if ch < '0' || ch > '9' {
return 0
}
p = p*10 + int(ch-'0')
}
return p
}