mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
perf(api): omit description from list issues response
Change ListIssues and ListOpenIssues SQL queries to select specific columns (excluding description, acceptance_criteria, context_refs). Reduces list API payload size, especially for issues with embedded images. Frontend handles null description gracefully — board card short-circuits, issue detail fetches full data via its own query. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,53 @@ func issueToResponse(i db.Issue, issuePrefix string) IssueResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// issueListRowToResponse converts a list-query row (no description) to an IssueResponse.
|
||||
func issueListRowToResponse(i db.ListIssuesRow, issuePrefix string) IssueResponse {
|
||||
identifier := issuePrefix + "-" + strconv.Itoa(int(i.Number))
|
||||
return IssueResponse{
|
||||
ID: uuidToString(i.ID),
|
||||
WorkspaceID: uuidToString(i.WorkspaceID),
|
||||
Number: i.Number,
|
||||
Identifier: identifier,
|
||||
Title: i.Title,
|
||||
Status: i.Status,
|
||||
Priority: i.Priority,
|
||||
AssigneeType: textToPtr(i.AssigneeType),
|
||||
AssigneeID: uuidToPtr(i.AssigneeID),
|
||||
CreatorType: i.CreatorType,
|
||||
CreatorID: uuidToString(i.CreatorID),
|
||||
ParentIssueID: uuidToPtr(i.ParentIssueID),
|
||||
ProjectID: uuidToPtr(i.ProjectID),
|
||||
Position: i.Position,
|
||||
DueDate: timestampToPtr(i.DueDate),
|
||||
CreatedAt: timestampToString(i.CreatedAt),
|
||||
UpdatedAt: timestampToString(i.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func openIssueRowToResponse(i db.ListOpenIssuesRow, issuePrefix string) IssueResponse {
|
||||
identifier := issuePrefix + "-" + strconv.Itoa(int(i.Number))
|
||||
return IssueResponse{
|
||||
ID: uuidToString(i.ID),
|
||||
WorkspaceID: uuidToString(i.WorkspaceID),
|
||||
Number: i.Number,
|
||||
Identifier: identifier,
|
||||
Title: i.Title,
|
||||
Status: i.Status,
|
||||
Priority: i.Priority,
|
||||
AssigneeType: textToPtr(i.AssigneeType),
|
||||
AssigneeID: uuidToPtr(i.AssigneeID),
|
||||
CreatorType: i.CreatorType,
|
||||
CreatorID: uuidToString(i.CreatorID),
|
||||
ParentIssueID: uuidToPtr(i.ParentIssueID),
|
||||
ProjectID: uuidToPtr(i.ProjectID),
|
||||
Position: i.Position,
|
||||
DueDate: timestampToPtr(i.DueDate),
|
||||
CreatedAt: timestampToString(i.CreatedAt),
|
||||
UpdatedAt: timestampToString(i.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
// SearchIssueResponse extends IssueResponse with search metadata.
|
||||
type SearchIssueResponse struct {
|
||||
IssueResponse
|
||||
@@ -254,7 +301,7 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) {
|
||||
prefix := h.getIssuePrefix(ctx, wsUUID)
|
||||
resp := make([]IssueResponse, len(issues))
|
||||
for i, issue := range issues {
|
||||
resp[i] = issueToResponse(issue, prefix)
|
||||
resp[i] = openIssueRowToResponse(issue, prefix)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
@@ -309,7 +356,7 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) {
|
||||
prefix := h.getIssuePrefix(ctx, wsUUID)
|
||||
resp := make([]IssueResponse, len(issues))
|
||||
for i, issue := range issues {
|
||||
resp[i] = issueToResponse(issue, prefix)
|
||||
resp[i] = issueListRowToResponse(issue, prefix)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
|
||||
@@ -269,7 +269,10 @@ func (q *Queries) ListChildIssues(ctx context.Context, parentIssueID pgtype.UUID
|
||||
}
|
||||
|
||||
const listIssues = `-- name: ListIssues :many
|
||||
SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number, project_id FROM issue
|
||||
SELECT id, workspace_id, title, status, priority,
|
||||
assignee_type, assignee_id, creator_type, creator_id,
|
||||
parent_issue_id, position, due_date, created_at, updated_at, number, project_id
|
||||
FROM issue
|
||||
WHERE workspace_id = $1
|
||||
AND ($4::text IS NULL OR status = $4)
|
||||
AND ($5::text IS NULL OR priority = $5)
|
||||
@@ -287,7 +290,26 @@ type ListIssuesParams struct {
|
||||
AssigneeID pgtype.UUID `json:"assignee_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListIssues(ctx context.Context, arg ListIssuesParams) ([]Issue, error) {
|
||||
type ListIssuesRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Priority string `json:"priority"`
|
||||
AssigneeType pgtype.Text `json:"assignee_type"`
|
||||
AssigneeID pgtype.UUID `json:"assignee_id"`
|
||||
CreatorType string `json:"creator_type"`
|
||||
CreatorID pgtype.UUID `json:"creator_id"`
|
||||
ParentIssueID pgtype.UUID `json:"parent_issue_id"`
|
||||
Position float64 `json:"position"`
|
||||
DueDate pgtype.Timestamptz `json:"due_date"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Number int32 `json:"number"`
|
||||
ProjectID pgtype.UUID `json:"project_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListIssues(ctx context.Context, arg ListIssuesParams) ([]ListIssuesRow, error) {
|
||||
rows, err := q.db.Query(ctx, listIssues,
|
||||
arg.WorkspaceID,
|
||||
arg.Limit,
|
||||
@@ -300,14 +322,13 @@ func (q *Queries) ListIssues(ctx context.Context, arg ListIssuesParams) ([]Issue
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Issue{}
|
||||
items := []ListIssuesRow{}
|
||||
for rows.Next() {
|
||||
var i Issue
|
||||
var i ListIssuesRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.WorkspaceID,
|
||||
&i.Title,
|
||||
&i.Description,
|
||||
&i.Status,
|
||||
&i.Priority,
|
||||
&i.AssigneeType,
|
||||
@@ -315,8 +336,6 @@ func (q *Queries) ListIssues(ctx context.Context, arg ListIssuesParams) ([]Issue
|
||||
&i.CreatorType,
|
||||
&i.CreatorID,
|
||||
&i.ParentIssueID,
|
||||
&i.AcceptanceCriteria,
|
||||
&i.ContextRefs,
|
||||
&i.Position,
|
||||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
@@ -335,7 +354,10 @@ func (q *Queries) ListIssues(ctx context.Context, arg ListIssuesParams) ([]Issue
|
||||
}
|
||||
|
||||
const listOpenIssues = `-- name: ListOpenIssues :many
|
||||
SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number, project_id FROM issue
|
||||
SELECT id, workspace_id, title, status, priority,
|
||||
assignee_type, assignee_id, creator_type, creator_id,
|
||||
parent_issue_id, position, due_date, created_at, updated_at, number, project_id
|
||||
FROM issue
|
||||
WHERE workspace_id = $1
|
||||
AND status NOT IN ('done', 'cancelled')
|
||||
AND ($2::text IS NULL OR priority = $2)
|
||||
@@ -349,20 +371,38 @@ type ListOpenIssuesParams struct {
|
||||
AssigneeID pgtype.UUID `json:"assignee_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListOpenIssues(ctx context.Context, arg ListOpenIssuesParams) ([]Issue, error) {
|
||||
type ListOpenIssuesRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Priority string `json:"priority"`
|
||||
AssigneeType pgtype.Text `json:"assignee_type"`
|
||||
AssigneeID pgtype.UUID `json:"assignee_id"`
|
||||
CreatorType string `json:"creator_type"`
|
||||
CreatorID pgtype.UUID `json:"creator_id"`
|
||||
ParentIssueID pgtype.UUID `json:"parent_issue_id"`
|
||||
Position float64 `json:"position"`
|
||||
DueDate pgtype.Timestamptz `json:"due_date"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Number int32 `json:"number"`
|
||||
ProjectID pgtype.UUID `json:"project_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListOpenIssues(ctx context.Context, arg ListOpenIssuesParams) ([]ListOpenIssuesRow, error) {
|
||||
rows, err := q.db.Query(ctx, listOpenIssues, arg.WorkspaceID, arg.Priority, arg.AssigneeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Issue{}
|
||||
items := []ListOpenIssuesRow{}
|
||||
for rows.Next() {
|
||||
var i Issue
|
||||
var i ListOpenIssuesRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.WorkspaceID,
|
||||
&i.Title,
|
||||
&i.Description,
|
||||
&i.Status,
|
||||
&i.Priority,
|
||||
&i.AssigneeType,
|
||||
@@ -370,8 +410,6 @@ func (q *Queries) ListOpenIssues(ctx context.Context, arg ListOpenIssuesParams)
|
||||
&i.CreatorType,
|
||||
&i.CreatorID,
|
||||
&i.ParentIssueID,
|
||||
&i.AcceptanceCriteria,
|
||||
&i.ContextRefs,
|
||||
&i.Position,
|
||||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
-- name: ListIssues :many
|
||||
SELECT * FROM issue
|
||||
SELECT id, workspace_id, title, status, priority,
|
||||
assignee_type, assignee_id, creator_type, creator_id,
|
||||
parent_issue_id, position, due_date, created_at, updated_at, number, project_id
|
||||
FROM issue
|
||||
WHERE workspace_id = $1
|
||||
AND (sqlc.narg('status')::text IS NULL OR status = sqlc.narg('status'))
|
||||
AND (sqlc.narg('priority')::text IS NULL OR priority = sqlc.narg('priority'))
|
||||
@@ -55,7 +58,10 @@ RETURNING *;
|
||||
DELETE FROM issue WHERE id = $1;
|
||||
|
||||
-- name: ListOpenIssues :many
|
||||
SELECT * FROM issue
|
||||
SELECT id, workspace_id, title, status, priority,
|
||||
assignee_type, assignee_id, creator_type, creator_id,
|
||||
parent_issue_id, position, due_date, created_at, updated_at, number, project_id
|
||||
FROM issue
|
||||
WHERE workspace_id = $1
|
||||
AND status NOT IN ('done', 'cancelled')
|
||||
AND (sqlc.narg('priority')::text IS NULL OR priority = sqlc.narg('priority'))
|
||||
|
||||
Reference in New Issue
Block a user