feat(workspace): implement Yjs CRDT real-time collaborative editing (resolves FLAW-01)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 29s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 29s
This commit is contained in:
1 parent
0bdeb5f320
commit
ef8cf8f022
13 files changed
+1028
-85
No files matched your search
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -30,6 +31,9 @@ type Server struct {
|
||||
}
|
||||
|
||||
func NewServer(cfg *config.Config, store *storage.Store, wsHub *ws.Hub, staticFS fs.FS) *Server {
|
||||
if wsHub != nil && store != nil {
|
||||
wsHub.SetCRDTStore(store)
|
||||
}
|
||||
srv := &Server{
|
||||
cfg: cfg,
|
||||
store: store,
|
||||
@@ -141,6 +145,10 @@ func (s *Server) Router() http.Handler {
|
||||
r.Post("/api/v1/documents/{id}/lock/heartbeat", s.handleHeartbeatDocLock)
|
||||
r.Delete("/api/v1/documents/{id}/lock", s.handleUnlockDoc)
|
||||
|
||||
// Document CRDT Realtime Sync
|
||||
r.Get("/api/v1/documents/{id}/crdt", s.handleGetDocCRDT)
|
||||
r.Put("/api/v1/documents/{id}/crdt", s.handleSaveDocCRDT)
|
||||
|
||||
// Shared Lists & Realtime Collaboration
|
||||
r.Get("/api/v1/lists", s.handleGetAllLists)
|
||||
r.Post("/api/v1/lists", s.handleCreateList)
|
||||
@@ -1373,6 +1381,68 @@ func (s *Server) handleUnlockDoc(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"unlocked": released})
|
||||
}
|
||||
|
||||
// --- DOCUMENT CRDT REALTIME SYNC ---
|
||||
|
||||
func (s *Server) handleGetDocCRDT(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
docID := chi.URLParam(r, "id")
|
||||
if _, err := s.store.GetDocumentByID(userID, docID); err != nil {
|
||||
writeError(w, http.StatusNotFound, "Dokument nicht gefunden")
|
||||
return
|
||||
}
|
||||
stateBlob, updates, err := s.store.GetDocumentCRDTState(docID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
b64State := ""
|
||||
if len(stateBlob) > 0 {
|
||||
b64State = base64.StdEncoding.EncodeToString(stateBlob)
|
||||
}
|
||||
var b64Updates []string
|
||||
for _, u := range updates {
|
||||
b64Updates = append(b64Updates, base64.StdEncoding.EncodeToString(u))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"docId": docID,
|
||||
"state": b64State,
|
||||
"updates": b64Updates,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleSaveDocCRDT(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
docID := chi.URLParam(r, "id")
|
||||
doc, err := s.store.GetDocumentByID(userID, docID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "Dokument nicht gefunden")
|
||||
return
|
||||
}
|
||||
if !doc.IsOwner && doc.Permission != "write" {
|
||||
writeError(w, http.StatusForbidden, "Keine Schreibberechtigung")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiger Request-Body")
|
||||
return
|
||||
}
|
||||
if req.State != "" {
|
||||
raw, err := base64.StdEncoding.DecodeString(req.State)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges Base64 Format")
|
||||
return
|
||||
}
|
||||
if err := s.store.SaveDocumentCRDTState(docID, raw, userID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"success": true})
|
||||
}
|
||||
|
||||
// --- REALTIME LISTS & SHARING ---
|
||||
|
||||
func (s *Server) handleGetAllLists(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in new issue
Block a user