263 lines
8.8 KiB
Go
263 lines
8.8 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/tim/cairnquire/apps/server/internal/collaboration"
|
|
)
|
|
|
|
// Comments
|
|
|
|
func (s *Server) handleListComments(w http.ResponseWriter, r *http.Request) {
|
|
documentID := r.URL.Query().Get("document_id")
|
|
if documentID == "" {
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "document_id is required"})
|
|
return
|
|
}
|
|
|
|
anchorHash := r.URL.Query().Get("anchor_hash")
|
|
var comments []collaboration.Comment
|
|
var err error
|
|
if anchorHash != "" {
|
|
comments, err = s.collaboration.ListCommentsByAnchor(r.Context(), documentID, anchorHash)
|
|
} else {
|
|
comments, err = s.collaboration.ListComments(r.Context(), documentID)
|
|
}
|
|
if err != nil {
|
|
s.logger.Warn("list comments", "error", err)
|
|
writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"comments": comments})
|
|
}
|
|
|
|
func (s *Server) handleCreateComment(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
var input collaboration.CreateCommentInput
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
comment, err := s.collaboration.CreateComment(r.Context(), principal, input)
|
|
if err != nil {
|
|
s.logger.Warn("create comment", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSONWithStatus(w, http.StatusCreated, comment)
|
|
}
|
|
|
|
func (s *Server) handleResolveComment(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
commentID := chi.URLParam(r, "id")
|
|
if err := s.collaboration.ResolveComment(r.Context(), principal, commentID); err != nil {
|
|
s.logger.Warn("resolve comment", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "resolved"})
|
|
}
|
|
|
|
// Notifications
|
|
|
|
func (s *Server) handleListNotifications(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
notifications, err := s.collaboration.ListNotifications(r.Context(), principal, unreadOnly, limit)
|
|
if err != nil {
|
|
s.logger.Warn("list notifications", "error", err)
|
|
writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"notifications": notifications})
|
|
}
|
|
|
|
func (s *Server) handleMarkNotificationRead(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
notificationID := chi.URLParam(r, "id")
|
|
if err := s.collaboration.MarkNotificationRead(r.Context(), principal, notificationID); err != nil {
|
|
s.logger.Warn("mark notification read", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (s *Server) handleMarkAllNotificationsRead(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
if err := s.collaboration.MarkAllNotificationsRead(r.Context(), principal); err != nil {
|
|
s.logger.Warn("mark all notifications read", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (s *Server) handleNotificationBell(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSON(w, http.StatusOK, collaboration.NotificationBell{UnreadCount: 0, Items: []collaboration.Notification{}})
|
|
return
|
|
}
|
|
|
|
bell, err := s.collaboration.GetBellStatus(r.Context(), principal)
|
|
if err != nil {
|
|
s.logger.Warn("notification bell", "error", err)
|
|
writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, bell)
|
|
}
|
|
|
|
// Watchers
|
|
|
|
func (s *Server) handleWatchDocument(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DocumentID string `json:"documentId"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
if err := s.collaboration.WatchDocument(r.Context(), principal, req.DocumentID); err != nil {
|
|
s.logger.Warn("watch document", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "watching"})
|
|
}
|
|
|
|
func (s *Server) handleUnwatchDocument(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DocumentID string `json:"documentId"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
if err := s.collaboration.UnwatchDocument(r.Context(), principal, req.DocumentID); err != nil {
|
|
s.logger.Warn("unwatch document", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "unwatched"})
|
|
}
|
|
|
|
func (s *Server) handleWatchFolder(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
FolderPath string `json:"folderPath"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
if err := s.collaboration.WatchFolder(r.Context(), principal, req.FolderPath); err != nil {
|
|
s.logger.Warn("watch folder", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "watching"})
|
|
}
|
|
|
|
func (s *Server) handleUnwatchFolder(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSONWithStatus(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
FolderPath string `json:"folderPath"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
if err := s.collaboration.UnwatchFolder(r.Context(), principal, req.FolderPath); err != nil {
|
|
s.logger.Warn("unwatch folder", "error", err)
|
|
writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "unwatched"})
|
|
}
|
|
|
|
func (s *Server) handleIsWatching(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := requirePrincipal(r)
|
|
if !ok {
|
|
writeJSON(w, http.StatusOK, map[string]bool{"watching": false})
|
|
return
|
|
}
|
|
|
|
documentID := r.URL.Query().Get("document_id")
|
|
watching, err := s.collaboration.IsWatching(r.Context(), principal, documentID)
|
|
if err != nil {
|
|
s.logger.Warn("is watching", "error", err)
|
|
writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]bool{"watching": watching})
|
|
}
|
|
|
|
// Document page with comments
|
|
|
|
func (s *Server) renderDocumentPageWithComments(w http.ResponseWriter, r *http.Request, pagePath string) {
|
|
// This is a helper that could be used to inject comments into the template
|
|
// For now, comments are fetched client-side via JS
|
|
s.renderDocumentPage(w, r, pagePath)
|
|
}
|