This commit is contained in:
andy committed 2026-08-18 18:23:11 +08:00
1 parent 648b274c55
commit 18cb51670c
14 files changed
+420 -171

No files matched your search

+34 -23
View File
@@ -22,6 +22,7 @@ import (
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/postgres"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/prompt"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/publicapi"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/settings"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/templates"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/usage"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/webhook"
@@ -45,10 +46,12 @@ type Options struct {
RemoteFetcher assets.RemoteFetcher
// ProviderRegistry can replace all external adapters in deterministic tests.
ProviderRegistry jobs.ProviderRegistry
// Log adapters remain injectable for deterministic composition tests.
// Runtime settings intentionally have one concrete source so /api/settings
// and billing account endpoints cannot observe different stores.
// Log and runtime-settings adapters remain injectable for deterministic
// composition tests. Production uses the PostgreSQL Store opened below.
Logs httpapi.LogService
// Runtime settings intentionally have one source so /api/settings, startup
// services, dynamic providers, and billing account endpoints stay coherent.
RuntimeSettingsRepository settings.RuntimeSettingsRepository
}
type App struct {
@@ -71,10 +74,6 @@ func New(options Options) (*App, error) {
readFile = os.ReadFile
}
authConfig, err := ParseAuthConfig(getenv)
if err != nil {
return nil, err
}
config, err := postgres.ParseConfig(getenv, readFile)
if err != nil {
return nil, err
@@ -89,6 +88,22 @@ func New(options Options) (*App, error) {
database.Close()
}
}()
runtimeSettings := defaultSettingsService(getenv)
if config.Backend == postgres.BackendPostgres {
repository := options.RuntimeSettingsRepository
if repository == nil {
repository = database.Store
}
runtimeSettings = databaseSettingsService(getenv, repository)
}
startupGetenv, err := runtimeSettingsGetenv(ctx, getenv, runtimeSettings)
if err != nil {
return nil, fmt.Errorf("load startup runtime settings: %w", err)
}
authConfig, err := ParseAuthConfig(startupGetenv)
if err != nil {
return nil, err
}
readiness := databaseReadiness{config: config, store: database.Store}
// PostgreSQL remains the production source of truth. Local mode swaps every
@@ -135,7 +150,7 @@ func New(options Options) (*App, error) {
platformAuthorizer, err := httpapi.NewPlatformAuthorizer(
authState,
resolver,
httpapi.WithLocalDevelopmentFallback(config.Backend == postgres.BackendLocal && !strings.EqualFold(strings.TrimSpace(getenv("NODE_ENV")), "production")),
httpapi.WithLocalDevelopmentFallback(config.Backend == postgres.BackendLocal && !strings.EqualFold(strings.TrimSpace(startupGetenv("NODE_ENV")), "production")),
)
if err != nil {
return nil, err
@@ -154,8 +169,8 @@ func New(options Options) (*App, error) {
}
passwordIssuer = identity.NewPasswordLogin(authenticator, nil)
}
cookieSecure := getenv("ZHINIAN_AUTH_COOKIE_SECURE")
publicBaseURL := firstAuthEnv(getenv, "NEXT_PUBLIC_APP_URL", "ZHINIAN_PUBLIC_BASE_URL")
cookieSecure := startupGetenv("ZHINIAN_AUTH_COOKIE_SECURE")
publicBaseURL := firstAuthEnv(startupGetenv, "NEXT_PUBLIC_APP_URL", "ZHINIAN_PUBLIC_BASE_URL")
authPassword, err := httpapi.NewAuthPasswordHandler(httpapi.PasswordAuthConfig{
Configured: authConfig.Configured,
SessionSecret: authConfig.SessionSecret,
@@ -172,13 +187,13 @@ func New(options Options) (*App, error) {
authCompatibility := httpapi.NewAuthCompatibilityHandler()
publicAuthenticator := publicapi.NewAuthenticator(publicapi.Config{
APIKeys: getenv("ZHINIAN_API_KEYS"),
InternalWorkerToken: getenv("ZHINIAN_INTERNAL_WORKER_TOKEN"),
Production: strings.EqualFold(strings.TrimSpace(getenv("NODE_ENV")), "production"),
APIKeys: startupGetenv("ZHINIAN_API_KEYS"),
InternalWorkerToken: startupGetenv("ZHINIAN_INTERNAL_WORKER_TOKEN"),
Production: strings.EqualFold(strings.TrimSpace(startupGetenv("NODE_ENV")), "production"),
})
administrationService := administration.NewService(administrationStore)
if created, err := BootstrapSuperAdmin(ctx, config.Backend, administrationService, ParseBootstrapAdminConfig(getenv)); err != nil {
if created, err := BootstrapSuperAdmin(ctx, config.Backend, administrationService, ParseBootstrapAdminConfig(startupGetenv)); err != nil {
return nil, fmt.Errorf("bootstrap super administrator: %w", err)
} else if created {
log.Printf("zhinian-api bootstrapped the first super administrator from ZHINIAN_BOOTSTRAP_ADMIN_* configuration")
@@ -201,12 +216,12 @@ func New(options Options) (*App, error) {
blobStore := options.BlobStore
if blobStore == nil {
var configured bool
blobStore, configured, err = configuredOSSBlobStore(getenv)
blobStore, configured, err = configuredOSSBlobStore(startupGetenv)
if err != nil {
return nil, err
}
if !configured {
runtimeDirectory := strings.TrimSpace(getenv("ZHINIAN_RUNTIME_DIR"))
runtimeDirectory := strings.TrimSpace(startupGetenv("ZHINIAN_RUNTIME_DIR"))
if runtimeDirectory == "" {
runtimeDirectory = filepath.Join(".runtime")
}
@@ -220,7 +235,7 @@ func New(options Options) (*App, error) {
if remoteFetcher == nil {
remoteFetcher, err = assets.NewPublicHTTPRemoteFetcher(
30*time.Second,
remoteAssetMaxBytes(getenv),
remoteAssetMaxBytes(startupGetenv),
assets.NewPublicDestinationPolicy(nil, nil),
)
if err != nil {
@@ -229,17 +244,13 @@ func New(options Options) (*App, error) {
}
assetService := assets.NewService(assetCatalog, blobStore, remoteFetcher, nil, nil)
assetsHandler, err := httpapi.NewAssetsHandler(assetService, platformAuthorizer, publicAuthenticator, httpapi.AssetsConfig{
MaxJSONBytes: positiveInt64Env(getenv, "ZHINIAN_MAX_JSON_BYTES", 1<<20), MaxUploadBytes: positiveInt64Env(getenv, "ZHINIAN_MAX_UPLOAD_BYTES", 20<<20),
MaxJSONBytes: positiveInt64Env(startupGetenv, "ZHINIAN_MAX_JSON_BYTES", 1<<20), MaxUploadBytes: positiveInt64Env(startupGetenv, "ZHINIAN_MAX_UPLOAD_BYTES", 20<<20),
})
if err != nil {
return nil, err
}
billingService := billing.NewService(billingStore, nil).SetEnabled(strings.TrimSpace(getenv("ZHINIAN_BILLING_REQUIRED")) != "0")
runtimeSettings := defaultSettingsService(getenv)
if config.Backend == postgres.BackendPostgres {
runtimeSettings = databaseSettingsService(getenv, database.Store)
}
billingService := billing.NewService(billingStore, nil).SetEnabled(strings.TrimSpace(startupGetenv("ZHINIAN_BILLING_REQUIRED")) != "0")
billingAccounts := settingsBillingAccountStore{service: runtimeSettings}
templateService := templates.NewService(templateCatalog, nil, nil)
logService := options.Logs