mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-29 02:19:19 +02:00
Compare commits
7 Commits
v0.1.16
...
agent/lamb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a515fc0be | ||
|
|
451715f5a1 | ||
|
|
fdf594155c | ||
|
|
c39470a53f | ||
|
|
e5dfb34a2a | ||
|
|
58549975e0 | ||
|
|
0bbc6bc1c5 |
@@ -1437,7 +1437,7 @@ function AgentDetail({
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4 text-muted-foreground" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuContent align="end" className="w-auto">
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={() => setConfirmArchive(true)}
|
||||
|
||||
@@ -167,7 +167,7 @@ vi.mock("@/features/issues/config", () => ({
|
||||
todo: { label: "Todo", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent" },
|
||||
in_progress: { label: "In Progress", iconColor: "text-warning", hoverBg: "hover:bg-warning/10" },
|
||||
in_review: { label: "In Review", iconColor: "text-success", hoverBg: "hover:bg-success/10" },
|
||||
done: { label: "Done", iconColor: "text-info", hoverBg: "hover:bg-info/10" },
|
||||
done: { label: "Done", iconColor: "text-done", hoverBg: "hover:bg-done/10" },
|
||||
blocked: { label: "Blocked", iconColor: "text-destructive", hoverBg: "hover:bg-destructive/10" },
|
||||
cancelled: { label: "Cancelled", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent" },
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
--color-success: var(--success);
|
||||
--color-warning: var(--warning);
|
||||
--color-info: var(--info);
|
||||
--color-done: var(--done);
|
||||
--color-brand: var(--brand);
|
||||
--color-brand-foreground: var(--brand-foreground);
|
||||
--color-priority: var(--priority);
|
||||
@@ -95,6 +96,7 @@
|
||||
--success: oklch(0.55 0.16 145);
|
||||
--warning: oklch(0.75 0.16 85);
|
||||
--info: oklch(0.55 0.18 250);
|
||||
--done: oklch(0.55 0.18 300);
|
||||
--priority: oklch(0.65 0.18 50);
|
||||
--scrollbar-thumb: oklch(0 0 0 / 10%);
|
||||
--scrollbar-thumb-hover: oklch(0 0 0 / 18%);
|
||||
@@ -139,6 +141,7 @@
|
||||
--success: oklch(0.65 0.15 145);
|
||||
--warning: oklch(0.70 0.16 85);
|
||||
--info: oklch(0.65 0.18 250);
|
||||
--done: oklch(0.65 0.18 300);
|
||||
--priority: oklch(0.70 0.18 50);
|
||||
--scrollbar-thumb: oklch(1 0 0 / 8%);
|
||||
--scrollbar-thumb-hover: oklch(1 0 0 / 18%);
|
||||
|
||||
@@ -90,9 +90,17 @@ export const useInboxStore = create<InboxState>((set, get) => ({
|
||||
items: s.items.map((i) => (i.id === id ? { ...i, read: true } : i)),
|
||||
})),
|
||||
archive: (id) =>
|
||||
set((s) => ({
|
||||
items: s.items.map((i) => (i.id === id ? { ...i, archived: true } : i)),
|
||||
})),
|
||||
set((s) => {
|
||||
const target = s.items.find((i) => i.id === id);
|
||||
const issueId = target?.issue_id;
|
||||
return {
|
||||
items: s.items.map((i) =>
|
||||
i.id === id || (issueId && i.issue_id === issueId)
|
||||
? { ...i, archived: true }
|
||||
: i,
|
||||
),
|
||||
};
|
||||
}),
|
||||
markAllRead: () =>
|
||||
set((s) => ({
|
||||
items: s.items.map((i) => (!i.archived ? { ...i, read: true } : i)),
|
||||
|
||||
@@ -46,7 +46,7 @@ export const STATUS_CONFIG: Record<
|
||||
todo: { label: "Todo", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent", dividerColor: "bg-muted-foreground/40", badgeBg: "bg-muted", badgeText: "text-muted-foreground", columnBg: "bg-muted/40" },
|
||||
in_progress: { label: "In Progress", iconColor: "text-warning", hoverBg: "hover:bg-warning/10", dividerColor: "bg-warning", badgeBg: "bg-warning", badgeText: "text-white", columnBg: "bg-warning/5" },
|
||||
in_review: { label: "In Review", iconColor: "text-success", hoverBg: "hover:bg-success/10", dividerColor: "bg-success", badgeBg: "bg-success", badgeText: "text-white", columnBg: "bg-success/5" },
|
||||
done: { label: "Done", iconColor: "text-info", hoverBg: "hover:bg-info/10", dividerColor: "bg-info", badgeBg: "bg-info", badgeText: "text-white", columnBg: "bg-info/5" },
|
||||
done: { label: "Done", iconColor: "text-done", hoverBg: "hover:bg-done/10", dividerColor: "bg-done", badgeBg: "bg-done", badgeText: "text-white", columnBg: "bg-done/5" },
|
||||
blocked: { label: "Blocked", iconColor: "text-destructive", hoverBg: "hover:bg-destructive/10", dividerColor: "bg-destructive", badgeBg: "bg-destructive", badgeText: "text-white", columnBg: "bg-destructive/5" },
|
||||
cancelled: { label: "Cancelled", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent", dividerColor: "bg-muted-foreground/40", badgeBg: "bg-muted", badgeText: "text-muted-foreground", columnBg: "bg-muted/40" },
|
||||
};
|
||||
|
||||
@@ -162,6 +162,9 @@ func init() {
|
||||
|
||||
// issue comment list
|
||||
issueCommentListCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
issueCommentListCmd.Flags().Int("limit", 0, "Maximum number of comments to return (0 = all)")
|
||||
issueCommentListCmd.Flags().Int("offset", 0, "Number of comments to skip")
|
||||
issueCommentListCmd.Flags().String("since", "", "Only return comments created after this timestamp (RFC3339)")
|
||||
|
||||
// issue runs
|
||||
issueRunsCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
@@ -536,9 +539,36 @@ func runIssueCommentList(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
params := url.Values{}
|
||||
if v, _ := cmd.Flags().GetInt("limit"); v > 0 {
|
||||
params.Set("limit", fmt.Sprintf("%d", v))
|
||||
}
|
||||
if v, _ := cmd.Flags().GetInt("offset"); v > 0 {
|
||||
params.Set("offset", fmt.Sprintf("%d", v))
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("since"); v != "" {
|
||||
params.Set("since", v)
|
||||
}
|
||||
|
||||
path := "/api/issues/" + args[0] + "/comments"
|
||||
if len(params) > 0 {
|
||||
path += "?" + params.Encode()
|
||||
}
|
||||
|
||||
var comments []map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/issues/"+args[0]+"/comments", &comments); err != nil {
|
||||
return fmt.Errorf("list comments: %w", err)
|
||||
isPaginated := len(params) > 0
|
||||
if isPaginated {
|
||||
headers, getErr := client.GetJSONWithHeaders(ctx, path, &comments)
|
||||
if getErr != nil {
|
||||
return fmt.Errorf("list comments: %w", getErr)
|
||||
}
|
||||
if total := headers.Get("X-Total-Count"); total != "" {
|
||||
fmt.Fprintf(os.Stderr, "Showing %d of %s comments.\n", len(comments), total)
|
||||
}
|
||||
} else {
|
||||
if err := client.GetJSON(ctx, path, &comments); err != nil {
|
||||
return fmt.Errorf("list comments: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
|
||||
@@ -77,6 +77,34 @@ func (c *APIClient) GetJSON(ctx context.Context, path string, out any) error {
|
||||
return json.NewDecoder(resp.Body).Decode(out)
|
||||
}
|
||||
|
||||
// GetJSONWithHeaders performs a GET request, decodes the JSON response, and
|
||||
// returns the response headers. Useful when callers need header values like
|
||||
// X-Total-Count for pagination.
|
||||
func (c *APIClient) GetJSONWithHeaders(ctx context.Context, path string, out any) (http.Header, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL+path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.setHeaders(req)
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
data, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
return nil, fmt.Errorf("GET %s returned %d: %s", path, resp.StatusCode, strings.TrimSpace(string(data)))
|
||||
}
|
||||
if out != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return resp.Header, err
|
||||
}
|
||||
}
|
||||
return resp.Header, nil
|
||||
}
|
||||
|
||||
// DeleteJSON performs a DELETE request.
|
||||
func (c *APIClient) DeleteJSON(ctx context.Context, path string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, c.BaseURL+path, nil)
|
||||
|
||||
@@ -47,7 +47,7 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
b.WriteString("### Read\n")
|
||||
b.WriteString("- `multica issue get <id> --output json` — Get full issue details (title, description, status, priority, assignee)\n")
|
||||
b.WriteString("- `multica issue list [--status X] [--priority X] [--assignee X] --output json` — List issues in workspace\n")
|
||||
b.WriteString("- `multica issue comment list <issue-id> --output json` — List all comments on an issue (includes id, parent_id for threading)\n")
|
||||
b.WriteString("- `multica issue comment list <issue-id> [--limit N] [--offset N] [--since <RFC3339>] --output json` — List comments on an issue (supports pagination; includes id, parent_id for threading)\n")
|
||||
b.WriteString("- `multica workspace get --output json` — Get workspace details and context\n")
|
||||
b.WriteString("- `multica agent list --output json` — List agents in workspace\n")
|
||||
b.WriteString("- `multica issue runs <issue-id> --output json` — List all execution runs for an issue (status, timestamps, errors)\n")
|
||||
@@ -83,6 +83,7 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
b.WriteString("**This task was triggered by a comment.** Your primary job is to respond.\n\n")
|
||||
fmt.Fprintf(&b, "1. Run `multica issue get %s --output json` to understand the issue context\n", ctx.IssueID)
|
||||
fmt.Fprintf(&b, "2. Run `multica issue comment list %s --output json` to read the conversation\n", ctx.IssueID)
|
||||
b.WriteString(" - If the output is very large or truncated, use pagination: `--limit 30` to get the latest 30 comments, or `--since <timestamp>` to fetch only recent ones\n")
|
||||
fmt.Fprintf(&b, "3. Find the triggering comment (ID: `%s`) and understand what is being asked\n", ctx.TriggerCommentID)
|
||||
fmt.Fprintf(&b, "4. Reply: `multica issue comment add %s --parent %s --content \"...\"`\n", ctx.IssueID, ctx.TriggerCommentID)
|
||||
b.WriteString("5. If the comment requests code changes or further work, do the work first, then reply with your results\n")
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
@@ -58,10 +60,81 @@ func (h *Handler) ListComments(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
comments, err := h.Queries.ListComments(r.Context(), db.ListCommentsParams{
|
||||
IssueID: issue.ID,
|
||||
WorkspaceID: issue.WorkspaceID,
|
||||
})
|
||||
// Parse optional pagination query params.
|
||||
q := r.URL.Query()
|
||||
var limit, offset int32
|
||||
var hasPagination bool
|
||||
if v := q.Get("limit"); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 1 {
|
||||
writeError(w, http.StatusBadRequest, "invalid limit parameter")
|
||||
return
|
||||
}
|
||||
limit = int32(n)
|
||||
hasPagination = true
|
||||
}
|
||||
if v := q.Get("offset"); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 0 {
|
||||
writeError(w, http.StatusBadRequest, "invalid offset parameter")
|
||||
return
|
||||
}
|
||||
offset = int32(n)
|
||||
hasPagination = true
|
||||
}
|
||||
|
||||
var sinceTime pgtype.Timestamptz
|
||||
if v := q.Get("since"); v != "" {
|
||||
t, err := time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid since parameter; expected RFC3339 format")
|
||||
return
|
||||
}
|
||||
sinceTime = pgtype.Timestamptz{Time: t, Valid: true}
|
||||
}
|
||||
|
||||
var comments []db.Comment
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case sinceTime.Valid && hasPagination:
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
comments, err = h.Queries.ListCommentsSincePaginated(r.Context(), db.ListCommentsSincePaginatedParams{
|
||||
IssueID: issue.ID,
|
||||
WorkspaceID: issue.WorkspaceID,
|
||||
CreatedAt: sinceTime,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
case sinceTime.Valid:
|
||||
// Apply a server-side cap to prevent unbounded result sets when
|
||||
// --since is used without --limit.
|
||||
comments, err = h.Queries.ListCommentsSincePaginated(r.Context(), db.ListCommentsSincePaginatedParams{
|
||||
IssueID: issue.ID,
|
||||
WorkspaceID: issue.WorkspaceID,
|
||||
CreatedAt: sinceTime,
|
||||
Limit: 500,
|
||||
Offset: 0,
|
||||
})
|
||||
hasPagination = true
|
||||
case hasPagination:
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
comments, err = h.Queries.ListCommentsPaginated(r.Context(), db.ListCommentsPaginatedParams{
|
||||
IssueID: issue.ID,
|
||||
WorkspaceID: issue.WorkspaceID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
default:
|
||||
comments, err = h.Queries.ListComments(r.Context(), db.ListCommentsParams{
|
||||
IssueID: issue.ID,
|
||||
WorkspaceID: issue.WorkspaceID,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to list comments")
|
||||
return
|
||||
@@ -80,6 +153,17 @@ func (h *Handler) ListComments(w http.ResponseWriter, r *http.Request) {
|
||||
resp[i] = commentToResponse(c, grouped[cid], groupedAtt[cid])
|
||||
}
|
||||
|
||||
// Include total count in response header when paginating.
|
||||
if hasPagination {
|
||||
total, countErr := h.Queries.CountComments(r.Context(), db.CountCommentsParams{
|
||||
IssueID: issue.ID,
|
||||
WorkspaceID: issue.WorkspaceID,
|
||||
})
|
||||
if countErr == nil {
|
||||
w.Header().Set("X-Total-Count", strconv.FormatInt(total, 10))
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -143,10 +143,21 @@ func (h *Handler) ArchiveInboxItem(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Archive all sibling inbox items for the same issue (issue-level archive)
|
||||
if item.IssueID.Valid {
|
||||
h.Queries.ArchiveInboxByIssue(r.Context(), db.ArchiveInboxByIssueParams{
|
||||
WorkspaceID: item.WorkspaceID,
|
||||
RecipientType: item.RecipientType,
|
||||
RecipientID: item.RecipientID,
|
||||
IssueID: item.IssueID,
|
||||
})
|
||||
}
|
||||
|
||||
userID := requestUserID(r)
|
||||
workspaceID := uuidToString(item.WorkspaceID)
|
||||
h.publish(protocol.EventInboxArchived, workspaceID, "member", userID, map[string]any{
|
||||
"item_id": uuidToString(item.ID),
|
||||
"issue_id": uuidToPtr(item.IssueID),
|
||||
"recipient_id": uuidToString(item.RecipientID),
|
||||
})
|
||||
|
||||
|
||||
@@ -11,6 +11,23 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const countComments = `-- name: CountComments :one
|
||||
SELECT count(*) FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2
|
||||
`
|
||||
|
||||
type CountCommentsParams struct {
|
||||
IssueID pgtype.UUID `json:"issue_id"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CountComments(ctx context.Context, arg CountCommentsParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countComments, arg.IssueID, arg.WorkspaceID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createComment = `-- name: CreateComment :one
|
||||
INSERT INTO comment (issue_id, workspace_id, author_type, author_id, content, type, parent_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
@@ -155,6 +172,151 @@ func (q *Queries) ListComments(ctx context.Context, arg ListCommentsParams) ([]C
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listCommentsPaginated = `-- name: ListCommentsPaginated :many
|
||||
SELECT id, issue_id, author_type, author_id, content, type, created_at, updated_at, parent_id, workspace_id FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $3 OFFSET $4
|
||||
`
|
||||
|
||||
type ListCommentsPaginatedParams struct {
|
||||
IssueID pgtype.UUID `json:"issue_id"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListCommentsPaginated(ctx context.Context, arg ListCommentsPaginatedParams) ([]Comment, error) {
|
||||
rows, err := q.db.Query(ctx, listCommentsPaginated,
|
||||
arg.IssueID,
|
||||
arg.WorkspaceID,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Comment{}
|
||||
for rows.Next() {
|
||||
var i Comment
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.IssueID,
|
||||
&i.AuthorType,
|
||||
&i.AuthorID,
|
||||
&i.Content,
|
||||
&i.Type,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ParentID,
|
||||
&i.WorkspaceID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listCommentsSince = `-- name: ListCommentsSince :many
|
||||
SELECT id, issue_id, author_type, author_id, content, type, created_at, updated_at, parent_id, workspace_id FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2 AND created_at > $3
|
||||
ORDER BY created_at ASC
|
||||
`
|
||||
|
||||
type ListCommentsSinceParams struct {
|
||||
IssueID pgtype.UUID `json:"issue_id"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListCommentsSince(ctx context.Context, arg ListCommentsSinceParams) ([]Comment, error) {
|
||||
rows, err := q.db.Query(ctx, listCommentsSince, arg.IssueID, arg.WorkspaceID, arg.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Comment{}
|
||||
for rows.Next() {
|
||||
var i Comment
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.IssueID,
|
||||
&i.AuthorType,
|
||||
&i.AuthorID,
|
||||
&i.Content,
|
||||
&i.Type,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ParentID,
|
||||
&i.WorkspaceID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listCommentsSincePaginated = `-- name: ListCommentsSincePaginated :many
|
||||
SELECT id, issue_id, author_type, author_id, content, type, created_at, updated_at, parent_id, workspace_id FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2 AND created_at > $3
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $4 OFFSET $5
|
||||
`
|
||||
|
||||
type ListCommentsSincePaginatedParams struct {
|
||||
IssueID pgtype.UUID `json:"issue_id"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListCommentsSincePaginated(ctx context.Context, arg ListCommentsSincePaginatedParams) ([]Comment, error) {
|
||||
rows, err := q.db.Query(ctx, listCommentsSincePaginated,
|
||||
arg.IssueID,
|
||||
arg.WorkspaceID,
|
||||
arg.CreatedAt,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Comment{}
|
||||
for rows.Next() {
|
||||
var i Comment
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.IssueID,
|
||||
&i.AuthorType,
|
||||
&i.AuthorID,
|
||||
&i.Content,
|
||||
&i.Type,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ParentID,
|
||||
&i.WorkspaceID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateComment = `-- name: UpdateComment :one
|
||||
UPDATE comment SET
|
||||
content = $2,
|
||||
|
||||
@@ -66,6 +66,31 @@ func (q *Queries) ArchiveCompletedInbox(ctx context.Context, arg ArchiveComplete
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const archiveInboxByIssue = `-- name: ArchiveInboxByIssue :execrows
|
||||
UPDATE inbox_item SET archived = true
|
||||
WHERE workspace_id = $1 AND recipient_type = $2 AND recipient_id = $3 AND issue_id = $4 AND archived = false
|
||||
`
|
||||
|
||||
type ArchiveInboxByIssueParams struct {
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
RecipientType string `json:"recipient_type"`
|
||||
RecipientID pgtype.UUID `json:"recipient_id"`
|
||||
IssueID pgtype.UUID `json:"issue_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ArchiveInboxByIssue(ctx context.Context, arg ArchiveInboxByIssueParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, archiveInboxByIssue,
|
||||
arg.WorkspaceID,
|
||||
arg.RecipientType,
|
||||
arg.RecipientID,
|
||||
arg.IssueID,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const archiveInboxItem = `-- name: ArchiveInboxItem :one
|
||||
UPDATE inbox_item SET archived = true
|
||||
WHERE id = $1
|
||||
|
||||
@@ -3,6 +3,27 @@ SELECT * FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2
|
||||
ORDER BY created_at ASC;
|
||||
|
||||
-- name: ListCommentsPaginated :many
|
||||
SELECT * FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $3 OFFSET $4;
|
||||
|
||||
-- name: ListCommentsSince :many
|
||||
SELECT * FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2 AND created_at > $3
|
||||
ORDER BY created_at ASC;
|
||||
|
||||
-- name: ListCommentsSincePaginated :many
|
||||
SELECT * FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2 AND created_at > $3
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $4 OFFSET $5;
|
||||
|
||||
-- name: CountComments :one
|
||||
SELECT count(*) FROM comment
|
||||
WHERE issue_id = $1 AND workspace_id = $2;
|
||||
|
||||
-- name: GetComment :one
|
||||
SELECT * FROM comment
|
||||
WHERE id = $1;
|
||||
|
||||
@@ -32,6 +32,10 @@ UPDATE inbox_item SET archived = true
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: ArchiveInboxByIssue :execrows
|
||||
UPDATE inbox_item SET archived = true
|
||||
WHERE workspace_id = $1 AND recipient_type = $2 AND recipient_id = $3 AND issue_id = $4 AND archived = false;
|
||||
|
||||
-- name: CountUnreadInbox :one
|
||||
SELECT count(*) FROM inbox_item
|
||||
WHERE workspace_id = $1 AND recipient_type = $2 AND recipient_id = $3 AND read = false AND archived = false;
|
||||
|
||||
Reference in New Issue
Block a user