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

129 lines
4.4 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 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
}