feat(backend): implement HTTP API, auth, chat, and admin surfaces
Expose learning, registration controls, mentor chat, realtime WebSocket, and staff administration.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user