Raw
  1package admin
  2
  3import (
  4	"context"
  5	"errors"
  6	"time"
  7
  8	"tug/internal/storage"
  9)
 10
 11func (s *Service) ListInvitations(ctx context.Context, page int) (InvitationList, error) {
 12	page = max(page, 1)
 13	got, err := s.store.ListInvitationPage(ctx, (page-1)*InvitationsPerPage, InvitationsPerPage)
 14	if err != nil {
 15		return InvitationList{}, failed("list invitations", err)
 16	}
 17	pending, err := s.pending(ctx)
 18	if err != nil {
 19		return InvitationList{}, err
 20	}
 21	now := s.now()
 22	out := InvitationList{Total: got.Total, Invitations: make([]Invitation, 0, len(got.Records))}
 23	for _, r := range got.Records {
 24		inv := Invitation{ID: r.ID, Label: r.Label, CreatedAt: r.CreatedAt, ExpiresAt: r.ExpiresAt, State: state(r.Invitation, now), UsedAt: r.UsedAt}
 25		if r.CreatedByName != nil {
 26			inv.CreatedBy = *r.CreatedByName
 27		}
 28		if r.UsedBy != nil && r.UsedByName != nil {
 29			inv.UsedBy, inv.UsedByID = *r.UsedByName, *r.UsedBy
 30			inv.UsedByPending = isPendingIn(pending, *r.UsedBy)
 31		}
 32		if r.BatchID != nil {
 33			inv.Batch = *r.BatchID
 34		}
 35		for _, g := range r.Groups {
 36			inv.Groups = append(inv.Groups, InvitationGroup{Name: g.GroupName, Deleted: g.GroupID == nil})
 37		}
 38		out.Invitations = append(out.Invitations, inv)
 39	}
 40	return out, nil
 41}
 42
 43func (s *Service) Invitation(ctx context.Context, id string) (Invitation, error) {
 44	inv, err := s.store.InvitationByID(ctx, id)
 45	if err != nil {
 46		return Invitation{}, target("read invitation", err)
 47	}
 48	return s.resolver(ctx).invitation(inv, s.now())
 49}
 50
 51func (s *Service) RevokeInvitation(ctx context.Context, actor, id string) error {
 52	inv, err := s.store.InvitationByID(ctx, id)
 53	if err != nil {
 54		return target("read invitation", err)
 55	}
 56	if state(inv, s.now()) != InvitationPending {
 57		return ProblemInvitationClosed
 58	}
 59	err = s.store.RevokeInvitationAudited(ctx, id, actor)
 60	if errors.Is(err, storage.ErrConflict) {
 61		return ProblemInvitationClosed
 62	}
 63	if err != nil {
 64		return target("revoke invitation", err)
 65	}
 66	return nil
 67}
 68
 69func state(inv storage.Invitation, now time.Time) InvitationState {
 70	switch {
 71	case inv.UsedAt != nil:
 72		return InvitationUsed
 73	case inv.RevokedAt != nil:
 74		return InvitationRevoked
 75	case !inv.ExpiresAt.After(now):
 76		return InvitationExpired
 77	}
 78	return InvitationPending
 79}
 80
 81func (r *resolver) invitation(inv storage.Invitation, now time.Time) (Invitation, error) {
 82	view := Invitation{ID: inv.ID, CreatedAt: inv.CreatedAt, ExpiresAt: inv.ExpiresAt, State: state(inv, now)}
 83	if inv.CreatedBy != nil {
 84		creator, err := r.actor(*inv.CreatedBy)
 85		if err != nil {
 86			return Invitation{}, err
 87		}
 88		view.CreatedBy = creator
 89	}
 90	return view, nil
 91}
 92
 93func (s *Service) RevokeInvitationBatch(ctx context.Context, actor, batch string) (int, error) {
 94	n, err := s.store.RevokeInvitationBatchAudited(ctx, batch, actor)
 95	if errors.Is(err, storage.ErrNotFound) {
 96		return 0, ProblemUnknownBatch
 97	}
 98	if err != nil {
 99		return 0, failed("revoke invitation batch", err)
100	}
101	return n, nil
102}