Files
ahp 422e6ed289 feat(backend): implement HTTP API, auth, chat, and admin surfaces
Expose learning, registration controls, mentor chat, realtime WebSocket, and staff administration.
2026-10-07 19:40:26 +03:30

131 lines
3.4 KiB
Go

package api
import (
"context"
"fmt"
"net/http"
"time"
"pargar/backend/internal/models"
)
func (s *Server) handleListEvents(w http.ResponseWriter, r *http.Request) {
u := currentUser(r)
events, err := s.store.ListUpcomingEvents(r.Context(), u.ID)
if err != nil {
writeErr(w, http.StatusInternalServerError, "بارگذاری رویدادها ممکن نشد")
return
}
if events == nil {
events = []models.Event{}
}
writeJSON(w, http.StatusOK, map[string]any{"events": events})
}
func (s *Server) handleGetEvent(w http.ResponseWriter, r *http.Request) {
u := currentUser(r)
id := routeID(r, "id")
e, err := s.store.GetEvent(r.Context(), id)
if err != nil {
writeErr(w, http.StatusNotFound, "رویداد پیدا نشد")
return
}
e.Rsvped = s.rsvped(r, e.ID, u.ID)
writeJSON(w, http.StatusOK, map[string]any{"event": e})
}
func (s *Server) rsvped(r *http.Request, eventID, userID int64) bool {
events, err := s.store.ListUpcomingEvents(r.Context(), userID)
if err != nil {
return false
}
for _, e := range events {
if e.ID == eventID {
return e.Rsvped
}
}
return false
}
func (s *Server) handleRsvp(w http.ResponseWriter, r *http.Request) {
u := currentUser(r)
id := routeID(r, "id")
if err := s.store.Rsvp(r.Context(), id, u.ID); err != nil {
writeErr(w, http.StatusInternalServerError, "ثبت‌نام در رویداد ممکن نشد")
return
}
e, err := s.store.GetEvent(r.Context(), id)
if err == nil {
go func() {
_ = s.notify.Notify(context.Background(), u.ID, "event", "rsvp",
"ثبت‌نام رویداد تأیید شد", "شما در رویداد «"+e.Title+"» ثبت‌نام کرده‌اید. هنگام شروع به شما یادآوری می‌کنیم.", "/unwrap?tab=events", nil)
}()
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "rsvped": true})
}
func (s *Server) handleUnrsvp(w http.ResponseWriter, r *http.Request) {
u := currentUser(r)
id := routeID(r, "id")
if err := s.store.Unrsvp(r.Context(), id, u.ID); err != nil {
writeErr(w, http.StatusInternalServerError, "لغو ثبت‌نام رویداد ممکن نشد")
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "rsvped": false})
}
func (s *Server) handleEventICS(w http.ResponseWriter, r *http.Request) {
u := currentUser(r)
id := routeID(r, "id")
e, err := s.store.GetEvent(r.Context(), id)
if err != nil {
writeErr(w, http.StatusNotFound, "رویداد پیدا نشد")
return
}
_ = u // any authenticated user may download
ics := buildICS(e, s.cfg.PublicURL)
w.Header().Set("Content-Type", "text/calendar; charset=utf-8")
w.Header().Set("Content-Disposition", "attachment; filename=event-"+fmt.Sprintf("%d", e.ID)+".ics")
_, _ = w.Write([]byte(ics))
}
func buildICS(e *models.Event, origin string) string {
f := func(t time.Time) string { return t.UTC().Format("20060102T150405Z") }
return fmt.Sprintf(`BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Pargar//Events//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:%d@pargar
DTSTAMP:%s
DTSTART:%s
DTEND:%s
SUMMARY:%s
DESCRIPTION:%s
LOCATION:%s
END:VEVENT
END:VCALENDAR
`, e.ID, f(time.Now()), f(e.StartsAt), f(e.EndsAt),
escapeICS(e.Title), escapeICS(e.Description), escapeICS(e.ExternalURL))
}
func escapeICS(v string) string {
out := ""
for _, r := range v {
switch r {
case '\\':
out += "\\\\"
case ';':
out += "\\;"
case ',':
out += "\\,"
case '\n':
out += "\\n"
default:
out += string(r)
}
}
return out
}