appview: create search endpoint (wip) #10

open
opened by boltless.me targeting master from feat/search
Changed files
+99 -9
appview
db
indexer
issues
issues
+78 -7
appview/db/issues.go
···
import (
"database/sql"
+
"fmt"
+
"strconv"
+
"strings"
"time"
"github.com/bluesky-social/indigo/atproto/syntax"
···
return ownerDid, err
}
-
func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) {
+
func GetIssues(e Execer, repoAt syntax.ATURI, page pagination.Page) ([]Issue, error) {
var issues []Issue
-
openValue := 0
-
if isOpen {
-
openValue = 1
-
}
rows, err := e.Query(
`
···
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 = ?
+
i.repo_at = ?
group by
i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open
)
···
numbered_issue
where
row_num between ? and ?`,
-
repoAt, openValue, page.Offset+1, page.Offset+page.Limit)
+
repoAt, page.Offset+1, page.Offset+page.Limit)
if err != nil {
return nil, err
}
···
return issues, nil
}
+
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
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
var issue Issue
+
var createdAt string
+
var metadata IssueMetadata
+
err := rows.Scan(
+
&issue.ID,
+
&issue.OwnerDid,
+
&issue.IssueId,
+
&createdAt,
+
&issue.Title,
+
&issue.Body,
+
&issue.Open,
+
&metadata.CommentCount,
+
)
+
if err != nil {
+
return nil, err
+
}
+
+
createdTime, err := time.Parse(time.RFC3339, createdAt)
+
if err != nil {
+
return nil, err
+
}
+
issue.Created = createdTime
+
issue.Metadata = &metadata
+
+
issues = append(issues, issue)
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
return issues, nil
+
}
+
// timeframe here is directly passed into the sql query filter, and any
// timeframe in the past should be negative; e.g.: "-3 months"
func GetIssuesByOwnerDid(e Execer, ownerDid string, timeframe string) ([]Issue, error) {
+1 -1
appview/indexer/issues/indexer.go
···
}
for _, repo := range repos {
page := pagination.FirstPage()
-
issues, err := db.GetIssues(e, repo.RepoAt(), true, page)
+
issues, err := db.GetIssues(e, repo.RepoAt(), page)
if err != nil {
return err
}
+20 -1
appview/issues/issues.go
···
isOpen = true
}
+
keyword := params.Get("q")
+
+
res, err := rp.indexer.Search(r.Context(), issues_indexer.SearchOptions{
+
Keyword: keyword,
+
IsOpen: &isOpen,
+
Page: pagination.FirstPage(),
+
})
+
if err != nil {
+
log.Println("failed to search for issues", err)
+
return
+
}
+
log.Println("searched issues:", res.Hits)
+
page, ok := r.Context().Value("page").(pagination.Page)
if !ok {
log.Println("failed to get page")
···
return
}
-
issues, err := db.GetIssues(rp.db, f.RepoAt, isOpen, page)
+
// issues, err := db.GetIssues(rp.db, f.RepoAt, isOpen, page)
+
// if err != nil {
+
// log.Println("failed to get issues", err)
+
// rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
+
// return
+
// }
+
issues, err := db.GetIssuesByIDs(rp.db, res.Hits)
if err != nil {
log.Println("failed to get issues", err)
rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.")