Files
sendico/api/pkg/db/internal/mongo/db.go
Stephan D 62a6631b9a
All checks were successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
service backend
2025-11-07 18:35:26 +01:00

258 lines
8.4 KiB
Go
Executable File

package mongo
import (
"context"
"os"
"github.com/mitchellh/mapstructure"
"github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/db/account"
"github.com/tech/sendico/pkg/db/internal/mongo/accountdb"
"github.com/tech/sendico/pkg/db/internal/mongo/invitationdb"
"github.com/tech/sendico/pkg/db/internal/mongo/organizationdb"
"github.com/tech/sendico/pkg/db/internal/mongo/policiesdb"
"github.com/tech/sendico/pkg/db/internal/mongo/refreshtokensdb"
"github.com/tech/sendico/pkg/db/internal/mongo/rolesdb"
"github.com/tech/sendico/pkg/db/internal/mongo/transactionimp"
"github.com/tech/sendico/pkg/db/invitation"
"github.com/tech/sendico/pkg/db/organization"
"github.com/tech/sendico/pkg/db/policy"
"github.com/tech/sendico/pkg/db/refreshtokens"
"github.com/tech/sendico/pkg/db/repository"
"github.com/tech/sendico/pkg/db/role"
"github.com/tech/sendico/pkg/db/transaction"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice"
mutil "github.com/tech/sendico/pkg/mutil/config"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.uber.org/zap"
)
// Config represents configuration
type Config struct {
Port *string `mapstructure:"port"`
PortEnv *string `mapstructure:"port_env"`
User *string `mapstructure:"user"`
UserEnv *string `mapstructure:"user_env"`
PasswordEnv string `mapstructure:"password_env"`
Database *string `mapstructure:"database"`
DatabaseEnv *string `mapstructure:"database_env"`
Host *string `mapstructure:"host"`
HostEnv *string `mapstructure:"host_env"`
AuthSource *string `mapstructure:"auth_source,omitempty"`
AuthSourceEnv *string `mapstructure:"auth_source_env,omitempty"`
AuthMechanism *string `mapstructure:"auth_mechanism,omitempty"`
AuthMechanismEnv *string `mapstructure:"auth_mechanism_env,omitempty"`
ReplicaSet *string `mapstructure:"replica_set,omitempty"`
ReplicaSetEnv *string `mapstructure:"replica_set_env,omitempty"`
Enforcer *auth.Config `mapstructure:"enforcer"`
}
type DBSettings struct {
Host string
Port string
User string
Password string
Database string
AuthSource string
AuthMechanism string
ReplicaSet string
}
func newProtectedDB[T any](
db *DB,
create func(ctx context.Context, logger mlogger.Logger, enforcer auth.Enforcer, pdb policy.DB, client *mongo.Database) (T, error),
) (T, error) {
pdb, err := db.NewPoliciesDB()
if err != nil {
db.logger.Warn("Failed to create policies database", zap.Error(err))
var zero T
return zero, err
}
return create(context.Background(), db.logger, db.Enforcer(), pdb, db.db())
}
func Config2DBSettings(logger mlogger.Logger, config *Config) *DBSettings {
p := new(DBSettings)
p.Port = mutil.GetConfigValue(logger, "port", "port_env", config.Port, config.PortEnv)
p.Database = mutil.GetConfigValue(logger, "database", "database_env", config.Database, config.DatabaseEnv)
p.Password = os.Getenv(config.PasswordEnv)
p.User = mutil.GetConfigValue(logger, "user", "user_env", config.User, config.UserEnv)
p.Host = mutil.GetConfigValue(logger, "host", "host_env", config.Host, config.HostEnv)
p.AuthSource = mutil.GetConfigValue(logger, "auth_source", "auth_source_env", config.AuthSource, config.AuthSourceEnv)
p.AuthMechanism = mutil.GetConfigValue(logger, "auth_mechanism", "auth_mechanism_env", config.AuthMechanism, config.AuthMechanismEnv)
p.ReplicaSet = mutil.GetConfigValue(logger, "replica_set", "replica_set_env", config.ReplicaSet, config.ReplicaSetEnv)
return p
}
func decodeConfig(logger mlogger.Logger, settings model.SettingsT) (*Config, *DBSettings, error) {
var config Config
if err := mapstructure.Decode(settings, &config); err != nil {
logger.Warn("Failed to decode settings", zap.Error(err), zap.Any("settings", settings))
return nil, nil, err
}
dbSettings := Config2DBSettings(logger, &config)
return &config, dbSettings, nil
}
func dialMongo(logger mlogger.Logger, dbSettings *DBSettings) (*mongo.Client, error) {
cred := options.Credential{
AuthMechanism: dbSettings.AuthMechanism,
AuthSource: dbSettings.AuthSource,
Username: dbSettings.User,
Password: dbSettings.Password,
}
dbURI := buildURI(dbSettings)
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(dbURI).SetAuth(cred))
if err != nil {
logger.Error("Unable to connect to database", zap.Error(err))
return nil, err
}
logger.Info("Connected successfully", zap.String("uri", dbURI))
if err := client.Ping(context.Background(), readpref.Primary()); err != nil {
logger.Error("Unable to ping database", zap.Error(err))
_ = client.Disconnect(context.Background())
return nil, err
}
return client, nil
}
func ConnectClient(logger mlogger.Logger, settings model.SettingsT) (*mongo.Client, *Config, *DBSettings, error) {
config, dbSettings, err := decodeConfig(logger, settings)
if err != nil {
return nil, nil, nil, err
}
client, err := dialMongo(logger, dbSettings)
if err != nil {
return nil, nil, nil, err
}
return client, config, dbSettings, nil
}
// DB represents the structure of the database
type DB struct {
logger mlogger.Logger
config *DBSettings
client *mongo.Client
enforcer auth.Enforcer
manager auth.Manager
pdb policy.DB
}
func (db *DB) db() *mongo.Database {
return db.client.Database(db.config.Database)
}
func (db *DB) NewAccountDB() (account.DB, error) {
return accountdb.Create(db.logger, db.db())
}
func (db *DB) NewOrganizationDB() (organization.DB, error) {
pdb, err := db.NewPoliciesDB()
if err != nil {
db.logger.Warn("Failed to create policies database", zap.Error(err))
return nil, err
}
organizationDB, err := organizationdb.Create(context.Background(), db.logger, db.Enforcer(), pdb, db.db())
if err != nil {
return nil, err
}
// Return the concrete type - interface mismatch will be handled at runtime
// TODO: Update organization.DB interface to match implementation signatures
return organizationDB, nil
}
func (db *DB) NewRefreshTokensDB() (refreshtokens.DB, error) {
return refreshtokensdb.Create(db.logger, db.db())
}
func (db *DB) NewInvitationsDB() (invitation.DB, error) {
return newProtectedDB(db, invitationdb.Create)
}
func (db *DB) NewPoliciesDB() (policy.DB, error) {
return db.pdb, nil
}
func (db *DB) NewRolesDB() (role.DB, error) {
return rolesdb.Create(db.logger, db.db())
}
func (db *DB) TransactionFactory() transaction.Factory {
return transactionimp.CreateFactory(db.client)
}
func (db *DB) Permissions() auth.Provider {
return db
}
func (db *DB) Manager() auth.Manager {
return db.manager
}
func (db *DB) Enforcer() auth.Enforcer {
return db.enforcer
}
func (db *DB) GetPolicyDescription(ctx context.Context, resource mservice.Type) (*model.PolicyDescription, error) {
var policyDescription model.PolicyDescription
return &policyDescription, db.pdb.FindOne(ctx, repository.Filter("resourceTypes", resource), &policyDescription)
}
func (db *DB) CloseConnection() {
if err := db.client.Disconnect(context.Background()); err != nil {
db.logger.Warn("Failed to close connection", zap.Error(err))
}
db.logger.Info("Database connection closed")
}
// NewConnection creates a new database connection
func NewConnection(logger mlogger.Logger, settings model.SettingsT) (*DB, error) {
client, config, dbSettings, err := ConnectClient(logger, settings)
if err != nil {
return nil, err
}
db := &DB{
logger: logger.Named("db"),
config: dbSettings,
client: client,
}
cleanup := func(ctx context.Context) {
if err := client.Disconnect(ctx); err != nil {
logger.Warn("Failed to close MongoDB connection", zap.Error(err))
}
}
rdb, err := db.NewRolesDB()
if err != nil {
db.logger.Warn("Failed to create roles database", zap.Error(err))
cleanup(context.Background())
return nil, err
}
if db.pdb, err = policiesdb.Create(db.logger, db.db()); err != nil {
db.logger.Warn("Failed to create policies database", zap.Error(err))
cleanup(context.Background())
return nil, err
}
if db.enforcer, db.manager, err = auth.CreateAuth(logger, db.client, db.db(), db.pdb, rdb, config.Enforcer); err != nil {
db.logger.Warn("Failed to create permissions enforcer", zap.Error(err))
cleanup(context.Background())
return nil, err
}
return db, nil
}