feat: presença por sessão, eventos SSE em tempo real e autosave
- presence.go: hub SSE + registro de presença (TTL 30s por heartbeat)
- GET /api/events (EventSource, auth cookie/Bearer) transmite saved/presence;
POST /api/presence publica {session, notes[]}; create/update/restore/delete
publicam savedEvent com a session de origem (eco ignorado pelo autor)
- PUT/POST/restore aceitam campo opcional session
- UI: id de sessão em sessionStorage; selo 👥 na aba, na lista de notas e no
topo do editor quando outra sessão tem a mesma nota aberta; aba LIMPA
recarrega sozinha ao receber saved; aba SUJA abre o diálogo de conflito
automaticamente (conflictPending evita diálogos empilhados)
- Autosave: debounce de 1,5s em qualquer alteração (rascunho vira nota),
mensagem de revisão 'autosave', status 'salvando…', toggle nas Preferências
- Testes: SSE hello/presence/saved + presença 400/401 via httptest
This commit is contained in:
1 parent
3b91c1a509
commit
a55591d208
9 files changed
+543
-15
No files matched your search
+11
-1
@@ -30,6 +30,7 @@ type noteInput struct {
|
||||
Author string `json:"author"`
|
||||
Message string `json:"message"`
|
||||
BaseHash string `json:"base_hash"` // hash em que a edição se baseia (opcional; evita sobrescrever mudanças remotas)
|
||||
Session string `json:"session"` // id da sessão do cliente (presença/eventos; opcional)
|
||||
}
|
||||
|
||||
func (in noteInput) fields() (string, string, []string, string, string, string, string) {
|
||||
@@ -77,6 +78,7 @@ func (s *Server) handleCreateNote(w http.ResponseWriter, r *http.Request) {
|
||||
mapStoreError(w, err)
|
||||
return
|
||||
}
|
||||
s.publishSaved(n, author, in.Session, false)
|
||||
writeJSON(w, http.StatusCreated, noteView{n, content})
|
||||
}
|
||||
|
||||
@@ -110,14 +112,22 @@ func (s *Server) handleUpdateNote(w http.ResponseWriter, r *http.Request) {
|
||||
mapStoreError(w, err)
|
||||
return
|
||||
}
|
||||
s.publishSaved(n, author, in.Session, false)
|
||||
writeJSON(w, http.StatusOK, noteView{n, content})
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteNote(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.st.Delete(r.PathValue("id")); err != nil {
|
||||
id := r.PathValue("id")
|
||||
n, err := s.st.Get(id)
|
||||
if err != nil {
|
||||
mapStoreError(w, err)
|
||||
return
|
||||
}
|
||||
if err := s.st.Delete(id); err != nil {
|
||||
mapStoreError(w, err)
|
||||
return
|
||||
}
|
||||
s.publishSaved(n, "", "", true)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user