diff --git a/internal/db/migrate.go b/internal/db/migrate.go index 1314f0f..f1fe5a0 100644 --- a/internal/db/migrate.go +++ b/internal/db/migrate.go @@ -21,6 +21,7 @@ func Migrate(db *sql.DB) error { createInvites, createBuyerGroups, createFolderAccess, + createFileComments, } for i, m := range migrations { @@ -219,6 +220,16 @@ CREATE TABLE IF NOT EXISTS folder_access ( PRIMARY KEY (folder_id, buyer_group) );` +const createFileComments = ` +CREATE TABLE IF NOT EXISTS file_comments ( + id TEXT PRIMARY KEY, + file_id TEXT NOT NULL, + deal_id TEXT NOT NULL, + user_id TEXT NOT NULL, + content TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +);` + // Additive migrations - each statement is run individually, errors ignored (for already-existing columns) var additiveMigrationStmts = []string{ // Section 1: org_type diff --git a/internal/handler/files.go b/internal/handler/files.go index 85c85f9..93a4434 100644 --- a/internal/handler/files.go +++ b/internal/handler/files.go @@ -158,6 +158,67 @@ func (h *Handler) logActivity(dealID, userID, orgID, actType, resourceType, reso id, orgID, dealID, userID, actType, resourceType, resourceName, resourceID) } +func (h *Handler) handleFileComments(w http.ResponseWriter, r *http.Request) { + profile := getProfile(r.Context()) + // Parse: /deals/files/comments/{fileID} + fileID := strings.TrimPrefix(r.URL.Path, "/deals/files/comments/") + + // Get deal_id for this file + var dealID string + h.db.QueryRow("SELECT deal_id FROM files WHERE id = ?", fileID).Scan(&dealID) + + if r.Method == http.MethodPost { + content := strings.TrimSpace(r.FormValue("content")) + if content != "" { + id := generateID("cmt") + h.db.Exec("INSERT INTO file_comments (id, file_id, deal_id, user_id, content) VALUES (?, ?, ?, ?, ?)", + id, fileID, dealID, profile.ID, content) + } + } + + // Get comments + rows, err := h.db.Query(` + SELECT c.id, c.content, c.created_at, COALESCE(p.full_name, 'Unknown') + FROM file_comments c LEFT JOIN profiles p ON c.user_id = p.id + WHERE c.file_id = ? ORDER BY c.created_at ASC`, fileID) + if err != nil { + http.Error(w, "Error loading comments", 500) + return + } + defer rows.Close() + + w.Header().Set("Content-Type", "text/html") + + // Build HTML response + html := `
` + hasComments := false + for rows.Next() { + hasComments = true + var id, content, userName string + var createdAt string + rows.Scan(&id, &content, &createdAt, &userName) + html += fmt.Sprintf(`
+
+ %s + %s +
+

%s

+
`, userName, createdAt, content) + } + if !hasComments { + html += `

No comments yet.

` + } + html += `
` + + // Add comment form + html += fmt.Sprintf(`
+ + +
`, fileID, fileID) + + w.Write([]byte(html)) +} + func fileExists(path string) bool { _, err := os.Stat(path) return err == nil diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 105ac8b..87f88fc 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -86,6 +86,7 @@ mux.HandleFunc("/auth/logout", h.handleLogout) mux.HandleFunc("/deals/files/upload", h.requireAuth(h.handleFileUpload)) mux.HandleFunc("/deals/files/delete", h.requireAuth(h.handleFileDelete)) mux.HandleFunc("/deals/files/download/", h.requireAuth(h.handleFileDownload)) + mux.HandleFunc("/deals/files/comments/", h.requireAuth(h.handleFileComments)) // HTMX partials mux.HandleFunc("/htmx/request-comment", h.requireAuth(h.handleUpdateComment)) diff --git a/internal/model/models.go b/internal/model/models.go index 37f690a..179611e 100644 --- a/internal/model/models.go +++ b/internal/model/models.go @@ -144,6 +144,16 @@ type Session struct { CreatedAt time.Time } +type FileComment struct { + ID string + FileID string + DealID string + UserID string + Content string + CreatedAt time.Time + UserName string // computed +} + type Invite struct { Token string OrgID string diff --git a/templates/dealroom.templ b/templates/dealroom.templ index a36c317..c20f8a4 100644 --- a/templates/dealroom.templ +++ b/templates/dealroom.templ @@ -152,6 +152,9 @@ templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.
+ @@ -167,6 +170,13 @@ templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.
+ + +
+

Click the comment icon to load comments.

+
+ + } }