From 54342b94f535f9e3201d4d4003d38a45070b7927 Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Sat, 12 Jul 2025 20:28:10 +0900 Subject: [PATCH] appview: add issue search endpoint Change-Id: sqlmmyoulmsmmyvqqwlrtsktryoutywx Signed-off-by: Seongmin Lee --- appview/db/issues.go | 124 ++++++++++++------ appview/issues/issues.go | 29 +++- .../pages/templates/repo/issues/issues.html | 11 +- 3 files changed, 124 insertions(+), 40 deletions(-) diff --git a/appview/db/issues.go b/appview/db/issues.go index e797b12..ab1bbcc 100644 --- a/appview/db/issues.go +++ b/appview/db/issues.go @@ -2,9 +2,13 @@ package db import ( "database/sql" + "fmt" + "strconv" + "strings" "time" "github.com/bluesky-social/indigo/atproto/syntax" + "tangled.sh/tangled.sh/core/appview/models" "tangled.sh/tangled.sh/core/appview/pagination" ) @@ -160,49 +164,95 @@ func GetAllIssues(e Execer, page pagination.Page) ([]Issue, error) { return issues, nil } -func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) { - var issues []Issue +// GetIssueIDs gets list of all existing issue's IDs +func GetIssueIDs(e Execer, opts models.IssueSearchOptions) ([]int64, error) { + var ids []int64 + + var filters []filter openValue := 0 - if isOpen { + if opts.IsOpen { openValue = 1 } + filters = append(filters, FilterEq("open", openValue)) + if opts.RepoAt != "" { + filters = append(filters, FilterEq("repo_at", opts.RepoAt)) + } - rows, err := e.Query( + var conditions []string + var args []any + + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.Arg()...) + } + + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") + } + query := fmt.Sprintf( ` - with numbered_issue as ( - select - i.id, - i.owner_did, - i.issue_id, - i.created, - i.title, - i.body, - i.open, - count(c.id) as comment_count, - row_number() over (order by i.created desc) as row_num - from - issues i - left join - comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id - where - i.repo_at = ? and i.open = ? - group by - i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open - ) select - id, - owner_did, - issue_id, - created, - title, - body, - open, - comment_count - from - numbered_issue - where - row_num between ? and ?`, - repoAt, openValue, page.Offset+1, page.Offset+page.Limit) + id + from + issues + %s + limit ? offset ?`, + whereClause, + ) + args = append(args, opts.Page.Limit, opts.Page.Offset) + rows, err := e.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var id int64 + err := rows.Scan(&id) + if err != nil { + return nil, err + } + + ids = append(ids, id) + } + + return ids, nil +} + +// GetIssuesByIDs gets list of issues from given IDs +func GetIssuesByIDs(e Execer, ids []int64) ([]Issue, error) { + var issues []Issue + + // HACK: would be better to create "?,?,?,..." or use something like sqlx + idStrings := make([]string, len(ids)) + for i, id := range ids { + idStrings[i] = strconv.FormatInt(id, 10) + } + idList := strings.Join(idStrings, ",") + query := fmt.Sprintf( + ` + select + i.id, + i.owner_did, + i.issue_id, + i.created, + i.title, + i.body, + i.open, + count(c.id) as comment_count + from + issues i + left join + comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id + where + i.id in (%s) + group by + i.id + order by i.created desc`, + idList, + ) + rows, err := e.Query(query) if err != nil { return nil, err } diff --git a/appview/issues/issues.go b/appview/issues/issues.go index 159479b..f0cd605 100644 --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -19,6 +19,7 @@ import ( "tangled.sh/tangled.sh/core/appview/config" "tangled.sh/tangled.sh/core/appview/db" issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues" + "tangled.sh/tangled.sh/core/appview/models" "tangled.sh/tangled.sh/core/appview/notify" "tangled.sh/tangled.sh/core/appview/oauth" "tangled.sh/tangled.sh/core/appview/pages" @@ -607,7 +608,32 @@ func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) { return } - issues, err := db.GetIssues(rp.db, f.RepoAt, isOpen, page) + keyword := params.Get("q") + + var ids []int64 + searchOpts := models.IssueSearchOptions{ + Keyword: keyword, + RepoAt: f.RepoAt.String(), + IsOpen: isOpen, + Page: page, + } + if keyword != "" { + res, err := rp.indexer.Search(r.Context(), searchOpts) + if err != nil { + log.Println("failed to search for issues", err) + return + } + log.Println("searched issues:", res.Hits) + ids = res.Hits + } else { + ids, err = db.GetIssueIDs(rp.db, searchOpts) + if err != nil { + log.Println("failed to search for issues", err) + return + } + } + + issues, err := db.GetIssuesByIDs(rp.db, ids) if err != nil { log.Println("failed to get issues", err) rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.") @@ -619,6 +645,7 @@ func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) { RepoInfo: f.RepoInfo(user), Issues: issues, FilteringByOpen: isOpen, + FilterQuery: keyword, Page: page, }) } diff --git a/appview/pages/templates/repo/issues/issues.html b/appview/pages/templates/repo/issues/issues.html index 7f01a1e..9991ca2 100644 --- a/appview/pages/templates/repo/issues/issues.html +++ b/appview/pages/templates/repo/issues/issues.html @@ -24,6 +24,13 @@ {{ i "ban" "w-4 h-4" }} {{ .RepoInfo.Stats.IssueCount.Closed }} closed +
+ + + +
{{ i "chevron-left" "w-4 h-4" }} previous @@ -114,7 +121,7 @@ next {{ i "chevron-right" "w-4 h-4" }} -- 2.43.0