···199199 unique(starred_by_did, repo_at)
200200 );
201201202202+ create table if not exists reactions (
203203+ id integer primary key autoincrement,
204204+ reacted_by_did text not null,
205205+ thread_at text not null,
206206+ kind text not null,
207207+ rkey text not null,
208208+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
209209+ unique(reacted_by_did, thread_at, kind)
210210+ );
211211+202212 create table if not exists emails (
203213 id integer primary key autoincrement,
204214 did text not null,
···409419 foreign key (pipeline_knot, pipeline_rkey)
410420 references pipelines (knot, rkey)
411421 on delete cascade
422422+ );
423423+424424+ create table if not exists repo_languages (
425425+ -- identifiers
426426+ id integer primary key autoincrement,
427427+428428+ -- repo identifiers
429429+ repo_at text not null,
430430+ ref text not null,
431431+ is_default_ref integer not null default 0,
432432+433433+ -- language breakdown
434434+ language text not null,
435435+ bytes integer not null check (bytes >= 0),
436436+437437+ unique(repo_at, ref, language)
412438 );
413439414440 create table if not exists migrations (
+2-2
appview/db/issues.go
···277277}
278278279279func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) {
280280- query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?`
280280+ query := `select owner_did, issue_id, created, title, body, open, issue_at from issues where repo_at = ? and issue_id = ?`
281281 row := e.QueryRow(query, repoAt, issueId)
282282283283 var issue Issue
284284 var createdAt string
285285- err := row.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open)
285285+ err := row.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &issue.IssueAt)
286286 if err != nil {
287287 return nil, nil, err
288288 }
+93
appview/db/language.go
···11+package db
22+33+import (
44+ "fmt"
55+ "strings"
66+77+ "github.com/bluesky-social/indigo/atproto/syntax"
88+)
99+1010+type RepoLanguage struct {
1111+ Id int64
1212+ RepoAt syntax.ATURI
1313+ Ref string
1414+ IsDefaultRef bool
1515+ Language string
1616+ Bytes int64
1717+}
1818+1919+func GetRepoLanguages(e Execer, filters ...filter) ([]RepoLanguage, error) {
2020+ var conditions []string
2121+ var args []any
2222+ for _, filter := range filters {
2323+ conditions = append(conditions, filter.Condition())
2424+ args = append(args, filter.Arg()...)
2525+ }
2626+2727+ whereClause := ""
2828+ if conditions != nil {
2929+ whereClause = " where " + strings.Join(conditions, " and ")
3030+ }
3131+3232+ query := fmt.Sprintf(
3333+ `select id, repo_at, ref, is_default_ref, language, bytes from repo_languages %s`,
3434+ whereClause,
3535+ )
3636+ rows, err := e.Query(query, args...)
3737+3838+ if err != nil {
3939+ return nil, fmt.Errorf("failed to execute query: %w ", err)
4040+ }
4141+4242+ var langs []RepoLanguage
4343+ for rows.Next() {
4444+ var rl RepoLanguage
4545+ var isDefaultRef int
4646+4747+ err := rows.Scan(
4848+ &rl.Id,
4949+ &rl.RepoAt,
5050+ &rl.Ref,
5151+ &isDefaultRef,
5252+ &rl.Language,
5353+ &rl.Bytes,
5454+ )
5555+ if err != nil {
5656+ return nil, fmt.Errorf("failed to scan: %w ", err)
5757+ }
5858+5959+ if isDefaultRef != 0 {
6060+ rl.IsDefaultRef = true
6161+ }
6262+6363+ langs = append(langs, rl)
6464+ }
6565+ if err = rows.Err(); err != nil {
6666+ return nil, fmt.Errorf("failed to scan rows: %w ", err)
6767+ }
6868+6969+ return langs, nil
7070+}
7171+7272+func InsertRepoLanguages(e Execer, langs []RepoLanguage) error {
7373+ stmt, err := e.Prepare(
7474+ "insert or replace into repo_languages (repo_at, ref, is_default_ref, language, bytes) values (?, ?, ?, ?, ?)",
7575+ )
7676+ if err != nil {
7777+ return err
7878+ }
7979+8080+ for _, l := range langs {
8181+ isDefaultRef := 0
8282+ if l.IsDefaultRef {
8383+ isDefaultRef = 1
8484+ }
8585+8686+ _, err := stmt.Exec(l.RepoAt, l.Ref, isDefaultRef, l.Language, l.Bytes)
8787+ if err != nil {
8888+ return err
8989+ }
9090+ }
9191+9292+ return nil
9393+}
+108
appview/db/profile.go
···348348 return tx.Commit()
349349}
350350351351+func GetProfiles(e Execer, filters ...filter) ([]Profile, error) {
352352+ var conditions []string
353353+ var args []any
354354+ for _, filter := range filters {
355355+ conditions = append(conditions, filter.Condition())
356356+ args = append(args, filter.Arg()...)
357357+ }
358358+359359+ whereClause := ""
360360+ if conditions != nil {
361361+ whereClause = " where " + strings.Join(conditions, " and ")
362362+ }
363363+364364+ profilesQuery := fmt.Sprintf(
365365+ `select
366366+ id,
367367+ did,
368368+ description,
369369+ include_bluesky,
370370+ location
371371+ from
372372+ profile
373373+ %s`,
374374+ whereClause,
375375+ )
376376+ rows, err := e.Query(profilesQuery, args...)
377377+ if err != nil {
378378+ return nil, err
379379+ }
380380+381381+ profileMap := make(map[string]*Profile)
382382+ for rows.Next() {
383383+ var profile Profile
384384+ var includeBluesky int
385385+386386+ err = rows.Scan(&profile.ID, &profile.Did, &profile.Description, &includeBluesky, &profile.Location)
387387+ if err != nil {
388388+ return nil, err
389389+ }
390390+391391+ if includeBluesky != 0 {
392392+ profile.IncludeBluesky = true
393393+ }
394394+395395+ profileMap[profile.Did] = &profile
396396+ }
397397+ if err = rows.Err(); err != nil {
398398+ return nil, err
399399+ }
400400+401401+ // populate profile links
402402+ inClause := strings.TrimSuffix(strings.Repeat("?, ", len(profileMap)), ", ")
403403+ args = make([]any, len(profileMap))
404404+ i := 0
405405+ for did := range profileMap {
406406+ args[i] = did
407407+ i++
408408+ }
409409+410410+ linksQuery := fmt.Sprintf("select link, did from profile_links where did in (%s)", inClause)
411411+ rows, err = e.Query(linksQuery, args...)
412412+ if err != nil {
413413+ return nil, err
414414+ }
415415+ idxs := make(map[string]int)
416416+ for did := range profileMap {
417417+ idxs[did] = 0
418418+ }
419419+ for rows.Next() {
420420+ var link, did string
421421+ if err = rows.Scan(&link, &did); err != nil {
422422+ return nil, err
423423+ }
424424+425425+ idx := idxs[did]
426426+ profileMap[did].Links[idx] = link
427427+ idxs[did] = idx + 1
428428+ }
429429+430430+ pinsQuery := fmt.Sprintf("select at_uri, did from profile_pinned_repositories where did in (%s)", inClause)
431431+ rows, err = e.Query(pinsQuery, args...)
432432+ if err != nil {
433433+ return nil, err
434434+ }
435435+ idxs = make(map[string]int)
436436+ for did := range profileMap {
437437+ idxs[did] = 0
438438+ }
439439+ for rows.Next() {
440440+ var link syntax.ATURI
441441+ var did string
442442+ if err = rows.Scan(&link, &did); err != nil {
443443+ return nil, err
444444+ }
445445+446446+ idx := idxs[did]
447447+ profileMap[did].PinnedRepos[idx] = link
448448+ idxs[did] = idx + 1
449449+ }
450450+451451+ var profiles []Profile
452452+ for _, p := range profileMap {
453453+ profiles = append(profiles, *p)
454454+ }
455455+456456+ return profiles, nil
457457+}
458458+351459func GetProfile(e Execer, did string) (*Profile, error) {
352460 var profile Profile
353461 profile.Did = did