Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,158 changes: 0 additions & 1,158 deletions pkg/handlers/event.go

This file was deleted.

132 changes: 132 additions & 0 deletions pkg/handlers/event_admin_registration_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package handlers

import (
"context"
"errors"
"net/http"
"strconv"
"strings"

"github.com/SCE-Development/SCEvents/pkg/models"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/mongo"
)

func (h *EventHandler) loadEventAndAuthorizeAdmin(ctx context.Context, eventID, userID, userRole string) bool {
ev, err := h.stores.Mongo.GetEventByID(ctx, eventID)
if err != nil {
return false
}
return ev.CanEdit(userID, userRole)
}

func parsePagination(c *gin.Context) (int64, int64, bool) {
limit := int64(50)
offset := int64(0)

if limitQ := strings.TrimSpace(c.Query("limit")); limitQ != "" {
parsed, err := strconv.ParseInt(limitQ, 10, 64)
if err != nil || parsed <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "limit must be a positive integer"})
return 0, 0, false
}
limit = parsed
}

if offsetQ := strings.TrimSpace(c.Query("offset")); offsetQ != "" {
parsed, err := strconv.ParseInt(offsetQ, 10, 64)
if err != nil || parsed < 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "offset must be a non-negative integer"})
return 0, 0, false
}
offset = parsed
}

return limit, offset, true
}

func (h *EventHandler) ListEventRegistrations(c *gin.Context) {
eventID := strings.TrimSpace(c.Param("id"))
if eventID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "event id is required"})
return
}

userID := strings.TrimSpace(c.GetString("userID"))
if userID == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "login required"})
return
}
userRole := strings.TrimSpace(c.GetString("userRole"))

limit, offset, ok := parsePagination(c)
if !ok {
return
}

if !h.loadEventAndAuthorizeAdmin(c.Request.Context(), eventID, userID, userRole) {
c.JSON(http.StatusForbidden, gin.H{"error": "you are not an admin of this event"})
return
}

registrations, err := h.stores.Mongo.ListRegistrationsByEventID(c.Request.Context(), eventID, limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch event registrations"})
return
}

statusCounts, err := h.stores.Mongo.CountRegistrationsByStatusForEvent(c.Request.Context(), eventID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch registration dashboard summary"})
return
}

summary := models.RegistrationDashboardSummary{
Pending: statusCounts[models.StatusPending],
Accepted: statusCounts[models.StatusAccepted],
Rejected: statusCounts[models.StatusRejected],
}
summary.Total = summary.Pending + summary.Accepted + summary.Rejected

c.JSON(http.StatusOK, gin.H{
"event_id": eventID,
"summary": summary,
"attendees": registrations,
"registrations": registrations,
"limit": limit,
"offset": offset,
})
}

func (h *EventHandler) GetEventRegistrationByRequestID(c *gin.Context) {
eventID := strings.TrimSpace(c.Param("id"))
requestID := strings.TrimSpace(c.Param("request_id"))
if eventID == "" || requestID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "event id and request id are required"})
return
}

userID := strings.TrimSpace(c.GetString("userID"))
if userID == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "login required"})
return
}
userRole := strings.TrimSpace(c.GetString("userRole"))

if !h.loadEventAndAuthorizeAdmin(c.Request.Context(), eventID, userID, userRole) {
c.JSON(http.StatusForbidden, gin.H{"error": "you are not an admin of this event"})
return
}

registrationReq, err := h.stores.Mongo.GetRegistrationByEventAndRequestID(c.Request.Context(), eventID, requestID)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
c.JSON(http.StatusNotFound, gin.H{"error": "registration request not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch registration request"})
return
}

c.JSON(http.StatusOK, registrationReq)
}
143 changes: 143 additions & 0 deletions pkg/handlers/event_attendee_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package handlers

import (
"errors"
"net/http"
"strings"

"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/mongo"
)

type PublicAttendee struct {
UserID string `json:"user_id"`
Name string `json:"name"`
}

func formatPublicAttendeeName(fullName string) string {
fullName = strings.TrimSpace(fullName)
if fullName == "" {
return "Registered attendee"
}

parts := strings.Fields(fullName)
if len(parts) == 1 {
return parts[0]
}

last := []rune(parts[len(parts)-1])
if len(last) == 0 {
return parts[0]
}

return parts[0] + " " + string(last[0]) + "."
}

func (h *EventHandler) GetEventAttendanceSummary(c *gin.Context) {
eventID := strings.TrimSpace(c.Param("id"))
if eventID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "event id is required",
})
return
}

_, err := h.stores.Mongo.GetEventByID(c.Request.Context(), eventID)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
c.JSON(http.StatusNotFound, gin.H{
"error": "event not found",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "failed to fetch event",
})
return
}

attendeeCount, err := h.stores.Mongo.CountAcceptedRegistrationsForEvent(c.Request.Context(), eventID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "failed to fetch attendance summary",
})
return
}

c.JSON(http.StatusOK, gin.H{
"event_id": eventID,
"attendee_count": attendeeCount,
})
}

func (h *EventHandler) GetEventAttendees(c *gin.Context) {
eventID := strings.TrimSpace(c.Param("id"))
if eventID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "event id is required",
})
return
}

userID := strings.TrimSpace(c.GetString("userID"))
if userID == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "login required",
})
return
}
userRole := strings.TrimSpace(c.GetString("userRole"))

event, err := h.stores.Mongo.GetEventByID(c.Request.Context(), eventID)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
c.JSON(http.StatusNotFound, gin.H{
"error": "event not found",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "failed to fetch event",
})
return
}
canView := event.CanEdit(userID, userRole)
if !canView {
hasAccepted, err := h.stores.Mongo.HasAcceptedRegistration(c.Request.Context(), eventID, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "failed to verify attendee access",
})
return
}
canView = hasAccepted
}

if !canView {
c.JSON(http.StatusForbidden, gin.H{
"error": "only registered attendees and event organizers can view the attendee list",
})
return
}

registrations, err := h.stores.Mongo.ListAcceptedRegistrationsForEvent(c.Request.Context(), eventID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "failed to fetch attendees",
})
return
}

attendees := make([]PublicAttendee, 0, len(registrations))
for _, r := range registrations {
attendees = append(attendees, PublicAttendee{
UserID: strings.TrimSpace(r.Registrant.UserID),
Name: formatPublicAttendeeName(r.Registrant.Name),
})
}

c.JSON(http.StatusOK, gin.H{
"event_id": eventID,
"attendees": attendees,
})
}
79 changes: 79 additions & 0 deletions pkg/handlers/event_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package handlers

import (
"net/http"
"strings"
"time"

"github.com/SCE-Development/SCEvents/pkg/db"
"github.com/SCE-Development/SCEvents/pkg/models"
"github.com/gin-gonic/gin"
)

type EventHandler struct {
stores *db.Stores
}

func NewEventHandler(stores *db.Stores) *EventHandler {
return &EventHandler{stores: stores}
}

const dateLayout = "2006-01-02"

type EventRegistrationStatus string

const (
EventRegistrationStatusNone EventRegistrationStatus = "none"
EventRegistrationStatusPending EventRegistrationStatus = "pending"
EventRegistrationStatusRegistered EventRegistrationStatus = "registered"
EventRegistrationStatusWaitlisted EventRegistrationStatus = "waitlisted"
EventRegistrationStatusRejected EventRegistrationStatus = "rejected"
)

type EventResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Date string `json:"date"`
EndDate string `json:"end_date,omitempty"`
Time string `json:"time"`
Location string `json:"location"`
Description string `json:"description"`
Admins []string `json:"admins"`
RegistrationForm []models.FormQuestion `json:"registration_form"`
MaxAttendees int `json:"max_attendees"`
CreatedAt string `json:"created_at"`
Status string `json:"status"`
Visibility string `json:"visibility"`
MinimumVisibleRole string `json:"minimum_visible_role,omitempty"`
WaitlistEnabled bool `json:"waitlist_enabled"`
WaitlistSize int `json:"waitlist_size,omitempty"`
PublishDate *time.Time `json:"publish_date,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
RegistrationStatus *EventRegistrationStatus `json:"registration_status,omitempty"`
}

func writeEventEditForbidden(c *gin.Context, ev *models.Event) {
if len(ev.Admins) == 0 {
c.JSON(http.StatusForbidden, gin.H{
"error": "this event has no dedicated admins; only site admins may modify it",
})
return
}
c.JSON(http.StatusForbidden, gin.H{
"error": "you are not an admin of this event",
})
}

func buildViewerFromContext(c *gin.Context) models.EventViewer {
accessLevel := 0
if v, exists := c.Get("accessLevel"); exists {
if n, ok := v.(int); ok {
accessLevel = n
}
}

return models.EventViewer{
UserID: strings.TrimSpace(c.GetString("userID")),
AccessLevel: accessLevel,
}
}
Loading
Loading