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 lifecycleUseCase struct { members domrepo.MemberRepository tenants domrepo.TenantRepository uidGen domrepo.UIDGenerator } // LifecycleUseCaseParam wires LifecycleUseCase. type LifecycleUseCaseParam struct { Members domrepo.MemberRepository Tenants domrepo.TenantRepository UIDGen domrepo.UIDGenerator } // MustLifecycleUseCase constructs LifecycleUseCase. func MustLifecycleUseCase(param LifecycleUseCaseParam) domusecase.LifecycleUseCase { if param.Members == nil || param.Tenants == nil || param.UIDGen == nil { panic("member: lifecycle dependencies are required") } return &lifecycleUseCase{ members: param.Members, tenants: param.Tenants, uidGen: param.UIDGen, } } func (uc *lifecycleUseCase) CreateUnverified(ctx context.Context, req *domusecase.CreatePlatformMemberRequest) (*domusecase.MemberDTO, error) { if req == nil || req.TenantID == "" || req.Email == "" { return nil, errb.InputMissingRequired("tenant_id and email are required") } tenant, err := uc.tenants.GetByTenantID(ctx, req.TenantID) if err != nil { if errors.Is(err, member.ErrTenantNotFound) { return nil, errb.ResNotFound("tenant", req.TenantID).WithCause(err) } return nil, errb.SysInternal("read tenant failed").WithCause(err) } uid, err := uc.uidGen.Next(ctx, req.TenantID, tenant.UIDPrefix) if err != nil { return nil, errb.SysInternal("allocate uid failed").WithCause(err) } now := time.Now().UTC().UnixMilli() rec := &entity.Member{ TenantID: req.TenantID, UID: uid, ZitadelEmail: req.Email, DisplayName: req.DisplayName, Language: defaultLanguage(req.Language), Status: enum.MemberStatusUnverified, Origin: enum.MemberOriginPlatformNative, PasswordHash: req.PasswordHash, CreateAt: now, UpdateAt: now, } if err := uc.members.Insert(ctx, rec); err != nil { if errors.Is(err, member.ErrDuplicateMember) { return nil, errb.ResAlreadyExist("member already exists").WithCause(err) } return nil, errb.SysInternal("create member failed").WithCause(err) } return memberToDTO(rec), nil } func (uc *lifecycleUseCase) Activate(ctx context.Context, tenantID, uid string) error { return uc.transition(ctx, tenantID, uid, enum.MemberStatusUnverified, enum.MemberStatusActive, "") } func (uc *lifecycleUseCase) Suspend(ctx context.Context, tenantID, uid, reason string) error { return uc.transition(ctx, tenantID, uid, enum.MemberStatusActive, enum.MemberStatusSuspended, reason) } func (uc *lifecycleUseCase) Reactivate(ctx context.Context, tenantID, uid string) error { return uc.transition(ctx, tenantID, uid, enum.MemberStatusSuspended, enum.MemberStatusActive, "") } func (uc *lifecycleUseCase) SoftDelete(ctx context.Context, tenantID, uid string) error { rec, err := uc.members.GetByUID(ctx, tenantID, uid) if err != nil { if errors.Is(err, member.ErrNotFound) { return errb.ResNotFound("member", uid).WithCause(err) } return errb.SysInternal("read member failed").WithCause(err) } if rec.Status == enum.MemberStatusDeleted { return nil } if err := uc.members.UpdateStatus(ctx, tenantID, uid, enum.MemberStatusDeleted, ""); err != nil { return errb.SysInternal("soft delete member failed").WithCause(err) } return nil } func (uc *lifecycleUseCase) AbortPending(ctx context.Context, tenantID, uid string) error { return uc.transition(ctx, tenantID, uid, enum.MemberStatusUnverified, enum.MemberStatusDeleted, "") } func (uc *lifecycleUseCase) transition(ctx context.Context, tenantID, uid string, from, to enum.MemberStatus, reason string) error { if tenantID == "" || uid == "" { return errb.InputMissingRequired("tenant_id and uid are required") } rec, err := uc.members.GetByUID(ctx, tenantID, uid) if err != nil { if errors.Is(err, member.ErrNotFound) { return errb.ResNotFound("member", uid).WithCause(err) } return errb.SysInternal("read member failed").WithCause(err) } if rec.Status != from { return errb.ResInvalidState("invalid member status transition").WithCause(member.ErrInvalidStatus) } if err := uc.members.UpdateStatus(ctx, tenantID, uid, to, reason); err != nil { return errb.SysInternal("update member status failed").WithCause(err) } return nil }