Files
MindSift/internal/api/realtime.go
T
sandrodachandClaude Opus 5.5 74c394efaa
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m31s
refactor: split sqlite.go and server.go by domain (FLAW-20)
Pure moves, no behaviour change: the storage layer now has users,
apikeys, thoughts, tasklists, documents, folders, tags, lists, teams and
system files next to sqlite.go (store, schema, migrations); the API has
handlers_*.go per area plus realtime.go and alarms.go next to server.go
(server, router, middleware). Declarations were moved verbatim with an
AST-based tool; imports pruned with goimports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:14:33 +01:00

129 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"encoding/json"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/sandrodach/mindsift/internal/auth"
)
func (s *Server) authenticateUserFromReq(r *http.Request) string {
token := r.URL.Query().Get("token")
if token == "" {
if authHdr := r.Header.Get("Authorization"); strings.HasPrefix(authHdr, "Bearer ") {
token = strings.TrimPrefix(authHdr, "Bearer ")
}
}
if token != "" {
if strings.HasPrefix(token, "mindsift_sec_") {
if user, err := s.store.ValidateAPIKeyAndGetUser(token); err == nil && user != nil && user.IsActive {
return user.ID
}
}
if claims, err := auth.ValidateToken(s.cfg.JWTSecret, token); err == nil && claims != nil {
if !s.store.IsTokenRevoked(token) {
if user, err := s.store.GetUserByID(claims.UserID); err == nil && user != nil && user.IsActive {
if claims.TokenVersion == 0 || claims.TokenVersion == user.TokenVersion {
return claims.UserID
}
}
}
}
}
return ""
}
func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
// Some corporate proxies and reverse proxies (e.g. OpenResty/Nginx or HTTP/2 intermediaries)
// strip or alter the Connection header (e.g. "close" or missing) while passing
// Sec-WebSocket-Key or Upgrade: websocket. Normalize it so Gorilla Upgrader succeeds.
if r.Header.Get("Sec-Websocket-Key") != "" {
if !strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") {
r.Header.Set("Connection", "Upgrade")
}
if !strings.Contains(strings.ToLower(r.Header.Get("Upgrade")), "websocket") {
r.Header.Set("Upgrade", "websocket")
}
}
roomID := chi.URLParam(r, "listId")
if roomID == "" {
roomID = chi.URLParam(r, "spaceId")
}
if roomID == "" {
roomID = r.URL.Query().Get("listId")
}
if roomID == "" {
roomID = r.URL.Query().Get("spaceId")
}
userID := s.authenticateUserFromReq(r)
if userID == "" {
writeError(w, http.StatusUnauthorized, "Nicht autorisiert – bitte anmelden")
return
}
s.wsHub.ServeWS(w, r, roomID, userID)
}
func (s *Server) handleSSE(w http.ResponseWriter, r *http.Request) {
roomID := r.URL.Query().Get("listId")
if roomID == "" {
roomID = r.URL.Query().Get("spaceId")
}
userID := s.authenticateUserFromReq(r)
if userID == "" {
writeError(w, http.StatusUnauthorized, "Nicht autorisiert – bitte anmelden")
return
}
var initialRooms []string
if roomsParam := r.URL.Query().Get("rooms"); roomsParam != "" {
for _, rm := range strings.Split(roomsParam, ",") {
rm = strings.TrimSpace(rm)
if rm != "" {
initialRooms = append(initialRooms, rm)
}
}
}
if docID := r.URL.Query().Get("docId"); docID != "" {
initialRooms = append(initialRooms, "doc:"+strings.TrimSpace(docID))
}
s.wsHub.ServeSSE(w, r, roomID, userID, initialRooms)
}
func (s *Server) handleSSERooms(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r.Context())
var req struct {
SSEID string `json:"sseId"`
Room string `json:"room"`
DocID string `json:"docId"`
Action string `json:"action"` // "join" or "leave"
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
return
}
if req.Room == "" && req.DocID != "" {
req.Room = "doc:" + req.DocID
}
if req.SSEID == "" || req.Room == "" {
writeError(w, http.StatusBadRequest, "sseId und room erforderlich")
return
}
if req.Action == "leave" {
s.wsHub.LeaveSSERoomByID(req.SSEID, userID, req.Room)
writeJSON(w, http.StatusOK, map[string]bool{"success": true})
return
}
if !s.wsHub.JoinSSERoomByID(req.SSEID, userID, req.Room) {
writeError(w, http.StatusForbidden, "Kein Zugriff auf diesen Raum")
return
}
writeJSON(w, http.StatusOK, map[string]bool{"success": true})
}