appview/pulls: add search endpoint #709

merged
opened by boltless.me targeting master from boltless.me/core: feat/search
Changed files
+105 -2
appview
db
pages
templates
repo
pulls
pulls
state
+61
appview/db/pulls.go
··· 281 281 return GetPullsWithLimit(e, 0, filters...) 282 282 } 283 283 284 + func GetPullIDs(e Execer, opts models.PullSearchOptions) ([]int64, error) { 285 + var ids []int64 286 + 287 + var filters []filter 288 + filters = append(filters, FilterEq("state", opts.State)) 289 + if opts.RepoAt != "" { 290 + filters = append(filters, FilterEq("repo_at", opts.RepoAt)) 291 + } 292 + 293 + var conditions []string 294 + var args []any 295 + 296 + for _, filter := range filters { 297 + conditions = append(conditions, filter.Condition()) 298 + args = append(args, filter.Arg()...) 299 + } 300 + 301 + whereClause := "" 302 + if conditions != nil { 303 + whereClause = " where " + strings.Join(conditions, " and ") 304 + } 305 + pageClause := "" 306 + if opts.Page.Limit != 0 { 307 + pageClause = fmt.Sprintf( 308 + " limit %d offset %d ", 309 + opts.Page.Limit, 310 + opts.Page.Offset, 311 + ) 312 + } 313 + 314 + query := fmt.Sprintf( 315 + ` 316 + select 317 + id 318 + from 319 + pulls 320 + %s 321 + %s`, 322 + whereClause, 323 + pageClause, 324 + ) 325 + args = append(args, opts.Page.Limit, opts.Page.Offset) 326 + rows, err := e.Query(query, args...) 327 + if err != nil { 328 + return nil, err 329 + } 330 + defer rows.Close() 331 + 332 + for rows.Next() { 333 + var id int64 334 + err := rows.Scan(&id) 335 + if err != nil { 336 + return nil, err 337 + } 338 + 339 + ids = append(ids, id) 340 + } 341 + 342 + return ids, nil 343 + } 344 + 284 345 func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { 285 346 pulls, err := GetPullsWithLimit(e, 1, FilterEq("repo_at", repoAt), FilterEq("pull_id", pullId)) 286 347 if err != nil {
+1
appview/pages/pages.go
··· 1101 1101 Pulls []*models.Pull 1102 1102 Active string 1103 1103 FilteringBy models.PullState 1104 + FilterQuery string 1104 1105 Stacks map[string]models.Stack 1105 1106 Pipelines map[string]models.Pipeline 1106 1107 LabelDefs map[string]*models.LabelDefinition
+7
appview/pages/templates/repo/pulls/pulls.html
··· 31 31 {{ i "ban" "w-4 h-4" }} 32 32 <span>{{ .RepoInfo.Stats.PullCount.Closed }} closed</span> 33 33 </a> 34 + <form class="flex gap-4" method="GET"> 35 + <input type="hidden" name="state" value="{{ .FilteringBy.String }}"> 36 + <input class="" type="text" name="q" value="{{ .FilterQuery }}"> 37 + <button class="btn" type="submit"> 38 + search 39 + </button> 40 + </form> 34 41 </div> 35 42 <a 36 43 href="/{{ .RepoInfo.FullName }}/pulls/new"
+35 -2
appview/pulls/pulls.go
··· 17 17 "tangled.org/core/api/tangled" 18 18 "tangled.org/core/appview/config" 19 19 "tangled.org/core/appview/db" 20 + pulls_indexer "tangled.org/core/appview/indexer/pulls" 20 21 "tangled.org/core/appview/models" 21 22 "tangled.org/core/appview/notify" 22 23 "tangled.org/core/appview/oauth" ··· 49 50 enforcer *rbac.Enforcer 50 51 logger *slog.Logger 51 52 validator *validator.Validator 53 + indexer *pulls_indexer.Indexer 52 54 } 53 55 54 56 func New( ··· 61 63 notifier notify.Notifier, 62 64 enforcer *rbac.Enforcer, 63 65 validator *validator.Validator, 66 + indexer *pulls_indexer.Indexer, 64 67 logger *slog.Logger, 65 68 ) *Pulls { 66 69 return &Pulls{ ··· 74 77 enforcer: enforcer, 75 78 logger: logger, 76 79 validator: validator, 80 + indexer: indexer, 77 81 } 78 82 } 79 83 ··· 544 548 } 545 549 546 550 func (s *Pulls) RepoPulls(w http.ResponseWriter, r *http.Request) { 551 + l := s.logger.With("handler", "RepoPulls") 552 + 547 553 user := s.oauth.GetUser(r) 548 554 params := r.URL.Query() 549 555 ··· 561 567 return 562 568 } 563 569 570 + keyword := params.Get("q") 571 + 572 + var ids []int64 573 + searchOpts := models.PullSearchOptions{ 574 + Keyword: keyword, 575 + RepoAt: f.RepoAt().String(), 576 + State: state, 577 + // Page: page, 578 + } 579 + l.Debug("searching with", "searchOpts", searchOpts) 580 + if keyword != "" { 581 + res, err := s.indexer.Search(r.Context(), searchOpts) 582 + if err != nil { 583 + l.Error("failed to search for pulls", "err", err) 584 + return 585 + } 586 + l.Debug("searched pulls with indexer", "res.Hits", res.Hits) 587 + ids = res.Hits 588 + } else { 589 + ids, err = db.GetPullIDs(s.db, searchOpts) 590 + if err != nil { 591 + l.Error("failed to get all pull ids", "err", err) 592 + return 593 + } 594 + l.Debug("indexed all pulls from the db", "ids", ids) 595 + } 596 + 564 597 pulls, err := db.GetPulls( 565 598 s.db, 566 - db.FilterEq("repo_at", f.RepoAt()), 567 - db.FilterEq("state", state), 599 + db.FilterIn("id", ids), 568 600 ) 569 601 if err != nil { 570 602 log.Println("failed to get pulls", err) ··· 651 683 Pulls: pulls, 652 684 LabelDefs: defs, 653 685 FilteringBy: state, 686 + FilterQuery: keyword, 654 687 Stacks: stacks, 655 688 Pipelines: m, 656 689 })
+1
appview/state/router.go
··· 279 279 s.notifier, 280 280 s.enforcer, 281 281 s.validator, 282 + s.indexer.Pulls, 282 283 log.SubLogger(s.logger, "pulls"), 283 284 ) 284 285 return pulls.Router(mw)