template-monorepo/internal/model/member/usecase/provisioning_usecase.go

150 lines
5.2 KiB
Go
Raw Normal View History

2026-05-20 23:51:22 +00:00
package usecase
import (
"context"
"errors"
"time"
member "gateway/internal/model/member/domain"
"gateway/internal/model/member/domain/entity"
"gateway/internal/model/member/domain/enum"
domrepo "gateway/internal/model/member/domain/repository"
domusecase "gateway/internal/model/member/domain/usecase"
)
type provisioningUseCase struct {
members domrepo.MemberRepository
identities domrepo.IdentityRepository
tenants domrepo.TenantRepository
uidGen domrepo.UIDGenerator
}
// ProvisioningUseCaseParam wires ProvisioningUseCase.
type ProvisioningUseCaseParam struct {
Members domrepo.MemberRepository
Identities domrepo.IdentityRepository
Tenants domrepo.TenantRepository
UIDGen domrepo.UIDGenerator
}
// MustProvisioningUseCase constructs ProvisioningUseCase.
func MustProvisioningUseCase(param ProvisioningUseCaseParam) domusecase.ProvisioningUseCase {
if param.Members == nil || param.Identities == nil || param.Tenants == nil || param.UIDGen == nil {
panic("member: provisioning dependencies are required")
}
return &provisioningUseCase{
members: param.Members,
identities: param.Identities,
tenants: param.Tenants,
uidGen: param.UIDGen,
}
}
func (uc *provisioningUseCase) EnsureFromOIDC(ctx context.Context, req *domusecase.EnsureFromOIDCRequest) (*domusecase.MemberDTO, error) {
if req == nil || req.TenantID == "" || req.ZitadelSub == "" {
return nil, errb.InputMissingRequired("tenant_id and zitadel_sub are required")
}
if existing, err := uc.members.GetByZitadelUserID(ctx, req.TenantID, req.ZitadelSub); err == nil {
return memberToDTO(existing), nil
} else if !errors.Is(err, member.ErrNotFound) {
return nil, errb.SysInternal("read member failed").WithCause(err)
}
return uc.createProvisioned(ctx, req.TenantID, req.ZitadelSub, "", req.Email, req.DisplayName, req.Locale, enum.MemberOriginOIDC)
}
func (uc *provisioningUseCase) EnsureFromLDAP(ctx context.Context, req *domusecase.EnsureFromLDAPRequest) (*domusecase.MemberDTO, error) {
if req == nil || req.TenantID == "" {
return nil, errb.InputMissingRequired("tenant_id is required")
}
sub := req.ZitadelSub
if sub == "" {
sub = req.ExternalID
}
if sub != "" {
if existing, err := uc.members.GetByZitadelUserID(ctx, req.TenantID, sub); err == nil {
return memberToDTO(existing), nil
} else if !errors.Is(err, member.ErrNotFound) {
return nil, errb.SysInternal("read member failed").WithCause(err)
}
}
if req.ExternalID != "" {
if idn, err := uc.identities.GetByExternalID(ctx, req.TenantID, req.ExternalID); err == nil {
rec, getErr := uc.members.GetByUID(ctx, req.TenantID, idn.UID)
if getErr == nil {
return memberToDTO(rec), nil
}
}
}
return uc.createProvisioned(ctx, req.TenantID, sub, req.ExternalID, req.Email, req.DisplayName, "", enum.MemberOriginLDAP)
}
func (uc *provisioningUseCase) EnsureFromSCIM(ctx context.Context, req *domusecase.EnsureFromSCIMRequest) (*domusecase.MemberDTO, error) {
if req == nil || req.TenantID == "" {
return nil, errb.InputMissingRequired("tenant_id is required")
}
if req.ExternalID != "" {
if idn, err := uc.identities.GetByExternalID(ctx, req.TenantID, req.ExternalID); err == nil {
rec, getErr := uc.members.GetByUID(ctx, req.TenantID, idn.UID)
if getErr == nil {
return memberToDTO(rec), nil
}
}
}
sub := req.ZitadelSub
return uc.createProvisioned(ctx, req.TenantID, sub, req.ExternalID, req.Email, req.DisplayName, "", enum.MemberOriginSCIM)
}
func (uc *provisioningUseCase) createProvisioned(
ctx context.Context,
tenantID, zitadelSub, externalID, email, displayName, language string,
origin enum.MemberOrigin,
) (*domusecase.MemberDTO, error) {
tenant, err := uc.tenants.GetByTenantID(ctx, tenantID)
if err != nil {
if errors.Is(err, member.ErrTenantNotFound) {
return nil, errb.ResNotFound("tenant", tenantID).WithCause(err)
}
return nil, errb.SysInternal("read tenant failed").WithCause(err)
}
uid, err := uc.uidGen.Next(ctx, tenantID, tenant.UIDPrefix)
if err != nil {
return nil, errb.SysInternal("allocate uid failed").WithCause(err)
}
now := time.Now().UTC().UnixMilli()
rec := &entity.Member{
TenantID: tenantID,
UID: uid,
ZitadelUserID: zitadelSub,
ZitadelEmail: email,
DisplayName: displayName,
Language: defaultLanguage(language),
Status: enum.MemberStatusActive,
Origin: origin,
CreateAt: now,
UpdateAt: now,
}
if err := uc.members.Insert(ctx, rec); err != nil {
if errors.Is(err, member.ErrDuplicateMember) {
if zitadelSub != "" {
if existing, getErr := uc.members.GetByZitadelUserID(ctx, tenantID, zitadelSub); getErr == nil {
return memberToDTO(existing), nil
}
}
return nil, errb.ResAlreadyExist("member already exists").WithCause(err)
}
return nil, errb.SysInternal("create member failed").WithCause(err)
}
idn := &entity.Identity{
TenantID: tenantID,
UID: uid,
ZitadelUserID: zitadelSub,
ExternalID: externalID,
CreateAt: now,
UpdateAt: now,
}
if err := uc.identities.Insert(ctx, idn); err != nil && !errors.Is(err, member.ErrDuplicateMember) {
return nil, errb.SysInternal("create identity failed").WithCause(err)
}
return memberToDTO(rec), nil
}