Introduces invite, verify, accept, resend, and revoke flows using team_members and invitation tokens, sends the branded invitation template, and requires account activation before team login. Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
dbgen "Yimaru-Backend/gen/db"
|
|
"Yimaru-Backend/internal/domain"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
func mapDBTeamInvitation(row dbgen.TeamInvitation) domain.TeamInvitation {
|
|
inv := domain.TeamInvitation{
|
|
ID: row.ID,
|
|
TeamMemberID: row.TeamMemberID,
|
|
Token: row.Token,
|
|
Status: domain.TeamInvitationStatus(row.Status),
|
|
ExpiresAt: row.ExpiresAt.Time,
|
|
CreatedAt: row.CreatedAt.Time,
|
|
}
|
|
if row.InvitedBy.Valid {
|
|
inv.InvitedBy = &row.InvitedBy.Int64
|
|
}
|
|
if row.AcceptedAt.Valid {
|
|
t := row.AcceptedAt.Time
|
|
inv.AcceptedAt = &t
|
|
}
|
|
if row.UpdatedAt.Valid {
|
|
t := row.UpdatedAt.Time
|
|
inv.UpdatedAt = &t
|
|
}
|
|
return inv
|
|
}
|
|
|
|
func (s *Store) CreateTeamInvitation(ctx context.Context, invitation domain.TeamInvitation) (domain.TeamInvitation, error) {
|
|
var invitedBy pgtype.Int8
|
|
if invitation.InvitedBy != nil {
|
|
invitedBy = pgtype.Int8{Int64: *invitation.InvitedBy, Valid: true}
|
|
}
|
|
|
|
row, err := s.queries.CreateTeamInvitation(ctx, dbgen.CreateTeamInvitationParams{
|
|
TeamMemberID: invitation.TeamMemberID,
|
|
Token: invitation.Token,
|
|
ExpiresAt: pgtype.Timestamptz{Time: invitation.ExpiresAt, Valid: true},
|
|
InvitedBy: invitedBy,
|
|
})
|
|
if err != nil {
|
|
return domain.TeamInvitation{}, err
|
|
}
|
|
return mapDBTeamInvitation(row), nil
|
|
}
|
|
|
|
func (s *Store) GetTeamInvitationByToken(ctx context.Context, token string) (domain.TeamInvitation, error) {
|
|
row, err := s.queries.GetTeamInvitationByToken(ctx, token)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return domain.TeamInvitation{}, domain.ErrTeamInvitationNotFound
|
|
}
|
|
return domain.TeamInvitation{}, err
|
|
}
|
|
return mapDBTeamInvitation(row), nil
|
|
}
|
|
|
|
func (s *Store) GetTeamInvitationByID(ctx context.Context, id int64) (domain.TeamInvitation, error) {
|
|
row, err := s.queries.GetTeamInvitationByID(ctx, id)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return domain.TeamInvitation{}, domain.ErrTeamInvitationNotFound
|
|
}
|
|
return domain.TeamInvitation{}, err
|
|
}
|
|
return mapDBTeamInvitation(row), nil
|
|
}
|
|
|
|
func (s *Store) GetPendingTeamInvitationByMemberID(ctx context.Context, memberID int64) (domain.TeamInvitation, error) {
|
|
row, err := s.queries.GetPendingTeamInvitationByMemberID(ctx, memberID)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return domain.TeamInvitation{}, domain.ErrTeamInvitationNotFound
|
|
}
|
|
return domain.TeamInvitation{}, err
|
|
}
|
|
return mapDBTeamInvitation(row), nil
|
|
}
|
|
|
|
func (s *Store) RevokePendingTeamInvitationsForMember(ctx context.Context, memberID int64) error {
|
|
return s.queries.RevokePendingTeamInvitationsForMember(ctx, memberID)
|
|
}
|
|
|
|
func (s *Store) AcceptTeamInvitation(ctx context.Context, invitationID int64) (domain.TeamInvitation, error) {
|
|
row, err := s.queries.AcceptTeamInvitation(ctx, invitationID)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return domain.TeamInvitation{}, domain.ErrTeamInvitationNotFound
|
|
}
|
|
return domain.TeamInvitation{}, err
|
|
}
|
|
return mapDBTeamInvitation(row), nil
|
|
}
|
|
|
|
func (s *Store) RevokeTeamInvitation(ctx context.Context, invitationID int64) (domain.TeamInvitation, error) {
|
|
row, err := s.queries.RevokeTeamInvitation(ctx, invitationID)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return domain.TeamInvitation{}, domain.ErrTeamInvitationNotFound
|
|
}
|
|
return domain.TeamInvitation{}, err
|
|
}
|
|
return mapDBTeamInvitation(row), nil
|
|
}
|
|
|
|
func (s *Store) ExpireTeamInvitation(ctx context.Context, invitationID int64) error {
|
|
return s.queries.ExpireTeamInvitation(ctx, invitationID)
|
|
}
|
|
|
|
func (s *Store) ListTeamInvitations(ctx context.Context, status *string, limit, offset int32) ([]domain.TeamInvitationWithMember, int64, error) {
|
|
rows, err := s.queries.ListTeamInvitations(ctx, dbgen.ListTeamInvitationsParams{
|
|
Status: toPgText(status),
|
|
Offset: pgtype.Int4{Int32: offset, Valid: true},
|
|
Limit: pgtype.Int4{Int32: limit, Valid: true},
|
|
})
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
out := make([]domain.TeamInvitationWithMember, 0, len(rows))
|
|
var total int64
|
|
for _, row := range rows {
|
|
inv := mapDBTeamInvitation(dbgen.TeamInvitation{
|
|
ID: row.ID,
|
|
TeamMemberID: row.TeamMemberID,
|
|
Token: row.Token,
|
|
Status: row.Status,
|
|
ExpiresAt: row.ExpiresAt,
|
|
InvitedBy: row.InvitedBy,
|
|
AcceptedAt: row.AcceptedAt,
|
|
CreatedAt: row.CreatedAt,
|
|
UpdatedAt: row.UpdatedAt,
|
|
})
|
|
out = append(out, domain.TeamInvitationWithMember{
|
|
TeamInvitation: inv,
|
|
Email: row.Email,
|
|
FirstName: row.FirstName,
|
|
LastName: row.LastName,
|
|
TeamRole: domain.TeamRole(row.TeamRole),
|
|
})
|
|
total = row.TotalCount
|
|
}
|
|
return out, total, nil
|
|
}
|