package v1 // NoteHandler provides HTTP handlers for contact notes. // Reference: Chatwoot app/controllers/api/v1/accounts/contacts/notes_controller.rb // // Routes: // GET /api/v1/accounts/:account_id/contacts/:contact_id/notes -> List // GET /api/v1/accounts/:account_id/contacts/:contact_id/notes/:id -> Show // POST /api/v1/accounts/:account_id/contacts/:contact_id/notes -> Create // PUT /api/v1/accounts/:account_id/contacts/:contact_id/notes/:id -> Update // DELETE /api/v1/accounts/:account_id/contacts/:contact_id/notes/:id -> Destroy import ( "strconv" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/pkg/response" ) // NoteHandler handles HTTP requests for contact notes. type NoteHandler struct { noteService *service.NoteService } // NewNoteHandler creates a new NoteHandler. func NewNoteHandler(noteService *service.NoteService) *NoteHandler { return &NoteHandler{noteService: noteService} } // noteRequest represents the JSON request body for creating/updating a note. // Reference: Chatwoot `params.require(:note).permit(:content)` type noteRequest struct { Content string `json:"content" binding:"required"` } // List returns all notes for a contact. // Reference: Chatwoot `def index` func (h *NoteHandler) List(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("account_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid account ID") return } contactID, err := strconv.ParseUint(c.Param("contact_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid contact ID") return } notes, err := h.noteService.List(uint(accountID), uint(contactID)) if err != nil { response.AbortWithStatusError(c, 500, response.ErrInternal, "Failed to list notes") return } response.OK(c, notes) } // Show returns a single note by ID. // Reference: Chatwoot `def show; end` func (h *NoteHandler) Show(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("account_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid account ID") return } contactID, err := strconv.ParseUint(c.Param("contact_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid contact ID") return } noteID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid note ID") return } note, err := h.noteService.Get(uint(accountID), uint(contactID), uint(noteID)) if err != nil { response.AbortWithStatusError(c, 404, response.ErrNotFound, "Note not found") return } response.OK(c, note) } // Create creates a new note for a contact. // Reference: Chatwoot `def create` // @note = @contact.notes.create!(note_params) // note_params merges {contact_id: @contact.id, user_id: Current.user.id} func (h *NoteHandler) Create(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("account_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid account ID") return } contactID, err := strconv.ParseUint(c.Param("contact_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid contact ID") return } // Chatwoot: params.require(:note) → {"note": {"content": "..."}} var wrapper struct { Note noteRequest `json:"note"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Content is required") return } req := wrapper.Note userID, exists := c.Get("user_id") if !exists { response.AbortWithStatusError(c, 401, response.ErrUnauthorized, "User not authenticated") return } note, err := h.noteService.Create(uint(accountID), uint(contactID), userID.(uint), req.Content) if err != nil { if err == service.ErrNoteContentEmpty { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Content is required") return } response.AbortWithStatusError(c, 500, response.ErrInternal, "Failed to create note") return } response.Created(c, note) } // Update updates an existing note's content. // Reference: Chatwoot `def update; @note.update(note_params); end` func (h *NoteHandler) Update(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("account_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid account ID") return } contactID, err := strconv.ParseUint(c.Param("contact_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid contact ID") return } noteID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid note ID") return } // Chatwoot: params.require(:note) → {"note": {"content": "..."}} var wrapper struct { Note noteRequest `json:"note"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Content is required") return } req := wrapper.Note note, err := h.noteService.Update(uint(accountID), uint(contactID), uint(noteID), req.Content) if err != nil { if err == service.ErrNoteNotFound { response.AbortWithStatusError(c, 404, response.ErrNotFound, "Note not found") return } response.AbortWithStatusError(c, 500, response.ErrInternal, "Failed to update note") return } response.OK(c, note) } // Destroy deletes a note. // Reference: Chatwoot `def destroy; @note.destroy!; head :ok; end` // Chatwoot returns 200 OK on destroy, NOT 204 NoContent. func (h *NoteHandler) Destroy(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("account_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid account ID") return } contactID, err := strconv.ParseUint(c.Param("contact_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid contact ID") return } noteID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { response.AbortWithStatusError(c, 400, response.ErrBadRequest, "Invalid note ID") return } err = h.noteService.Delete(uint(accountID), uint(contactID), uint(noteID)) if err != nil { response.AbortWithStatusError(c, 404, response.ErrNotFound, "Note not found") return } // Reference: Chatwoot returns `head :ok` → 200 OK with empty body response.OK(c, gin.H{}) }