feat: added endpoint to fetch invites status
This commit is contained in:
@@ -175,3 +175,34 @@ func (h *InviteHandler) GetInvites(w http.ResponseWriter, r *http.Request) {
|
|||||||
Data: invites,
|
Data: invites,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInvitesStatus handles fetching the counts of a users invitations
|
||||||
|
//
|
||||||
|
// @Summary Get invitations status
|
||||||
|
// @Description Gets active/expired/used counts for all the users invites
|
||||||
|
// @Tags Invite
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.InviteCounts
|
||||||
|
// @Error 500 {object} response.ErrorResponse "failed to count invites"
|
||||||
|
// @Router /user/invites [get]
|
||||||
|
|
||||||
|
func (h *InviteHandler) GetInvitesStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
authContext, err := domain.GetAuthContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "failed to get auth context", slog.Any("error", err))
|
||||||
|
response.Error(ctx, w, http.StatusInternalServerError, "failed to count invites")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
counts, err := h.InviteService.CountInvitesStructured(ctx, authContext.UserID)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "failed to count invites", slog.Any("error", err))
|
||||||
|
response.Error(ctx, w, http.StatusInternalServerError, "failed to count invites")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.JSON(ctx, w, http.StatusOK, counts)
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ func NewMux(cfg Config) http.Handler {
|
|||||||
apiMux.Handle("POST /user/invites", protected(inviteHandler.CreateInvite))
|
apiMux.Handle("POST /user/invites", protected(inviteHandler.CreateInvite))
|
||||||
apiMux.Handle("DELETE /user/invites/{id}", protected(inviteHandler.DeleteInvite))
|
apiMux.Handle("DELETE /user/invites/{id}", protected(inviteHandler.DeleteInvite))
|
||||||
apiMux.Handle("GET /user/invites", protected(inviteHandler.GetInvites))
|
apiMux.Handle("GET /user/invites", protected(inviteHandler.GetInvites))
|
||||||
|
apiMux.Handle("GET /user/invites/status", protected(inviteHandler.GetInvitesStatus))
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
apiMux.Handle("POST /lists", protected(listHandler.CreateList))
|
apiMux.Handle("POST /lists", protected(listHandler.CreateList))
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ type Invite struct {
|
|||||||
ConsumedAt *time.Time `json:"consumed_at"`
|
ConsumedAt *time.Time `json:"consumed_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InviteCounts struct {
|
||||||
|
Active int `json:"active"`
|
||||||
|
Expired int `json:"expired"`
|
||||||
|
Used int `json:"used"`
|
||||||
|
}
|
||||||
|
|
||||||
type InviteRepository interface {
|
type InviteRepository interface {
|
||||||
CreateInvite(ctx context.Context, inviterUserID string, code string, expiresAt time.Time) (*Invite, error)
|
CreateInvite(ctx context.Context, inviterUserID string, code string, expiresAt time.Time) (*Invite, error)
|
||||||
DeleteInvite(ctx context.Context, userID string, inviteID string) error
|
DeleteInvite(ctx context.Context, userID string, inviteID string) error
|
||||||
@@ -28,6 +34,7 @@ type InviteRepository interface {
|
|||||||
GetInvite(ctx context.Context, code string) (*Invite, error)
|
GetInvite(ctx context.Context, code string) (*Invite, error)
|
||||||
GetInvites(ctx context.Context, userID string, offset int, count int) ([]Invite, error)
|
GetInvites(ctx context.Context, userID string, offset int, count int) ([]Invite, error)
|
||||||
CountInvites(ctx context.Context, userID string) (int, error)
|
CountInvites(ctx context.Context, userID string) (int, error)
|
||||||
|
CountInvitesStructured(ctx context.Context, userID string) (*InviteCounts, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type InviteService struct {
|
type InviteService struct {
|
||||||
@@ -107,3 +114,11 @@ func (s *InviteService) CountInvites(ctx context.Context, userID string) (int, e
|
|||||||
|
|
||||||
return s.repo.CountInvites(ctx, userID)
|
return s.repo.CountInvites(ctx, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InviteService) CountInvitesStructured(ctx context.Context, userID string) (*InviteCounts, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, ErrUserIDMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.CountInvitesStructured(ctx, userID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -128,3 +128,33 @@ func (r *InviteRepo) CountInvites(ctx context.Context, userID string) (int, erro
|
|||||||
|
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *InviteRepo) CountInvitesStructured(ctx context.Context, userID string) (*domain.InviteCounts, error) {
|
||||||
|
var counts domain.InviteCounts
|
||||||
|
conn := r.conn(ctx)
|
||||||
|
err := conn.QueryRowContext(ctx,
|
||||||
|
"SELECT COUNT(*) FROM invites WHERE inviter_user_id=$1 AND expires_at > NOW() AND consumed_at IS NULL",
|
||||||
|
userID,
|
||||||
|
).Scan(&counts.Active)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to count active invites: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.QueryRowContext(ctx,
|
||||||
|
"SELECT COUNT(*) FROM invites WHERE inviter_user_id=$1 AND expires_at < NOW() AND consumed_at IS NULL",
|
||||||
|
userID,
|
||||||
|
).Scan(&counts.Expired)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to count expired invites: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.QueryRowContext(ctx,
|
||||||
|
"SELECT COUNT(consumed_at) FROM invites WHERE inviter_user_id=$1",
|
||||||
|
userID,
|
||||||
|
).Scan(&counts.Used)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to count consumed invites: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &counts, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user