package usecase import ( "fmt" libcrypto "gateway/internal/library/crypto" libmongo "gateway/internal/library/mongo" redislib "gateway/internal/library/redis" memberconfig "gateway/internal/model/member/config" domrepo "gateway/internal/model/member/domain/repository" domusecase "gateway/internal/model/member/domain/usecase" "gateway/internal/model/member/repository" ) // Module bundles member atomic primitives. Each entry is a single-purpose // usecase; composite flows (e.g. "send verification email then mark // business_email verified") are assembled at the logic / driver layer and // MUST NOT live inside another usecase. type Module struct { OTP domusecase.OTPUseCase TOTP domusecase.TOTPUseCase Profile domusecase.ProfileUseCase Lifecycle domusecase.LifecycleUseCase Provisioning domusecase.ProvisioningUseCase Tenant domusecase.TenantUseCase VerifyRate domrepo.VerifyRateStore Members domrepo.MemberRepository Tenants domrepo.TenantRepository Identities domrepo.IdentityRepository } // ModuleParam wires member module dependencies. type ModuleParam struct { Redis *redislib.Client MongoConf *libmongo.Conf Config memberconfig.Config // Optional overrides for tests. Members domrepo.MemberRepository Tenants domrepo.TenantRepository Identities domrepo.IdentityRepository TOTPProfile domrepo.TOTPProfileRepository UIDGen domrepo.UIDGenerator } // NewModuleFromParam builds member atomic usecases. func NewModuleFromParam(param ModuleParam) (*Module, error) { if param.Redis == nil || param.Redis.Zero() == nil { return nil, fmt.Errorf("member: redis is required") } cfg := param.Config.Defaults() otpStore := repository.NewRedisOTPChallengeStore(param.Redis) rateStore := repository.NewRedisVerifyRateStore(param.Redis) members := param.Members tenants := param.Tenants identities := param.Identities uidGen := param.UIDGen totpProfile := param.TOTPProfile if param.MongoConf != nil && param.MongoConf.Host != "" { if members == nil { members = repository.NewMemberRepository(repository.MemberRepositoryParam{Conf: param.MongoConf}) } if tenants == nil { tenants = repository.NewTenantRepository(repository.TenantRepositoryParam{Conf: param.MongoConf}) } if identities == nil { identities = repository.NewIdentityRepository(repository.IdentityRepositoryParam{Conf: param.MongoConf}) } if totpProfile == nil { totpProfile = repository.NewMongoTOTPProfileRepository(param.MongoConf) } } if uidGen == nil { uidGen = repository.NewRedisUIDGenerator(param.Redis) } if totpProfile == nil { totpProfile = repository.NewMemoryTOTPProfileRepository() } mod := &Module{ OTP: MustOTPUseCase(OTPUseCaseParam{Store: otpStore, Config: cfg}), VerifyRate: rateStore, Members: members, Tenants: tenants, Identities: identities, } if members != nil { mod.Profile = MustProfileUseCase(ProfileUseCaseParam{Members: members}) } if members != nil && tenants != nil && uidGen != nil { mod.Lifecycle = MustLifecycleUseCase(LifecycleUseCaseParam{ Members: members, Tenants: tenants, UIDGen: uidGen, }) mod.Tenant = MustTenantUseCase(TenantUseCaseParam{Tenants: tenants}) } if members != nil && identities != nil && tenants != nil && uidGen != nil { mod.Provisioning = MustProvisioningUseCase(ProvisioningUseCaseParam{ Members: members, Identities: identities, Tenants: tenants, UIDGen: uidGen, }) } if cfg.TOTP.SecretKEK != "" { cipher, err := libcrypto.NewAESGCMFromString(cfg.TOTP.SecretKEK) if err != nil { return nil, fmt.Errorf("member: totp kek: %w", err) } mod.TOTP = MustTOTPUseCase(TOTPUseCaseParam{ Profile: totpProfile, Enroll: repository.NewRedisTOTPEnrollStore(param.Redis), Replay: repository.NewRedisTOTPReplayStore(param.Redis), Cipher: cipher, Config: cfg, }) } return mod, nil }