// Package application composes the Go backend foundation Modules. package application import ( "context" "net/http" "os" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/httpapi" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/postgres" ) type Options struct { Context context.Context Getenv postgres.Getenv ReadFile postgres.ReadFile // AuthorizationLoader is an optional Identity persistence Adapter used by // composition tests and alternate runtime backends. Production defaults to // the PostgreSQL Store opened below. AuthorizationLoader identity.AuthorizationSnapshotLoader // CredentialAuthenticator is the narrow Password Login persistence seam. // Production defaults to the same PostgreSQL Store used for authorization. CredentialAuthenticator identity.CredentialAuthenticator } type App struct { handler http.Handler db *postgres.Module } func New(options Options) (*App, error) { ctx := options.Context if ctx == nil { ctx = context.Background() } getenv := options.Getenv if getenv == nil { getenv = os.Getenv } readFile := options.ReadFile if readFile == nil { 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 } database, err := postgres.Open(ctx, config) if err != nil { return nil, err } closeOnError := true defer func() { if closeOnError { database.Close() } }() readiness := databaseReadiness{config: config, store: database.Store} var resolver httpapi.SessionResolver if authConfig.Configured { loader := options.AuthorizationLoader if loader == nil { loader = database.Store } resolver = identity.NewResolver(loader, authConfig.SessionSecret, "platform", nil) } authMe, err := httpapi.NewAuthMeHandler(httpapi.AuthState{ Required: authConfig.Required, Configured: authConfig.Configured, }, resolver) if err != nil { return nil, err } var passwordIssuer httpapi.PasswordSessionIssuer if authConfig.Configured { authenticator := options.CredentialAuthenticator if authenticator == nil { authenticator = database.Store } passwordIssuer = identity.NewPasswordLogin(authenticator, nil) } cookieSecure := getenv("ZHINIAN_AUTH_COOKIE_SECURE") publicBaseURL := firstAuthEnv(getenv, "NEXT_PUBLIC_APP_URL", "ZHINIAN_PUBLIC_BASE_URL") authPassword, err := httpapi.NewAuthPasswordHandler(httpapi.PasswordAuthConfig{ Configured: authConfig.Configured, SessionSecret: authConfig.SessionSecret, CookieSecure: cookieSecure, PublicBaseURL: publicBaseURL, }, passwordIssuer) if err != nil { return nil, err } authLogout := httpapi.NewAuthLogoutHandler(httpapi.LogoutConfig{ CookieSecure: cookieSecure, PublicBaseURL: publicBaseURL, }) foundation := httpapi.NewHandler(readiness) mux := http.NewServeMux() mux.Handle("/api/auth/me", authMe) mux.Handle("/api/auth/password", authPassword) mux.Handle("/api/auth/logout", authLogout) mux.Handle("/", foundation) closeOnError = false return &App{ db: database, handler: mux, }, nil } func (app *App) Handler() http.Handler { return app.handler } func (app *App) Close() { if app.db != nil { app.db.Close() } } type databaseReadiness struct { config postgres.Config store *postgres.Store } func (adapter databaseReadiness) Status() httpapi.DatabaseStatus { configured := adapter.config.Backend == postgres.BackendLocal || adapter.config.DatabaseURL != "" return httpapi.DatabaseStatus{ Backend: string(adapter.config.Backend), Configured: configured, } } func (adapter databaseReadiness) Ready(ctx context.Context) (httpapi.DatabaseStatus, error) { status := adapter.Status() return status, adapter.store.Readiness(ctx) }