package service import ( "context" "fmt" "time" "github.com/owncord/server/db" "github.com/owncord/server/telemetry" ) // InviteService handles invite management. type InviteService struct { st Store } // NewInviteService creates an InviteService. func NewInviteService(st Store) *InviteService { return &InviteService{st: st} } // maxInviteExpiryHoursVal caps invite expiry to 30 days (H-4 hardening). const maxInviteExpiryHoursVal = 720 // MaxInviteExpiryHours returns the maximum invite expiry in hours. func MaxInviteExpiryHours() int { return maxInviteExpiryHoursVal } // CreateInvite creates a new invite code with optional max uses and expiry. func (s *InviteService) CreateInvite(ctx context.Context, createdBy int64, maxUses int, expiresInHours int) (*db.Invite, error) { ctx, span := telemetry.GlobalTracer("service/invite").Start(ctx, "InviteService.CreateInvite", telemetry.Int64("created_by", createdBy), ) start := time.Now() defer func() { telemetry.TimeSince(ctx, telemetry.NewAppMetrics().ServiceCallDurationSec, start, telemetry.String("method", "CreateInvite")) span.End() }() // Cap expiry. if expiresInHours > maxInviteExpiryHoursVal { expiresInHours = maxInviteExpiryHoursVal } var expiresAt *time.Time if expiresInHours > 0 { t := time.Now().Add(time.Duration(expiresInHours) * time.Hour) expiresAt = &t } code, err := s.st.CreateInvite(ctx, createdBy, maxUses, expiresAt) if err != nil { return nil, fmt.Errorf("%w: failed to create invite: %v", ErrInternal, err) } invite, err := s.st.GetInvite(ctx, code) if err != nil || invite == nil { return nil, fmt.Errorf("%w: failed to retrieve invite: %v", ErrInternal, err) } return invite, nil } // ListInvites returns all invites. func (s *InviteService) ListInvites(ctx context.Context) ([]*db.Invite, error) { invites, err := s.st.ListInvites(ctx) if err != nil { return nil, fmt.Errorf("%w: failed to list invites: %v", ErrInternal, err) } return invites, nil } // RevokeInvite revokes an invite by code. func (s *InviteService) RevokeInvite(ctx context.Context, code string) error { invite, err := s.st.GetInvite(ctx, code) if err != nil || invite == nil { return fmt.Errorf("%w: invite not found", ErrNotFound) } if err := s.st.RevokeInvite(ctx, code); err != nil { return fmt.Errorf("%w: failed to revoke invite: %v", ErrInternal, err) } return nil }