feat(orion): add rest api for waitlist
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/flowy-live/llink/internal/network"
|
||||
"github.com/flowy-live/llink/internal/particle"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
"github.com/flowy-live/llink/internal/waitlist"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@@ -23,15 +24,17 @@ type Handler struct {
|
||||
networkSvc network.Service
|
||||
particleSvc particle.Service
|
||||
depotSvc depot.Service
|
||||
waitlistSvc waitlist.Service
|
||||
}
|
||||
|
||||
func NewHandler(authSvc auth.AuthService, humanSvc human.Service, networkSvc network.Service, particleSvc particle.Service, depotSvc depot.Service) *Handler {
|
||||
func NewHandler(authSvc auth.AuthService, humanSvc human.Service, networkSvc network.Service, particleSvc particle.Service, depotSvc depot.Service, waitlistSvc waitlist.Service) *Handler {
|
||||
return &Handler{
|
||||
authSvc: authSvc,
|
||||
humanSvc: humanSvc,
|
||||
networkSvc: networkSvc,
|
||||
particleSvc: particleSvc,
|
||||
depotSvc: depotSvc,
|
||||
waitlistSvc: waitlistSvc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,6 +777,159 @@ func (h *Handler) ConfirmUpload(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Waitlist DTOs
|
||||
// ============================================================================
|
||||
|
||||
type AddToWaitlistRequest struct {
|
||||
Email string `json:"email"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
type WaitlistEntryResponse struct {
|
||||
Id int `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
InvitedAt *time.Time `json:"invited_at,omitempty"`
|
||||
}
|
||||
|
||||
type InviteWaitlistEntrantRequest struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Waitlist Handlers
|
||||
// ============================================================================
|
||||
|
||||
// AddToWaitlist adds an email to the waitlist (public, no auth)
|
||||
func (h *Handler) AddToWaitlist(w http.ResponseWriter, r *http.Request) {
|
||||
var req AddToWaitlistRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" {
|
||||
http.Error(w, "email is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.waitlistSvc.AddToWaitlist(r.Context(), req.Email, req.Metadata)
|
||||
if err != nil {
|
||||
if errors.Is(err, waitlist.AlreadyInWaitlistError) {
|
||||
http.Error(w, "already in the waitlist", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
slog.Error("failed to add to waitlist", "error", err, "email", req.Email)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetWaitlist returns all waitlist entries (admin-only)
|
||||
func (h *Handler) GetWaitlist(w http.ResponseWriter, r *http.Request) {
|
||||
if !middleware.IsAdminFromContext(r.Context()) {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
filterParam := r.URL.Query().Get("filter")
|
||||
filter := waitlist.GetWaitlistFilterAll
|
||||
switch filterParam {
|
||||
case "invited":
|
||||
filter = waitlist.GetWaitlistFilterInvitedOnly
|
||||
case "uninvited":
|
||||
filter = waitlist.GetWaitlistFilterUninvitedOnly
|
||||
}
|
||||
|
||||
entries, err := h.waitlistSvc.GetWaitlist(r.Context(), filter)
|
||||
if err != nil {
|
||||
slog.Error("failed to get waitlist", "error", err)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := make([]WaitlistEntryResponse, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
resp = append(resp, waitlistEntryToDTO(e))
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// GetWaitlistEntry returns a single waitlist entry by email (admin-only)
|
||||
func (h *Handler) GetWaitlistEntry(w http.ResponseWriter, r *http.Request) {
|
||||
if !middleware.IsAdminFromContext(r.Context()) {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
email := r.PathValue("email")
|
||||
if email == "" {
|
||||
http.Error(w, "email is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
entry, err := h.waitlistSvc.GetWaitlistEntryByEmail(r.Context(), email)
|
||||
if err != nil {
|
||||
if errors.Is(err, waitlist.EntryNotFoundError) {
|
||||
http.Error(w, "entry not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
slog.Error("failed to get waitlist entry", "error", err, "email", email)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(waitlistEntryToDTO(entry))
|
||||
}
|
||||
|
||||
// InviteWaitlistEntrant marks a waitlist entry as invited (admin-only)
|
||||
func (h *Handler) InviteWaitlistEntrant(w http.ResponseWriter, r *http.Request) {
|
||||
if !middleware.IsAdminFromContext(r.Context()) {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var req InviteWaitlistEntrantRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" {
|
||||
http.Error(w, "email is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.waitlistSvc.MarkWaitlistEntryInvited(r.Context(), req.Email); err != nil {
|
||||
if errors.Is(err, waitlist.EntryNotFoundError) {
|
||||
http.Error(w, "entry not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
slog.Error("failed to invite waitlist entrant", "error", err, "email", req.Email)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func waitlistEntryToDTO(e *waitlist.WaitlistEntry) WaitlistEntryResponse {
|
||||
return WaitlistEntryResponse{
|
||||
Id: e.Id,
|
||||
Email: e.Email,
|
||||
Metadata: e.Metadata,
|
||||
CreatedAt: e.CreatedAt,
|
||||
InvitedAt: e.InvitedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user