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 }