Files
uPortal2/backend/internal/invite/service.go
T

118 lines
2.7 KiB
Go
Raw Normal View History

2026-10-21 13:17:44 +03:00
package invite
import (
"context"
"crypto/rand"
"log/slog"
"math/big"
"strings"
"time"
)
type Service struct {
r *Repository
codeGroupSize int
codeNumGroups int
}
func NewService(r *Repository, codeGroupSize, codeNumGroups int) (*Service, error) {
if codeGroupSize == 0 {
codeGroupSize = 5
}
if codeNumGroups == 0 {
codeNumGroups = 5
}
return &Service{r: r, codeGroupSize: codeGroupSize, codeNumGroups: codeNumGroups}, nil
}
func (s *Service) generateCode() (string, error) {
const alphabet = "23456789ABCDEFGHJKMNPQRSTVWXYZ"
totalChars := s.codeGroupSize * s.codeNumGroups
alphabetLen := big.NewInt(int64(len(alphabet)))
var builder strings.Builder
builder.Grow(totalChars + (s.codeNumGroups - 1))
for i := range totalChars {
if i > 0 && i%s.codeGroupSize == 0 {
builder.WriteByte('-')
}
randIndex, err := rand.Int(rand.Reader, alphabetLen)
if err != nil {
slog.Error("generate invite code", slog.String("op", "service.generateCode"), slog.Any("error", err))
return "", ErrInternal
}
builder.WriteByte(alphabet[randIndex.Int64()])
}
return builder.String(), nil
}
func (s *Service) CreateInvite(ctx context.Context, quota int, expiresAt time.Time) (*Invite, error) {
token, err := s.generateCode()
if err != nil {
return nil, err
}
return s.r.CreateInvite(ctx, token, quota, expiresAt)
}
func (s *Service) InviteByToken(ctx context.Context, token string) (*Invite, error) {
return s.r.InviteByToken(ctx, token)
}
func (s *Service) CountInvites(ctx context.Context) (int64, error) {
return s.r.CountInvites(ctx)
}
func (s *Service) Invites(ctx context.Context, limit, offset int64) ([]*Invite, error) {
return s.r.Invites(ctx, limit, offset)
}
func (s *Service) UseInvite(ctx context.Context, token string) (*Invite, error) {
tx, err := s.r.Db.BeginTx(ctx, nil)
if err != nil {
slog.Error("begin transaction", slog.String("op", "service.UseInvite"), slog.String("token", token), slog.Any("error", err))
return nil, ErrInternal
}
defer tx.Rollback()
txRepo := s.r.WithTx(tx)
invite, err := txRepo.InviteByToken(ctx, token)
if err != nil {
return nil, err
}
if !invite.IsValid() {
return nil, ErrInvalid
}
value := *invite
cloned := &value
cloned.Uses += 1
if cloned.Uses < cloned.Quota {
err := txRepo.UpdateInvite(ctx, cloned)
if err != nil {
return nil, err
}
} else {
err := txRepo.DeleteInvite(ctx, cloned.Token)
if err != nil {
return nil, err
}
}
tx.Commit()
return cloned, nil
}
func (s *Service) DeleteInvite(ctx context.Context, invite *Invite) error {
return s.r.DeleteInvite(ctx, invite.Token)
}
func (s *Service) CleanupInvites(ctx context.Context) error {
return s.r.CleanupInvites(ctx)
}