Files
RenewCMS/internal/infrastructure/useCases/CreateInvite.go
T
2026-10-06 22:25:23 +02:00

112 lines
2.9 KiB
Go

package useCases
import (
"RenewCMS/internal/domain/audit"
"RenewCMS/internal/domain/hash"
"RenewCMS/internal/domain/invite"
"RenewCMS/internal/domain/mail"
"RenewCMS/internal/domain/user"
"context"
"errors"
"strings"
"time"
)
type CreateInviteUseCase struct {
userRepository user.Repository
inviteRepository invite.Repository
hasher hash.Hasher
mailer inviteMailer
recorder audit.Recorder
}
type CreateInviteCommand struct {
Email string
InvitedBy uint32
}
func NewCreateInviteUseCase(userRepository user.Repository, inviteRepository invite.Repository, hasher hash.Hasher, mailRepository mail.Repository, publicURL string, recorder audit.Recorder) *CreateInviteUseCase {
return &CreateInviteUseCase{userRepository, inviteRepository, hasher, inviteMailer{mailRepository, publicURL}, recorder}
}
func (g *CreateInviteUseCase) CreateInvite(ctx context.Context, command CreateInviteCommand) (invite.Invite, error) {
event := audit.NewEvent(audit.ActionInviteCreate, audit.CategoryUsers)
created, err := g.create(command)
if err != nil {
recordFailure(ctx, g.recorder, event, err)
return invite.Invite{}, err
}
event.Target = inviteTarget(created)
g.recorder.Record(ctx, event)
return created, nil
}
func (g *CreateInviteUseCase) create(command CreateInviteCommand) (invite.Invite, error) {
email := strings.ToLower(command.Email)
now := time.Now()
inviter, err := g.userRepository.Get(command.InvitedBy)
if err != nil {
return invite.Invite{}, err
}
if g.emailHasAccount(email) {
return invite.Invite{}, invite.ErrEmailHasAccount
}
if err := g.discardOpenInvite(email, now); err != nil {
return invite.Invite{}, err
}
rawCode, hashedCode, err := newInviteCode(g.hasher)
if err != nil {
return invite.Invite{}, err
}
createdInvite, err := g.inviteRepository.Create(invite.Invite{
Email: email,
HashedCode: hashedCode,
ExpiresAt: now.Add(invite.Lifetime),
InvitedBy: command.InvitedBy,
CreatedAt: now,
})
if err != nil {
return invite.Invite{}, err
}
if sendErr := g.mailer.send(email, rawCode, inviter); sendErr != nil {
if deleteErr := g.inviteRepository.Delete(createdInvite.ID); deleteErr != nil {
return invite.Invite{}, errors.Join(sendErr, deleteErr)
}
return invite.Invite{}, sendErr
}
return createdInvite, nil
}
func (g *CreateInviteUseCase) emailHasAccount(email string) bool {
for _, existingUser := range g.userRepository.GetAll() {
if strings.EqualFold(existingUser.Email, email) {
return true
}
}
return false
}
func (g *CreateInviteUseCase) discardOpenInvite(email string, now time.Time) error {
for _, existing := range g.inviteRepository.GetAll() {
if existing.Email != email {
continue
}
switch existing.StatusAt(now) {
case invite.StatusPending:
return invite.ErrPendingInvite
case invite.StatusExpired:
if err := g.inviteRepository.Delete(existing.ID); err != nil {
return err
}
}
}
return nil
}