Add Cloudflare email and collaboration workflows

This commit is contained in:
2026-07-22 12:59:16 -04:00
parent 09f51d1ef4
commit 4454e918c2
35 changed files with 1596 additions and 487 deletions

View File

@@ -3,6 +3,8 @@ package httpserver
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"github.com/go-chi/chi/v5"
"github.com/tim/cairnquire/apps/server/internal/collaboration"
@@ -32,11 +34,12 @@ func (s *Server) handleListComments(w http.ResponseWriter, r *http.Request) {
}
anchorHash := r.URL.Query().Get("anchor_hash")
includeResolved := r.URL.Query().Get("include_resolved") == "true"
var comments []collaboration.Comment
if anchorHash != "" {
comments, err = s.collaboration.ListCommentsByAnchor(r.Context(), documentID, anchorHash)
comments, err = s.collaboration.ListCommentsByAnchor(r.Context(), documentID, anchorHash, includeResolved)
} else {
comments, err = s.collaboration.ListComments(r.Context(), documentID)
comments, err = s.collaboration.ListComments(r.Context(), documentID, includeResolved)
}
if err != nil {
s.logger.Warn("list comments", "error", err)
@@ -128,8 +131,13 @@ func (s *Server) handleListNotifications(w http.ResponseWriter, r *http.Request)
unreadOnly := r.URL.Query().Get("unread") == "true"
limit := 50
if r.URL.Query().Get("limit") != "" {
// Parse limit if needed, but for now just use default
if rawLimit := r.URL.Query().Get("limit"); rawLimit != "" {
parsed, err := strconv.Atoi(rawLimit)
if err != nil || parsed < 1 || parsed > 100 {
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "limit must be between 1 and 100"})
return
}
limit = parsed
}
notifications, err := s.collaboration.ListNotifications(r.Context(), principal, unreadOnly, limit)
@@ -204,6 +212,21 @@ func (s *Server) handleWatchDocument(w http.ResponseWriter, r *http.Request) {
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
return
}
req.DocumentID = strings.TrimSpace(req.DocumentID)
document, err := s.repository.GetDocumentByID(r.Context(), req.DocumentID)
if err != nil {
writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "document not found"})
return
}
canRead, err := s.canReadDocumentRecord(r, *document)
if err != nil {
writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
if !canRead {
writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "document not found"})
return
}
if err := s.collaboration.WatchDocument(r.Context(), principal, req.DocumentID); err != nil {
s.logger.Warn("watch document", "error", err)
@@ -227,6 +250,7 @@ func (s *Server) handleUnwatchDocument(w http.ResponseWriter, r *http.Request) {
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
return
}
req.DocumentID = strings.TrimSpace(req.DocumentID)
if err := s.collaboration.UnwatchDocument(r.Context(), principal, req.DocumentID); err != nil {
s.logger.Warn("unwatch document", "error", err)
@@ -250,8 +274,17 @@ func (s *Server) handleWatchFolder(w http.ResponseWriter, r *http.Request) {
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
return
}
normalized, err := collaboration.NormalizeFolderPath(req.FolderPath)
if err != nil {
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
if !s.canReadFolder(r, normalized) {
writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "folder not found"})
return
}
if err := s.collaboration.WatchFolder(r.Context(), principal, req.FolderPath); err != nil {
if err := s.collaboration.WatchFolder(r.Context(), principal, normalized); err != nil {
s.logger.Warn("watch folder", "error", err)
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
@@ -273,8 +306,13 @@ func (s *Server) handleUnwatchFolder(w http.ResponseWriter, r *http.Request) {
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
return
}
normalized, err := collaboration.NormalizeFolderPath(req.FolderPath)
if err != nil {
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
if err := s.collaboration.UnwatchFolder(r.Context(), principal, req.FolderPath); err != nil {
if err := s.collaboration.UnwatchFolder(r.Context(), principal, normalized); err != nil {
s.logger.Warn("unwatch folder", "error", err)
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
@@ -290,6 +328,20 @@ func (s *Server) handleIsWatching(w http.ResponseWriter, r *http.Request) {
}
documentID := r.URL.Query().Get("document_id")
document, err := s.repository.GetDocumentByID(r.Context(), documentID)
if err != nil {
writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "document not found"})
return
}
canRead, err := s.canReadDocumentRecord(r, *document)
if err != nil {
writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
if !canRead {
writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "document not found"})
return
}
watching, err := s.collaboration.IsWatching(r.Context(), principal, documentID)
if err != nil {
s.logger.Warn("is watching", "error", err)