Merge pull request 'Added pagination to invites' (#34) from dev into main

This commit was merged in pull request #34.
This commit is contained in:
2026-09-01 10:19:17 +02:00
6 changed files with 105 additions and 9 deletions
+1
View File
@@ -0,0 +1 @@
package dttmr_api
+62 -3
View File
@@ -3,6 +3,7 @@ package handler
import ( import (
"log/slog" "log/slog"
"net/http" "net/http"
"strconv"
"github.com/robindittmar/dttmr-api/internal/api/response" "github.com/robindittmar/dttmr-api/internal/api/response"
"github.com/robindittmar/dttmr-api/internal/domain" "github.com/robindittmar/dttmr-api/internal/domain"
@@ -94,12 +95,59 @@ func (h *InviteHandler) DeleteInvite(w http.ResponseWriter, r *http.Request) {
// @Tags Invite // @Tags Invite
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} []domain.Invite // @Param page query int false "page"
// @Param count query int false "count"
// @Success 200 {object} response.Paginated[domain.Invite]
// @Error 400 {object} response.ErrorResponse "failed to decode request body"
// @Error 400 {object} response.ErrorResponse "invalid value for page"
// @Error 400 {object} response.ErrorResponse "invalid value for count"
// @Error 500 {object} response.ErrorResponse "failed to get invites" // @Error 500 {object} response.ErrorResponse "failed to get invites"
// @Router /user/invites [get] // @Router /user/invites [get]
func (h *InviteHandler) GetInvites(w http.ResponseWriter, r *http.Request) { func (h *InviteHandler) GetInvites(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
pageStr := r.URL.Query().Get("page")
if pageStr == "" {
pageStr = "1"
}
countStr := r.URL.Query().Get("count")
if countStr == "" {
countStr = "10"
}
page, err := strconv.Atoi(pageStr)
if err != nil {
slog.ErrorContext(ctx,
"failed to read page from query",
slog.String("page", pageStr))
response.Error(ctx, w, http.StatusBadRequest, "failed to decode request url")
return
}
count, err := strconv.Atoi(countStr)
if err != nil {
slog.ErrorContext(ctx,
"failed to read count from query",
slog.String("count", countStr))
response.Error(ctx, w, http.StatusBadRequest, "failed to decode request url")
return
}
if page < 1 {
slog.ErrorContext(ctx,
"page parameter is invalid",
slog.Int("page", page))
response.Error(ctx, w, http.StatusBadRequest, "invalid value for page")
return
}
if count <= 0 {
slog.ErrorContext(ctx,
"count parameter is invalid",
slog.Int("count", count))
response.Error(ctx, w, http.StatusBadRequest, "invalid value for count")
return
}
authContext, err := domain.GetAuthContext(ctx) authContext, err := domain.GetAuthContext(ctx)
if err != nil { if err != nil {
slog.ErrorContext(ctx, "failed to get auth context", slog.Any("error", err)) slog.ErrorContext(ctx, "failed to get auth context", slog.Any("error", err))
@@ -107,12 +155,23 @@ func (h *InviteHandler) GetInvites(w http.ResponseWriter, r *http.Request) {
return return
} }
invites, err := h.InviteService.GetInvites(ctx, authContext.UserID) invites, err := h.InviteService.GetInvites(ctx, authContext.UserID, page, count)
if err != nil { if err != nil {
slog.ErrorContext(ctx, "failed to get invites", slog.Any("error", err)) slog.ErrorContext(ctx, "failed to get invites", slog.Any("error", err))
response.Error(ctx, w, http.StatusInternalServerError, "failed to get invites") response.Error(ctx, w, http.StatusInternalServerError, "failed to get invites")
return return
} }
response.JSON(ctx, w, http.StatusOK, invites) total, err := h.InviteService.CountInvites(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 get invites")
return
}
response.JSON(ctx, w, http.StatusOK, response.Paginated[domain.Invite]{
Count: len(invites),
Total: total,
Data: invites,
})
} }
+6
View File
@@ -0,0 +1,6 @@
package request
type GetInvitesPayload struct {
Page int
CountPerPage int
}
+7
View File
@@ -0,0 +1,7 @@
package response
type Paginated[T any] struct {
Count int `json:"count"`
Total int `json:"total"`
Data []T `json:"data"`
}
+13 -3
View File
@@ -25,7 +25,8 @@ type InviteRepository interface {
DeleteInvite(ctx context.Context, userID string, inviteID string) error DeleteInvite(ctx context.Context, userID string, inviteID string) error
ConsumeInvite(ctx context.Context, inviteID string, inviteeUserID string) error ConsumeInvite(ctx context.Context, inviteID string, inviteeUserID string) error
GetInvite(ctx context.Context, code string) (*Invite, error) GetInvite(ctx context.Context, code string) (*Invite, error)
GetInvites(ctx context.Context, userID string) ([]Invite, error) GetInvites(ctx context.Context, userID string, offset int, count int) ([]Invite, error)
CountInvites(ctx context.Context, userID string) (int, error)
} }
type InviteService struct { type InviteService struct {
@@ -89,10 +90,19 @@ func (s *InviteService) GetInvite(ctx context.Context, code string) (*Invite, er
return invite, nil return invite, nil
} }
func (s *InviteService) GetInvites(ctx context.Context, userID string) ([]Invite, error) { func (s *InviteService) GetInvites(ctx context.Context, userID string, page int, countPerPage int) ([]Invite, error) {
if userID == "" { if userID == "" {
return nil, ErrUserIDMissing return nil, ErrUserIDMissing
} }
return s.repo.GetInvites(ctx, userID) offset := (page - 1) * countPerPage
return s.repo.GetInvites(ctx, userID, offset, countPerPage)
}
func (s *InviteService) CountInvites(ctx context.Context, userID string) (int, error) {
if userID == "" {
return 0, ErrUserIDMissing
}
return s.repo.CountInvites(ctx, userID)
} }
+16 -3
View File
@@ -81,10 +81,10 @@ func (r *InviteRepo) GetInvite(ctx context.Context, code string) (*domain.Invite
return &invite, nil return &invite, nil
} }
func (r *InviteRepo) GetInvites(ctx context.Context, userID string) ([]domain.Invite, error) { func (r *InviteRepo) GetInvites(ctx context.Context, userID string, offset int, count int) ([]domain.Invite, error) {
rows, err := r.db.QueryContext(ctx, rows, err := r.db.QueryContext(ctx,
"SELECT id, code, expires_at, consumed_at FROM invites WHERE inviter_user_id=$1", "SELECT id, code, expires_at, consumed_at FROM invites WHERE inviter_user_id=$1 ORDER BY created_at DESC OFFSET $2 LIMIT $3",
userID, userID, offset, count,
) )
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
@@ -107,3 +107,16 @@ func (r *InviteRepo) GetInvites(ctx context.Context, userID string) ([]domain.In
return invites, nil return invites, nil
} }
func (r *InviteRepo) CountInvites(ctx context.Context, userID string) (int, error) {
var count int
err := r.db.QueryRowContext(ctx,
"SELECT COUNT(*) FROM invites WHERE inviter_user_id=$1",
userID,
).Scan(&count)
if err != nil {
return 0, fmt.Errorf("failed to count invites: %w", err)
}
return count, nil
}