112 lines
3.9 KiB
Go
112 lines
3.9 KiB
Go
package api
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"messenger/internal/auth"
|
|
"messenger/internal/media"
|
|
"messenger/internal/platform/config"
|
|
"messenger/internal/platform/ratelimit"
|
|
"messenger/internal/realtime"
|
|
"messenger/internal/store"
|
|
)
|
|
|
|
type apiHandler struct {
|
|
store *store.Store
|
|
tokens *auth.TokenManager
|
|
hub *realtime.Hub
|
|
log *slog.Logger
|
|
secure bool
|
|
media media.Storage
|
|
notifier *pushNotifier
|
|
}
|
|
|
|
// NewHandler builds the /api/v1 route tree with all middleware applied.
|
|
// static, when non-nil, serves the built frontend for non-API paths.
|
|
func NewHandler(st *store.Store, tm *auth.TokenManager, hub *realtime.Hub, log *slog.Logger, cfg *config.Config, ms media.Storage, static http.Handler) http.Handler {
|
|
h := &apiHandler{
|
|
store: st,
|
|
tokens: tm,
|
|
hub: hub,
|
|
log: log,
|
|
secure: cfg.Environment == "production",
|
|
media: ms,
|
|
notifier: newPushNotifier(st, hub, log, cfg.VAPIDPublicKey, cfg.VAPIDPrivateKey, cfg.VAPIDSubject),
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
authHandler := requireAuth(tm, http.HandlerFunc(h.websocket))
|
|
|
|
authLimiter := ratelimit.New(20, time.Minute)
|
|
limited := func(inner http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if !authLimiter.Allow(r) {
|
|
w.Header().Set("Retry-After", "60")
|
|
writeError(w, http.StatusTooManyRequests, "too many requests")
|
|
return
|
|
}
|
|
inner(w, r)
|
|
}
|
|
}
|
|
|
|
mux.HandleFunc("GET /api/v1/ping", func(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]string{"message": "pong"})
|
|
})
|
|
|
|
mux.HandleFunc("POST /api/v1/auth/register", limited(h.register))
|
|
mux.HandleFunc("POST /api/v1/auth/login", limited(h.login))
|
|
mux.HandleFunc("POST /api/v1/auth/refresh", limited(h.refresh))
|
|
mux.HandleFunc("POST /api/v1/auth/logout", h.logout)
|
|
|
|
mux.Handle("GET /api/v1/users/me", requireAuth(tm, h.me))
|
|
mux.Handle("GET /api/v1/users", requireAuth(tm, h.searchUsers))
|
|
mux.Handle("GET /api/v1/search/messages", requireAuth(tm, h.searchMessages))
|
|
|
|
mux.Handle("GET /api/v1/contacts", requireAuth(tm, h.listContacts))
|
|
mux.Handle("POST /api/v1/contacts", requireAuth(tm, h.addContact))
|
|
mux.Handle("DELETE /api/v1/contacts/{user_id}", requireAuth(tm, h.removeContact))
|
|
|
|
mux.Handle("GET /api/v1/conversations", requireAuth(tm, h.listConversations))
|
|
mux.Handle("POST /api/v1/conversations", requireAuth(tm, h.createConversation))
|
|
mux.Handle("GET /api/v1/conversations/{id}/members", requireAuth(tm, h.listMembers))
|
|
mux.Handle("POST /api/v1/conversations/{id}/members", requireAuth(tm, h.addMember))
|
|
mux.Handle("DELETE /api/v1/conversations/{id}/members/{user_id}", requireAuth(tm, h.removeMember))
|
|
mux.Handle("GET /api/v1/conversations/{id}/messages", requireAuth(tm, h.listMessages))
|
|
mux.Handle("POST /api/v1/conversations/{id}/messages", requireAuth(tm, h.sendMessage))
|
|
mux.Handle("PUT /api/v1/conversations/{id}/messages/{msg_id}/reactions", requireAuth(tm, h.addReaction))
|
|
mux.Handle("DELETE /api/v1/conversations/{id}/messages/{msg_id}/reactions/{emoji}", requireAuth(tm, h.removeReaction))
|
|
|
|
mux.Handle("POST /api/v1/media", requireAuth(tm, h.uploadMedia))
|
|
mux.Handle("GET /api/v1/media/{id}", requireAuth(tm, h.downloadMedia))
|
|
mux.Handle("GET /api/v1/media/{id}/thumb", requireAuth(tm, h.downloadThumb))
|
|
|
|
mux.Handle("POST /api/v1/push/subscribe", requireAuth(tm, h.subscribePush))
|
|
mux.Handle("DELETE /api/v1/push/subscribe", requireAuth(tm, h.unsubscribePush))
|
|
|
|
mux.Handle("GET /ws", authHandler)
|
|
|
|
if static != nil {
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/api/") {
|
|
writeError(w, http.StatusNotFound, "not found")
|
|
return
|
|
}
|
|
static.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
return mux
|
|
}
|
|
|
|
func (h *apiHandler) websocket(w http.ResponseWriter, r *http.Request) {
|
|
userID, ok := userIDFrom(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "unauthenticated")
|
|
return
|
|
}
|
|
h.hub.HandleWS(w, r, userID)
|
|
}
|