Make product API versions independently installable

This commit is contained in:
Stephan D committed 2026-09-13 10:26:06 +02:00
1 parent ce6583f454
commit d04c8b12ab
69 files changed
+755 -404

No files matched your search

+28 -14
View File
@@ -1,23 +1,37 @@
package api
import (
"context"
"git.sendico.io/tech/sxchnge/product/app/urls"
"git.sendico.io/tech/sxchnge/shared/database"
"git.sendico.io/tech/sxchnge/product/auth/config"
"git.sendico.io/tech/sxchnge/product/auth/flow"
"git.sendico.io/tech/sxchnge/product/auth/storage"
"git.sendico.io/tech/sxchnge/product/customers"
"git.sendico.io/tech/sxchnge/product/twofactor"
"git.sendico.io/tech/sxchnge/shared/logging"
"git.sendico.io/tech/sxchnge/shared/service"
"github.com/go-chi/chi/v5"
)
type API interface {
LifecycleContext() context.Context
Logger() mlogger.Logger
DomainProvider() domainprovider.DomainProvider
Config() *Config
ProfileConfig() ConfigProvider
DBFactory() db.Factory
RuntimeComponent(name string) any
Register() Register
// Dependencies are initialized once and shared by installed transports.
type Dependencies struct {
Stores *storage.Stores
Customers accountservice.AccountService
Auth *flow.Service
Factors *twofactor.Service
Token middleware.TokenConfig
Signature middleware.Signature
LoginLimits middleware.LoginRateLimits
}
type MicroServiceFactoryT = func(API) (mservice.MicroService, error)
// Scope belongs to one installation. Router paths are relative to Mount.
type Scope struct {
Router chi.Router
Mount string
URLs domainprovider.DomainProvider
Logger mlogger.Logger
}
type Installer func(Scope, *Dependencies) error
type Mount struct {
Version string `yaml:"version"`
Path string `yaml:"path"`
}
+2
View File
@@ -7,6 +7,8 @@ import (
)
type Config struct {
// nil preserves the legacy v1 mount; an explicit empty list installs no API.
Versions []Mount `yaml:"versions"`
Notifications *notifications.Config `yaml:"notifications"`
Mw *mwa.Config `yaml:"middleware"`
AuthStorage *mwa.AuthStorageConfig `yaml:"auth_storage"`
-13
View File
@@ -1,13 +0,0 @@
package api
import (
"git.sendico.io/tech/sxchnge/product/auth/response"
api "git.sendico.io/tech/sxchnge/shared/http"
"git.sendico.io/tech/sxchnge/shared/service"
)
type Register interface {
Handler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.HandlerFunc)
AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc)
PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc)
}
+13 -17
View File
@@ -1,36 +1,32 @@
package api
import "git.sendico.io/tech/sxchnge/shared/service"
import (
"context"
type ConfigProvider interface {
SharedConfig() *Config
}
"git.sendico.io/tech/sxchnge/shared/database"
"git.sendico.io/tech/sxchnge/shared/logging"
)
type ConfigProvider interface{ SharedConfig() *Config }
type ConfigFactory func() ConfigProvider
type RuntimeComponents map[string]any
type RuntimeBootstrapFunc func(API) (RuntimeComponents, error)
type RuntimeFinishFunc func(API) error
// Bootstrap opens process-owned resources. Its cleanup is called once even if
// installation fails. On error, bootstrap must return cleanup or clean its own
// resources before returning, never both.
type Bootstrap func(context.Context, mlogger.Logger, *Config, db.Factory) (*Dependencies, func(context.Context) error, error)
type RuntimeConfig struct {
Name string
Services []ServiceFactory
ConfigFactory ConfigFactory
Bootstrap RuntimeBootstrapFunc
Finish RuntimeFinishFunc
}
type ServiceFactory struct {
Name mservice.Type
Factory MicroServiceFactoryT
Bootstrap Bootstrap
Versions map[string]Installer
}
func (cfg RuntimeConfig) RuntimeName() string {
if cfg.Name == "" {
return "edge"
return "product"
}
return cfg.Name
}
func (cfg RuntimeConfig) NewConfig() ConfigProvider {
if cfg.ConfigFactory != nil {
return cfg.ConfigFactory()
+26
View File
@@ -0,0 +1,26 @@
// Package v1 composes the product's first public HTTP contract.
package v1
import (
"git.sendico.io/tech/sxchnge/product/app/api"
auth "git.sendico.io/tech/sxchnge/product/auth/http/v1"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/public"
factors "git.sendico.io/tech/sxchnge/product/twofactor/http/v1"
"git.sendico.io/tech/sxchnge/shared/errors"
)
func Install(scope api.Scope, deps *api.Dependencies) error {
if deps == nil || deps.Stores == nil {
return merrors.InvalidArgument("v1 auth dependencies are required")
}
flows := public.Flows{Onboarding: deps.Auth}
if deps.Factors != nil {
flows.Factors = deps.Factors
}
// Signature is resolved at bootstrap, not by each version from the environment.
routes := auth.Install(scope.Logger, scope.Router, "", deps.Stores.Accounts, deps.Stores.RefreshTokens, deps.Stores.LoginAttempts, &deps.Token, deps.Signature, deps.LoginLimits, flows)
if deps.Factors != nil {
factors.Install(routes, deps.Factors, scope.Logger)
}
return nil
}