Merge pull request #285 from multica-ai/feature/attachment-improvements

feat: improve attachment support across issue and comment APIs
This commit is contained in:
LinYushen 2026-04-01 18:11:57 +08:00 committed by GitHub
commit 20a2332c94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 37 additions and 2 deletions

View file

@ -123,6 +123,7 @@ func init() {
issueCreateCmd.Flags().String("parent", "", "Parent issue ID") issueCreateCmd.Flags().String("parent", "", "Parent issue ID")
issueCreateCmd.Flags().String("due-date", "", "Due date (RFC3339 format)") issueCreateCmd.Flags().String("due-date", "", "Due date (RFC3339 format)")
issueCreateCmd.Flags().String("output", "json", "Output format: table or json") issueCreateCmd.Flags().String("output", "json", "Output format: table or json")
issueCreateCmd.Flags().StringSlice("attachment", nil, "File path(s) to attach (can be specified multiple times)")
// issue update // issue update
issueUpdateCmd.Flags().String("title", "", "New title") issueUpdateCmd.Flags().String("title", "", "New title")
@ -276,7 +277,13 @@ func runIssueCreate(cmd *cobra.Command, _ []string) error {
return err return err
} }
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) // Use a longer timeout when attachments are present (file uploads can be slow).
timeout := 15 * time.Second
attachments, _ := cmd.Flags().GetStringSlice("attachment")
if len(attachments) > 0 {
timeout = 60 * time.Second
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
body := map[string]any{"title": title} body := map[string]any{"title": title}
@ -309,6 +316,19 @@ func runIssueCreate(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("create issue: %w", err) return fmt.Errorf("create issue: %w", err)
} }
// Upload attachments and link them to the newly created issue.
issueID := strVal(result, "id")
for _, filePath := range attachments {
data, readErr := os.ReadFile(filePath)
if readErr != nil {
return fmt.Errorf("read attachment %s: %w", filePath, readErr)
}
if _, uploadErr := client.UploadFile(ctx, data, filePath, issueID); uploadErr != nil {
return fmt.Errorf("upload attachment %s: %w", filePath, uploadErr)
}
fmt.Fprintf(os.Stderr, "Uploaded %s\n", filePath)
}
output, _ := cmd.Flags().GetString("output") output, _ := cmd.Flags().GetString("output")
if output == "table" { if output == "table" {
headers := []string{"ID", "TITLE", "STATUS", "PRIORITY"} headers := []string{"ID", "TITLE", "STATUS", "PRIORITY"}

View file

@ -148,7 +148,9 @@ func (h *Handler) CreateComment(w http.ResponseWriter, r *http.Request) {
h.linkAttachmentsByIDs(r.Context(), comment.ID, issue.ID, req.AttachmentIDs) h.linkAttachmentsByIDs(r.Context(), comment.ID, issue.ID, req.AttachmentIDs)
} }
resp := commentToResponse(comment, nil, nil) // Fetch linked attachments so the response includes them.
groupedAtt := h.groupAttachments(r, []pgtype.UUID{comment.ID})
resp := commentToResponse(comment, nil, groupedAtt[uuidToString(comment.ID)])
slog.Info("comment created", append(logger.RequestAttrs(r), "comment_id", uuidToString(comment.ID), "issue_id", issueID)...) slog.Info("comment created", append(logger.RequestAttrs(r), "comment_id", uuidToString(comment.ID), "issue_id", issueID)...)
h.publish(protocol.EventCommentCreated, uuidToString(issue.WorkspaceID), authorType, authorID, map[string]any{ h.publish(protocol.EventCommentCreated, uuidToString(issue.WorkspaceID), authorType, authorID, map[string]any{
"comment": resp, "comment": resp,

View file

@ -36,6 +36,7 @@ type IssueResponse struct {
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
Reactions []IssueReactionResponse `json:"reactions,omitempty"` Reactions []IssueReactionResponse `json:"reactions,omitempty"`
Attachments []AttachmentResponse `json:"attachments,omitempty"`
} }
type agentTriggerSnapshot struct { type agentTriggerSnapshot struct {
@ -142,6 +143,18 @@ func (h *Handler) GetIssue(w http.ResponseWriter, r *http.Request) {
} }
} }
// Fetch issue-level attachments.
attachments, err := h.Queries.ListAttachmentsByIssue(r.Context(), db.ListAttachmentsByIssueParams{
IssueID: issue.ID,
WorkspaceID: issue.WorkspaceID,
})
if err == nil && len(attachments) > 0 {
resp.Attachments = make([]AttachmentResponse, len(attachments))
for i, a := range attachments {
resp.Attachments[i] = h.attachmentToResponse(a)
}
}
writeJSON(w, http.StatusOK, resp) writeJSON(w, http.StatusOK, resp)
} }