···449 query = `select count(id) from repos where did = ?`
450 args = append(args, did)
451 case models.VanityStatStarCount:
452- query = `select count(id) from stars where subject_at like 'at://' || ? || '%'`
453 args = append(args, did)
454 case models.VanityStatNone:
455 return 0, nil
···449 query = `select count(id) from repos where did = ?`
450 args = append(args, did)
451 case models.VanityStatStarCount:
452+ query = `select count(s.id) from stars s join repos r on (s.subject_at = r.at_uri or (s.subject_did is not null and s.subject_did = r.repo_did)) where r.did = ?`
453 args = append(args, did)
454 case models.VanityStatNone:
455 return 0, nil
+36-12
appview/db/reference.go
···60 on r.did = inp.owner_did
61 and r.name = inp.name
62 join issues i
63- on i.repo_at = r.at_uri
00064 and i.issue_id = inp.issue_id
65 left join issue_comments c
66 on inp.comment_id is not null
···131 on r.did = inp.owner_did
132 and r.name = inp.name
133 join pulls p
134- on p.repo_at = r.at_uri
000135 and p.pull_id = inp.pull_id
136 left join pull_comments c
137 on inp.comment_id is not null
138- and c.repo_at = r.at_uri and c.pull_id = p.pull_id
000139 and c.id = inp.comment_id
140 `,
141 strings.Join(vals, ","),
···316 }
317 rows, err := e.Query(
318 fmt.Sprintf(
319- `select r.did, r.name, i.issue_id, i.title, i.open
320 from issues i
321 join repos r
322- on r.at_uri = i.repo_at
000323 where (i.did, i.rkey) in (%s)`,
324 strings.Join(vals, ","),
325 ),
···351 filter := orm.FilterIn("c.at_uri", aturis)
352 rows, err := e.Query(
353 fmt.Sprintf(
354- `select r.did, r.name, i.issue_id, c.id, i.title, i.open
355 from issue_comments c
356 join issues i
357 on i.at_uri = c.issue_at
358 join repos r
359- on r.at_uri = i.repo_at
000360 where %s`,
361 filter.Condition(),
362 ),
···396 }
397 rows, err := e.Query(
398 fmt.Sprintf(
399- `select r.did, r.name, p.pull_id, p.title, p.state
400 from pulls p
401 join repos r
402- on r.at_uri = p.repo_at
000403 where (p.owner_did, p.rkey) in (%s)`,
404 strings.Join(vals, ","),
405 ),
···431 filter := orm.FilterIn("c.comment_at", aturis)
432 rows, err := e.Query(
433 fmt.Sprintf(
434- `select r.did, r.name, p.pull_id, c.id, p.title, p.state
435 from repos r
436 join pulls p
437- on r.at_uri = p.repo_at
000438 join pull_comments c
439- on r.at_uri = c.repo_at and p.pull_id = c.pull_id
000440 where %s`,
441 filter.Condition(),
442 ),
···60 on r.did = inp.owner_did
61 and r.name = inp.name
62 join issues i
63+ on coalesce(
64+ nullif(i.repo_did, '') = nullif(r.repo_did, ''),
65+ i.repo_at = r.at_uri
66+ )
67 and i.issue_id = inp.issue_id
68 left join issue_comments c
69 on inp.comment_id is not null
···134 on r.did = inp.owner_did
135 and r.name = inp.name
136 join pulls p
137+ on coalesce(
138+ nullif(p.repo_did, '') = nullif(r.repo_did, ''),
139+ p.repo_at = r.at_uri
140+ )
141 and p.pull_id = inp.pull_id
142 left join pull_comments c
143 on inp.comment_id is not null
144+ and coalesce(
145+ nullif(c.repo_did, '') = nullif(r.repo_did, ''),
146+ c.repo_at = r.at_uri
147+ ) and c.pull_id = p.pull_id
148 and c.id = inp.comment_id
149 `,
150 strings.Join(vals, ","),
···325 }
326 rows, err := e.Query(
327 fmt.Sprintf(
328+ `select distinct r.did, r.name, i.issue_id, i.title, i.open
329 from issues i
330 join repos r
331+ on coalesce(
332+ nullif(i.repo_did, '') = nullif(r.repo_did, ''),
333+ i.repo_at = r.at_uri
334+ )
335 where (i.did, i.rkey) in (%s)`,
336 strings.Join(vals, ","),
337 ),
···363 filter := orm.FilterIn("c.at_uri", aturis)
364 rows, err := e.Query(
365 fmt.Sprintf(
366+ `select distinct r.did, r.name, i.issue_id, c.id, i.title, i.open
367 from issue_comments c
368 join issues i
369 on i.at_uri = c.issue_at
370 join repos r
371+ on coalesce(
372+ nullif(i.repo_did, '') = nullif(r.repo_did, ''),
373+ i.repo_at = r.at_uri
374+ )
375 where %s`,
376 filter.Condition(),
377 ),
···411 }
412 rows, err := e.Query(
413 fmt.Sprintf(
414+ `select distinct r.did, r.name, p.pull_id, p.title, p.state
415 from pulls p
416 join repos r
417+ on coalesce(
418+ nullif(p.repo_did, '') = nullif(r.repo_did, ''),
419+ p.repo_at = r.at_uri
420+ )
421 where (p.owner_did, p.rkey) in (%s)`,
422 strings.Join(vals, ","),
423 ),
···449 filter := orm.FilterIn("c.comment_at", aturis)
450 rows, err := e.Query(
451 fmt.Sprintf(
452+ `select distinct r.did, r.name, p.pull_id, c.id, p.title, p.state
453 from repos r
454 join pulls p
455+ on coalesce(
456+ nullif(p.repo_did, '') = nullif(r.repo_did, ''),
457+ p.repo_at = r.at_uri
458+ )
459 join pull_comments c
460+ on coalesce(
461+ nullif(c.repo_did, '') = nullif(r.repo_did, ''),
462+ c.repo_at = r.at_uri
463+ ) and p.pull_id = c.pull_id
464 where %s`,
465 filter.Condition(),
466 ),
+64-15
appview/db/repos.go
···46 website,
47 topics,
48 source,
49- spindle
050 from
51 repos r
52 %s
···64 for rows.Next() {
65 var repo models.Repo
66 var createdAt string
67- var description, website, topicStr, source, spindle sql.NullString
6869 err := rows.Scan(
70 &repo.Id,
···78 &topicStr,
79 &source,
80 &spindle,
081 )
82 if err != nil {
83 return nil, fmt.Errorf("failed to execute repo query: %w ", err)
···100 }
101 if spindle.Valid {
102 repo.Spindle = spindle.String
000103 }
104105 repo.RepoStats = &models.RepoStats{}
···352 var nullableDescription sql.NullString
353 var nullableWebsite sql.NullString
354 var nullableTopicStr sql.NullString
0355356- row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics from repos where at_uri = ?`, atUri)
357358 var createdAt string
359- if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr); err != nil {
360 return nil, err
361 }
362 createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
···370 }
371 if nullableTopicStr.Valid {
372 repo.Topics = strings.Fields(nullableTopicStr.String)
000373 }
374375 return &repo, nil
376}
377378func PutRepo(tx *sql.Tx, repo models.Repo) error {
0000379 _, err := tx.Exec(
380 `update repos
381- set knot = ?, description = ?, website = ?, topics = ?
382 where did = ? and rkey = ?
383 `,
384- repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repo.Did, repo.Rkey,
385 )
386 return err
387}
388389func AddRepo(tx *sql.Tx, repo *models.Repo) error {
0000390 _, err := tx.Exec(
391 `insert into repos
392- (did, name, knot, rkey, at_uri, description, website, topics, source)
393- values (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
394- repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Website, repo.TopicStr(), repo.Source,
395 )
396 if err != nil {
397 return fmt.Errorf("failed to insert repo: %w", err)
···401 if err := SubscribeLabel(tx, &models.RepoLabel{
402 RepoAt: repo.RepoAt(),
403 LabelAt: syntax.ATURI(dl),
0404 }); err != nil {
405 return fmt.Errorf("failed to subscribe to label: %w", err)
406 }
···438 var repos []models.Repo
439440 rows, err := e.Query(
441- `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.website, r.created, r.source
442 from repos r
443 left join collaborators c on r.at_uri = c.repo_at
444 where (r.did = ? or c.subject_did = ?)
···458 var nullableDescription sql.NullString
459 var nullableWebsite sql.NullString
460 var nullableSource sql.NullString
0461462- err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource)
463 if err != nil {
464 return nil, err
465 }
466467 if nullableDescription.Valid {
468 repo.Description = nullableDescription.String
000469 }
470471 if nullableSource.Valid {
472 repo.Source = nullableSource.String
000473 }
474475 createdAtTime, err := time.Parse(time.RFC3339, createdAt)
···496 var nullableWebsite sql.NullString
497 var nullableTopicStr sql.NullString
498 var nullableSource sql.NullString
0499500 row := e.QueryRow(
501- `select id, did, name, knot, rkey, description, website, topics, created, source
502 from repos
503 where did = ? and name = ? and source is not null and source != ''`,
504 did, name,
505 )
506507- err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &createdAt, &nullableSource)
508 if err != nil {
509 return nil, err
510 }
···523524 if nullableSource.Valid {
525 repo.Source = nullableSource.String
000526 }
527528 createdAtTime, err := time.Parse(time.RFC3339, createdAt)
···535 return &repo, nil
536}
53700000000000000538func UpdateDescription(e Execer, repoAt, newDescription string) error {
539 _, err := e.Exec(
540 `update repos set description = ? where at_uri = ?`, newDescription, repoAt)
···548}
549550func SubscribeLabel(e Execer, rl *models.RepoLabel) error {
551- query := `insert or ignore into repo_labels (repo_at, label_at) values (?, ?)`
000000552553- _, err := e.Exec(query, rl.RepoAt.String(), rl.LabelAt.String())
554 return err
555}
556
···7)
89type Star struct {
10- Did string
11- RepoAt syntax.ATURI
12- Created time.Time
13- Rkey string
014}
1516// RepoStar is used for reverse mapping to repos
···7)
89type Star struct {
10+ Did string
11+ RepoAt syntax.ATURI
12+ SubjectDid string
13+ Created time.Time
14+ Rkey string
15}
1617// RepoStar is used for reverse mapping to repos
···3031// NOTE: this... should not even be here. the entire package will be removed in future refactor
32func GetBaseRepoPath(r *http.Request, repo *models.Repo) string {
00033 var (
34 user = chi.URLParam(r, "user")
35 name = chi.URLParam(r, "repo")
···108 // this is basically a models.Repo
109 OwnerDid: ownerId.DID.String(),
110 OwnerHandle: ownerId.Handle.String(),
0111 Name: repo.Name,
112 Rkey: repo.Rkey,
113 Description: repo.Description,
···3031// NOTE: this... should not even be here. the entire package will be removed in future refactor
32func GetBaseRepoPath(r *http.Request, repo *models.Repo) string {
33+ if repo.RepoDid != "" {
34+ return repo.RepoDid
35+ }
36 var (
37 user = chi.URLParam(r, "user")
38 name = chi.URLParam(r, "repo")
···111 // this is basically a models.Repo
112 OwnerDid: ownerId.DID.String(),
113 OwnerHandle: ownerId.Handle.String(),
114+ RepoDid: repo.RepoDid,
115 Name: repo.Name,
116 Rkey: repo.Rkey,
117 Description: repo.Description,
···23import (
4 "context"
05 "encoding/json"
6 "errors"
7 "fmt"
···86 return err
87 }
8800000089 knownKnots, err := enforcer.GetKnotsForUser(record.CommitterDid)
90 if err != nil {
91 return err
···95 }
9697 logger.Info("processing gitRefUpdate event",
98- "repo_did", record.RepoDid,
99 "repo_name", record.RepoName,
0100 "ref", record.Ref,
101 "old_sha", record.OldSha,
102 "new_sha", record.NewSha)
103104- // trigger webhook notifications first (before other ops that might fail)
105 var errWebhook error
106- repos, err := db.GetRepos(
107- d,
108- 0,
109- orm.FilterEq("did", record.RepoDid),
110- orm.FilterEq("name", record.RepoName),
111- )
112- if err != nil {
113- errWebhook = fmt.Errorf("failed to lookup repo for webhooks: %w", err)
114- } else if len(repos) == 1 {
0000000000000000115 notifier.Push(ctx, &repos[0], record.Ref, record.OldSha, record.NewSha, record.CommitterDid)
116 }
117···167168func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error {
169 if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil {
170- return fmt.Errorf("empty language data for repo: %s/%s", record.RepoDid, record.RepoName)
171 }
172173- repos, err := db.GetRepos(
174- d,
175- 0,
176- orm.FilterEq("did", record.RepoDid),
177- orm.FilterEq("name", record.RepoName),
178- )
179- if err != nil {
180- return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.RepoDid, record.RepoName, err)
0181 }
182- if len(repos) != 1 {
183- return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos))
0000000000000184 }
185- repo := repos[0]
186187 ref := plumbing.ReferenceName(record.Ref)
188 if !ref.IsBranch() {
···236 }
237238 // does this repo have a spindle configured?
239- repos, err := db.GetRepos(
240- d,
241- 0,
242- orm.FilterEq("did", record.TriggerMetadata.Repo.Did),
243- orm.FilterEq("name", record.TriggerMetadata.Repo.Repo),
244- )
245- if err != nil {
246- return fmt.Errorf("failed to look for repo in DB: nsid %s, rkey %s, %w", msg.Nsid, msg.Rkey, err)
00000000000000247 }
0248 if len(repos) != 1 {
249 return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos))
250 }
···23import (
4 "context"
5+ "database/sql"
6 "encoding/json"
7 "errors"
8 "fmt"
···87 return err
88 }
8990+ // backward compat: old knots emit owner DID as "repoDid" with no "ownerDid" field
91+ if record.OwnerDid == "" && record.RepoDid != nil && *record.RepoDid != "" {
92+ record.OwnerDid = *record.RepoDid
93+ record.RepoDid = nil
94+ }
95+96 knownKnots, err := enforcer.GetKnotsForUser(record.CommitterDid)
97 if err != nil {
98 return err
···102 }
103104 logger.Info("processing gitRefUpdate event",
105+ "owner_did", record.OwnerDid,
106 "repo_name", record.RepoName,
107+ "repo_did", record.RepoDid,
108 "ref", record.Ref,
109 "old_sha", record.OldSha,
110 "new_sha", record.NewSha)
1110112 var errWebhook error
113+ var repos []models.Repo
114+115+ if record.RepoDid != nil && *record.RepoDid != "" {
116+ repo, lookupErr := db.GetRepoByDid(d, *record.RepoDid)
117+ switch {
118+ case lookupErr == nil:
119+ repos = []models.Repo{*repo}
120+ case !errors.Is(lookupErr, sql.ErrNoRows):
121+ logger.Warn("failed to look up repo by DID", "err", lookupErr, "repoDid", *record.RepoDid)
122+ }
123+ }
124+125+ if len(repos) == 0 {
126+ repos, err = db.GetRepos(
127+ d,
128+ 0,
129+ orm.FilterEq("did", record.OwnerDid),
130+ orm.FilterEq("name", record.RepoName),
131+ )
132+ if err != nil {
133+ errWebhook = fmt.Errorf("failed to lookup repo for webhooks: %w", err)
134+ }
135+ }
136+137+ if errWebhook == nil && len(repos) == 1 {
138 notifier.Push(ctx, &repos[0], record.Ref, record.OldSha, record.NewSha, record.CommitterDid)
139 }
140···190191func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error {
192 if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil {
193+ return fmt.Errorf("empty language data for repo: %s/%s", record.OwnerDid, record.RepoName)
194 }
195196+ var repo models.Repo
197+ if record.RepoDid != nil && *record.RepoDid != "" {
198+ r, lookupErr := db.GetRepoByDid(d, *record.RepoDid)
199+ switch {
200+ case lookupErr == nil:
201+ repo = *r
202+ case !errors.Is(lookupErr, sql.ErrNoRows):
203+ return fmt.Errorf("failed to look up repo by DID %s: %w", *record.RepoDid, lookupErr)
204+ }
205 }
206+207+ if repo.Id == 0 {
208+ repos, err := db.GetRepos(
209+ d,
210+ 0,
211+ orm.FilterEq("did", record.OwnerDid),
212+ orm.FilterEq("name", record.RepoName),
213+ )
214+ if err != nil {
215+ return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.OwnerDid, record.RepoName, err)
216+ }
217+ if len(repos) != 1 {
218+ return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos))
219+ }
220+ repo = repos[0]
221 }
0222223 ref := plumbing.ReferenceName(record.Ref)
224 if !ref.IsBranch() {
···272 }
273274 // does this repo have a spindle configured?
275+ var repos []models.Repo
276+ if record.TriggerMetadata.Repo.RepoDid != nil && *record.TriggerMetadata.Repo.RepoDid != "" {
277+ repo, lookupErr := db.GetRepoByDid(d, *record.TriggerMetadata.Repo.RepoDid)
278+ switch {
279+ case lookupErr == nil:
280+ repos = []models.Repo{*repo}
281+ case !errors.Is(lookupErr, sql.ErrNoRows):
282+ return fmt.Errorf("failed to look up repo by DID %s: %w", *record.TriggerMetadata.Repo.RepoDid, lookupErr)
283+ }
284+ }
285+286+ if len(repos) == 0 {
287+ var err error
288+ repos, err = db.GetRepos(
289+ d,
290+ 0,
291+ orm.FilterEq("did", record.TriggerMetadata.Repo.Did),
292+ orm.FilterEq("name", record.TriggerMetadata.Repo.Repo),
293+ )
294+ if err != nil {
295+ return fmt.Errorf("failed to look for repo in DB: nsid %s, rkey %s, %w", msg.Nsid, msg.Rkey, err)
296+ }
297 }
298+299 if len(repos) != 1 {
300 return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos))
301 }
+28-2
appview/state/router.go
···1package state
23import (
0004 "net/http"
5 "strings"
67 "github.com/go-chi/chi/v5"
08 "tangled.org/core/appview/issues"
9 "tangled.org/core/appview/knots"
10 "tangled.org/core/appview/labels"
···45 if len(pathParts) > 0 {
46 firstPart := pathParts[0]
4748- // if using a DID or handle, just continue as per usual
49- if userutil.IsDid(firstPart) || userutil.IsHandle(firstPart) {
000000000000000000000050 userRouter.ServeHTTP(w, r)
51 return
52 }