this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

filter #1

open opened by chown.de targeting main from filter
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:x3ni2r3jgdqms5euhzt2qqdr/sh.tangled.repo.pull/3mkvjg7tvto22
+178 -32
Diff #0
+99 -3
store/store.go
··· 10 10 "encoding/json" 11 11 "fmt" 12 12 "io" 13 + "sort" 13 14 "time" 14 15 15 16 "entgo.io/ent/dialect" ··· 395 396 return out, rows.Err() 396 397 } 397 398 398 - // RecentRepos returns repos created since `since`, optionally filtered by 399 - // primary language. Pass limit <= 0 for no limit. Uses raw SQL for the 400 - // scalar star-count subquery and handle JOIN — both awkward in ent. 399 + // PrimaryLanguages returns the sorted set of "primary" languages observed 400 + // across all enriched repos — i.e., the highest-bytes language per repo, 401 + // deduplicated. Used to populate the language filter dropdown. 402 + func (s *Store) PrimaryLanguages(ctx context.Context) ([]string, error) { 403 + rows, err := s.db.QueryContext(ctx, `SELECT languages FROM repos WHERE languages IS NOT NULL AND languages != '' AND languages != '{}'`) 404 + if err != nil { 405 + return nil, err 406 + } 407 + defer rows.Close() 408 + seen := make(map[string]struct{}) 409 + for rows.Next() { 410 + var raw string 411 + if err := rows.Scan(&raw); err != nil { 412 + return nil, err 413 + } 414 + var langs map[string]int64 415 + if err := json.Unmarshal([]byte(raw), &langs); err != nil { 416 + continue 417 + } 418 + var best string 419 + var bestN int64 420 + for k, v := range langs { 421 + if v > bestN { 422 + bestN, best = v, k 423 + } 424 + } 425 + if best != "" { 426 + seen[best] = struct{}{} 427 + } 428 + } 429 + if err := rows.Err(); err != nil { 430 + return nil, err 431 + } 432 + out := make([]string, 0, len(seen)) 433 + for k := range seen { 434 + out = append(out, k) 435 + } 436 + sort.Strings(out) 437 + return out, nil 438 + } 439 + 440 + // ReposFilter narrows the result of RecentRepos. Zero values mean "no 441 + // filter" so callers can mix and match. 442 + type ReposFilter struct { 443 + Language string // primary language match (post-filter in Go over JSON map) 444 + Since time.Time // repos created at or after this time (zero = no time floor) 445 + ForksOnly bool // only rows with non-empty source 446 + NoForks bool // exclude rows with non-empty source 447 + } 448 + 449 + // RecentRepos returns repos matching the filter, sorted newest first. Pass 450 + // limit <= 0 for no limit. Uses raw SQL because the scalar star-count 451 + // subquery and handle LEFT JOIN are awkward to express via ent's query API. 452 + func (s *Store) RecentRepos(ctx context.Context, f ReposFilter, limit int) ([]Repo, error) { 453 + candidateLimit := -1 454 + if limit > 0 { 455 + candidateLimit = limit 456 + if f.Language != "" { 457 + candidateLimit = limit * 5 458 + } 459 + } 460 + forksOnly := 0 461 + if f.ForksOnly { 462 + forksOnly = 1 463 + } 464 + noForks := 0 465 + if f.NoForks { 466 + noForks = 1 467 + } 468 + rows, err := s.db.QueryContext(ctx, ` 469 + SELECT r.at_uri, r.did, r.rkey, r.name, r.knot, r.description, r.topics, 470 + r.website, r.source, r.spindle, r.repo_did, r.created_at, r.seen_at, 471 + 472 + 473 + 474 + LEFT JOIN handles h ON h.did = r.did 475 + WHERE r.created_at >= ? 476 + AND (? = '' OR r.languages IS NOT NULL) 477 + AND (? = 0 OR (r.source IS NOT NULL AND r.source != '')) 478 + AND (? = 0 OR r.source IS NULL OR r.source = '') 479 + ORDER BY r.created_at DESC 480 + LIMIT ? 481 + `, f.Since.UnixMilli(), f.Language, forksOnly, noForks, candidateLimit) 482 + if err != nil { 483 + return nil, err 484 + } 485 + 486 + 487 + 488 + 489 + 490 + if err != nil { 491 + return nil, err 492 + } 493 + if f.Language != "" && r.Primary() != f.Language { 494 + continue 495 + } 496 + out = append(out, r)
+45 -28
web/server.go
··· 27 27 28 28 29 29 30 + Language string 31 + SinceRaw string // empty = no time filter 32 + Threshold time.Time // zero = no time filter 33 + Forks string // "" or "only" 34 + } 30 35 36 + func parseQuery(r *http.Request) query { 37 + q := query{ 38 + Language: r.URL.Query().Get("language"), 39 + SinceRaw: r.URL.Query().Get("since"), 40 + Forks: r.URL.Query().Get("forks"), 41 + } 42 + if q.SinceRaw != "" { 43 + if d, err := time.ParseDuration(q.SinceRaw); err == nil { 31 44 32 45 33 46 47 + return q 48 + } 34 49 50 + func (q query) filter() store.ReposFilter { 51 + return store.ReposFilter{ 52 + Language: q.Language, 53 + Since: q.Threshold, 54 + ForksOnly: q.Forks == "only", 55 + NoForks: q.Forks == "no", 56 + } 57 + } 35 58 59 + func reposJSON(s *store.Store) http.HandlerFunc { 60 + return func(w http.ResponseWriter, r *http.Request) { 61 + q := parseQuery(r) 62 + repos, err := s.RecentRepos(r.Context(), q.filter(), 0) 63 + if err != nil { 64 + http.Error(w, err.Error(), http.StatusInternalServerError) 65 + return 36 66 37 67 38 68 39 69 40 70 41 71 42 - 43 - 44 - 45 - 46 - 47 - 48 - 49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 - 57 - 58 - 59 - 60 - 61 - 62 - 63 - 64 - 65 - 72 + func reposHTML(s *store.Store) http.HandlerFunc { 73 + return func(w http.ResponseWriter, r *http.Request) { 74 + q := parseQuery(r) 75 + repos, err := s.RecentRepos(r.Context(), q.filter(), 0) 76 + if err != nil { 77 + http.Error(w, err.Error(), http.StatusInternalServerError) 78 + return 79 + } 80 + langs, err := s.PrimaryLanguages(r.Context()) 81 + if err != nil { 66 82 http.Error(w, err.Error(), http.StatusInternalServerError) 67 83 return 68 84 } 69 85 render(w, "repos", struct { 70 - Active string 71 - Query query 72 - Repos []store.Repo 73 - }{"repos", q, repos}) 86 + Active string 87 + Query query 88 + Repos []store.Repo 89 + Languages []string 90 + }{"repos", q, repos, langs}) 74 91 } 75 92 } 76 93
+33 -1
web/templates/repos.html
··· 1 1 {{define "repos"}}{{template "head" .}} 2 2 <form method="get"> 3 - <label>language<input name="language" value="{{.Query.Language}}" placeholder="any"></label> 3 + <label>language 4 + <select name="language"> 5 + <option value=""{{if eq .Query.Language ""}} selected{{end}}>any</option> 6 + {{range .Languages}} 7 + <option value="{{.}}"{{if eq $.Query.Language .}} selected{{end}}>{{.}}</option> 8 + {{end}} 9 + </select> 10 + </label> 4 11 <label>since<input name="since" value="{{.Query.SinceRaw}}" placeholder="e.g. 24h"></label> 12 + <label>forks 13 + <select name="forks"> 14 + <option value=""{{if eq .Query.Forks ""}} selected{{end}}>any</option> 15 + <option value="only"{{if eq .Query.Forks "only"}} selected{{end}}>only forks</option> 16 + <option value="no"{{if eq .Query.Forks "no"}} selected{{end}}>no forks</option> 17 + </select> 18 + </label> 5 19 <button>filter</button> 6 20 </form> 21 + <p class="summary">{{len .Repos}} repos · <a href="/repos.json?language={{.Query.Language}}&since={{.Query.SinceRaw}}&forks={{.Query.Forks}}">json</a></p> 22 + {{if .Repos}} 23 + <table> 24 + <thead><tr><th>created</th><th>repo</th><th>knot</th><th>language</th><th>★</th><th>description</th></tr></thead> 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + {{- else -}} 33 + {{.DID}}/{{.Name}} 34 + {{- end -}} 35 + {{if .Source}} <span class="fork" title="forked from {{.Source}}">↗</span>{{end}} 36 + </td> 37 + <td class="knot">{{.Knot}}</td> 38 + <td class="lang">{{.Primary}}</td>
+1
web/templates/layout.html
··· 22 22 td.handle { font-weight: 600; } 23 23 td.handle a, td.knot a, .link a { color: inherit; text-decoration: none; border-bottom: 1px dotted currentColor; } 24 24 td.handle a:hover, td.knot a:hover, .link a:hover { border-bottom-style: solid; } 25 + .fork { color: #888; cursor: help; margin-left: .25em; } 25 26 td.knot, td.did { color: #888; } 26 27 td.lang { color: #6a6; } 27 28 td.num { text-align: right; font-variant-numeric: tabular-nums; color: #888; }

History

3 rounds 0 comments
sign up or login to add to the discussion
4 commits
expand
feat: filter by language
feat(web): additional filters
feat(web): live stats
fix(ci): install make
merge conflicts detected
expand
  • store/store.go:10
  • web/server.go:66
  • web/templates/repos.html:1
expand 0 comments
3 commits
expand
feat: filter by language
feat: additional filters
feat: live stats
expand 0 comments
chown.de submitted #0
2 commits
expand
feat: filter by language
feat: additional filters
expand 0 comments