tenseleyflow/shithub / 93f6663

Browse files

issues/sqlc: ListIssueEventsWithActor + CountIssueEvents

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
93f66635b9b385d58b252cb03fd4f2c0dae417e8
Parents
9293daf
Tree
dbfc557

3 changed files

StatusFile+-
M internal/issues/queries/issues.sql 17 0
M internal/issues/sqlc/issues.sql.go 72 0
M internal/issues/sqlc/querier.go 7 0
internal/issues/queries/issues.sqlmodified
@@ -206,6 +206,23 @@ SELECT * FROM issue_events
206
 WHERE issue_id = $1
206
 WHERE issue_id = $1
207
 ORDER BY created_at ASC;
207
 ORDER BY created_at ASC;
208
 
208
 
209
+-- name: ListIssueEventsWithActor :many
210
+-- Paginated timeline shape for the REST `/issues/{n}/events` endpoint:
211
+-- the same event rows ListIssueEvents returns, but LEFT-joined to users
212
+-- so the response can carry `actor_username` without a second round-trip.
213
+-- Suspended/deleted actor rows still appear (the timeline is historical
214
+-- truth), with NULL username when the user row is unrecoverable.
215
+SELECT e.id, e.issue_id, e.actor_user_id, e.kind, e.meta, e.ref_target_id,
216
+       e.created_at, u.username AS actor_username
217
+FROM issue_events e
218
+LEFT JOIN users u ON u.id = e.actor_user_id
219
+WHERE e.issue_id = $1
220
+ORDER BY e.created_at ASC, e.id ASC
221
+LIMIT $2 OFFSET $3;
222
+
223
+-- name: CountIssueEvents :one
224
+SELECT COUNT(*) FROM issue_events WHERE issue_id = $1;
225
+
209
 -- name: ListProfileAuthoredIssuesForUser :many
226
 -- name: ListProfileAuthoredIssuesForUser :many
210
 -- Cross-repository profile contribution activity. The handler performs the
227
 -- Cross-repository profile contribution activity. The handler performs the
211
 -- final repo visibility gate with policy.IsVisibleTo so private issues and
228
 -- final repo visibility gate with policy.IsVisibleTo so private issues and
internal/issues/sqlc/issues.sql.gomodified
@@ -66,6 +66,17 @@ func (q *Queries) AssignUserToIssue(ctx context.Context, db DBTX, arg AssignUser
66
 	return err
66
 	return err
67
 }
67
 }
68
 
68
 
69
+const countIssueEvents = `-- name: CountIssueEvents :one
70
+SELECT COUNT(*) FROM issue_events WHERE issue_id = $1
71
+`
72
+
73
+func (q *Queries) CountIssueEvents(ctx context.Context, db DBTX, issueID int64) (int64, error) {
74
+	row := db.QueryRow(ctx, countIssueEvents, issueID)
75
+	var count int64
76
+	err := row.Scan(&count)
77
+	return count, err
78
+}
79
+
69
 const countIssues = `-- name: CountIssues :one
80
 const countIssues = `-- name: CountIssues :one
70
 SELECT count(*)::bigint FROM issues
81
 SELECT count(*)::bigint FROM issues
71
 WHERE repo_id = $1
82
 WHERE repo_id = $1
@@ -598,6 +609,67 @@ func (q *Queries) ListIssueEvents(ctx context.Context, db DBTX, issueID int64) (
598
 	return items, nil
609
 	return items, nil
599
 }
610
 }
600
 
611
 
612
+const listIssueEventsWithActor = `-- name: ListIssueEventsWithActor :many
613
+SELECT e.id, e.issue_id, e.actor_user_id, e.kind, e.meta, e.ref_target_id,
614
+       e.created_at, u.username AS actor_username
615
+FROM issue_events e
616
+LEFT JOIN users u ON u.id = e.actor_user_id
617
+WHERE e.issue_id = $1
618
+ORDER BY e.created_at ASC, e.id ASC
619
+LIMIT $2 OFFSET $3
620
+`
621
+
622
+type ListIssueEventsWithActorParams struct {
623
+	IssueID int64
624
+	Limit   int32
625
+	Offset  int32
626
+}
627
+
628
+type ListIssueEventsWithActorRow struct {
629
+	ID            int64
630
+	IssueID       int64
631
+	ActorUserID   pgtype.Int8
632
+	Kind          string
633
+	Meta          []byte
634
+	RefTargetID   pgtype.Int8
635
+	CreatedAt     pgtype.Timestamptz
636
+	ActorUsername pgtype.Text
637
+}
638
+
639
+// Paginated timeline shape for the REST `/issues/{n}/events` endpoint:
640
+// the same event rows ListIssueEvents returns, but LEFT-joined to users
641
+// so the response can carry `actor_username` without a second round-trip.
642
+// Suspended/deleted actor rows still appear (the timeline is historical
643
+// truth), with NULL username when the user row is unrecoverable.
644
+func (q *Queries) ListIssueEventsWithActor(ctx context.Context, db DBTX, arg ListIssueEventsWithActorParams) ([]ListIssueEventsWithActorRow, error) {
645
+	rows, err := db.Query(ctx, listIssueEventsWithActor, arg.IssueID, arg.Limit, arg.Offset)
646
+	if err != nil {
647
+		return nil, err
648
+	}
649
+	defer rows.Close()
650
+	items := []ListIssueEventsWithActorRow{}
651
+	for rows.Next() {
652
+		var i ListIssueEventsWithActorRow
653
+		if err := rows.Scan(
654
+			&i.ID,
655
+			&i.IssueID,
656
+			&i.ActorUserID,
657
+			&i.Kind,
658
+			&i.Meta,
659
+			&i.RefTargetID,
660
+			&i.CreatedAt,
661
+			&i.ActorUsername,
662
+		); err != nil {
663
+			return nil, err
664
+		}
665
+		items = append(items, i)
666
+	}
667
+	if err := rows.Err(); err != nil {
668
+		return nil, err
669
+	}
670
+	return items, nil
671
+}
672
+
601
 const listIssues = `-- name: ListIssues :many
673
 const listIssues = `-- name: ListIssues :many
602
 SELECT id, repo_id, number, kind, title, body, body_html_cached, md_pipeline_version, author_user_id, state, state_reason, locked, lock_reason, milestone_id, created_at, updated_at, edited_at, closed_at, closed_by_user_id FROM issues
674
 SELECT id, repo_id, number, kind, title, body, body_html_cached, md_pipeline_version, author_user_id, state, state_reason, locked, lock_reason, milestone_id, created_at, updated_at, edited_at, closed_at, closed_by_user_id FROM issues
603
 WHERE repo_id = $1
675
 WHERE repo_id = $1
internal/issues/sqlc/querier.gomodified
@@ -19,6 +19,7 @@ type Querier interface {
19
 	AllocateIssueNumber(ctx context.Context, db DBTX, repoID int64) (int64, error)
19
 	AllocateIssueNumber(ctx context.Context, db DBTX, repoID int64) (int64, error)
20
 	// ─── assignees ───────────────────────────────────────────────────────
20
 	// ─── assignees ───────────────────────────────────────────────────────
21
 	AssignUserToIssue(ctx context.Context, db DBTX, arg AssignUserToIssueParams) error
21
 	AssignUserToIssue(ctx context.Context, db DBTX, arg AssignUserToIssueParams) error
22
+	CountIssueEvents(ctx context.Context, db DBTX, issueID int64) (int64, error)
22
 	CountIssues(ctx context.Context, db DBTX, arg CountIssuesParams) (int64, error)
23
 	CountIssues(ctx context.Context, db DBTX, arg CountIssuesParams) (int64, error)
23
 	// ─── issues ──────────────────────────────────────────────────────────
24
 	// ─── issues ──────────────────────────────────────────────────────────
24
 	CreateIssue(ctx context.Context, db DBTX, arg CreateIssueParams) (Issue, error)
25
 	CreateIssue(ctx context.Context, db DBTX, arg CreateIssueParams) (Issue, error)
@@ -48,6 +49,12 @@ type Querier interface {
48
 	ListIssueAssignees(ctx context.Context, db DBTX, issueID int64) ([]ListIssueAssigneesRow, error)
49
 	ListIssueAssignees(ctx context.Context, db DBTX, issueID int64) ([]ListIssueAssigneesRow, error)
49
 	ListIssueComments(ctx context.Context, db DBTX, issueID int64) ([]IssueComment, error)
50
 	ListIssueComments(ctx context.Context, db DBTX, issueID int64) ([]IssueComment, error)
50
 	ListIssueEvents(ctx context.Context, db DBTX, issueID int64) ([]IssueEvent, error)
51
 	ListIssueEvents(ctx context.Context, db DBTX, issueID int64) ([]IssueEvent, error)
52
+	// Paginated timeline shape for the REST `/issues/{n}/events` endpoint:
53
+	// the same event rows ListIssueEvents returns, but LEFT-joined to users
54
+	// so the response can carry `actor_username` without a second round-trip.
55
+	// Suspended/deleted actor rows still appear (the timeline is historical
56
+	// truth), with NULL username when the user row is unrecoverable.
57
+	ListIssueEventsWithActor(ctx context.Context, db DBTX, arg ListIssueEventsWithActorParams) ([]ListIssueEventsWithActorRow, error)
51
 	// Filterable list. Caller passes a state filter (open/closed/all
58
 	// Filterable list. Caller passes a state filter (open/closed/all
52
 	// where 'all' is encoded as NULL); label/assignee/author/milestone
59
 	// where 'all' is encoded as NULL); label/assignee/author/milestone
53
 	// filtering happens after this query in Go for v1 — see the
60
 	// filtering happens after this query in Go for v1 — see the