mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-24 11:26:34 +00:00
076fb9b2e7
* feat(comments): comment editing backend — user_id, UpdateComment, PATCH, SSE (TASK-1663)
Foundation for comment editing (PLAN-1662). No migration — comments.user_id
already exists (012_users.sql) but was never written or exposed.
- Populate user_id on create/reply: CreateComment takes an explicit userID
param (passed from currentUserID by the handlers, not via the request body
so it can't be spoofed). Expose user_id on models.Comment + all comment
SELECTs/scans. The workspace export path is left as-is — imported comments
keep NULL user_id (admin-only edit), matching the pre-identity fallback.
- Store.UpdateComment(id, body): replaces body + bumps updated_at; the
comments_fts_update trigger re-indexes.
- PATCH /workspaces/{ws}/comments/{commentID}: author-or-admin only
(canEditComment), rejects empty body. Editing is an authorship op, distinct
from delete (item editors). NULL user_id → admin-only.
- comment_updated SSE event: broadcast from the handler; added to the web
sse allowlist + ItemTimeline refresh set.
- web: api.comments.update(), Comment.user_id type.
Tests: author edits own (200), non-author non-admin (403), admin edits
anyone (200), empty body (400), NULL-user_id comment is admin-only.
Parent: PLAN-1662.
* fix(account): detach authored comments on account deletion per Codex review (round 1)
Now that TASK-1663 populates comments.user_id (FK to users.id),
DeleteAccountAtomic would fail on the FK for any user who authored a
comment. Null comments.user_id for the user before deleting the row —
comments live on in soft-deleted/other workspaces; the display-name
author is preserved and the comment just becomes admin-only to edit.
Regression test added.
53 lines
1.8 KiB
Go
53 lines
1.8 KiB
Go
package models
|
|
|
|
import "time"
|
|
|
|
// Comment represents a comment on an item.
|
|
type Comment struct {
|
|
ID string `json:"id"`
|
|
ItemID string `json:"item_id"`
|
|
WorkspaceID string `json:"workspace_id"`
|
|
Author string `json:"author"`
|
|
// UserID is the authenticated user who authored the comment. Empty for
|
|
// pre-identity comments (created before TASK-1663) and agent/system
|
|
// comments; the comment-edit permission check treats empty as
|
|
// "no provable author" → admin-only.
|
|
UserID string `json:"user_id,omitempty"`
|
|
Body string `json:"body"`
|
|
CreatedBy string `json:"created_by"`
|
|
Source string `json:"source"`
|
|
ActivityID string `json:"activity_id,omitempty"`
|
|
ParentID string `json:"parent_id,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// Populated by joins (not stored)
|
|
ItemTitle string `json:"item_title,omitempty"`
|
|
ItemSlug string `json:"item_slug,omitempty"`
|
|
|
|
// Populated by handlers for threaded views
|
|
Replies []Comment `json:"replies,omitempty"`
|
|
Reactions []Reaction `json:"reactions,omitempty"`
|
|
}
|
|
|
|
// CommentCreate is the input for creating a new comment.
|
|
type CommentCreate struct {
|
|
Author string `json:"author,omitempty"`
|
|
Body string `json:"body"`
|
|
CreatedBy string `json:"created_by,omitempty"`
|
|
Source string `json:"source,omitempty"`
|
|
ParentID string `json:"parent_id,omitempty"`
|
|
ActivityID string `json:"activity_id,omitempty"`
|
|
}
|
|
|
|
// Reaction represents an emoji reaction on a comment.
|
|
type Reaction struct {
|
|
ID string `json:"id"`
|
|
CommentID string `json:"comment_id"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
Actor string `json:"actor"`
|
|
Emoji string `json:"emoji"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ActorName string `json:"actor_name,omitempty"`
|
|
}
|