+172
appview/db/reference.go
+172
appview/db/reference.go
···
···
1
+
package db
2
+
3
+
import (
4
+
"database/sql"
5
+
"fmt"
6
+
"strings"
7
+
8
+
"github.com/bluesky-social/indigo/atproto/syntax"
9
+
"tangled.org/core/api/tangled"
10
+
"tangled.org/core/appview/models"
11
+
)
12
+
13
+
// FindReferences resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs.
14
+
// It will ignore missing refLinks.
15
+
func FindReferences(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) {
16
+
var (
17
+
issueRefs []models.ReferenceLink
18
+
pullRefs []models.ReferenceLink
19
+
)
20
+
for _, ref := range refLinks {
21
+
switch ref.Kind {
22
+
case models.RefKindIssue:
23
+
issueRefs = append(issueRefs, ref)
24
+
case models.RefKindPull:
25
+
pullRefs = append(pullRefs, ref)
26
+
}
27
+
}
28
+
issueUris, err := findIssueReferences(e, issueRefs)
29
+
if err != nil {
30
+
return nil, err
31
+
}
32
+
pullUris, err := findPullReferences(e, pullRefs)
33
+
if err != nil {
34
+
return nil, err
35
+
}
36
+
37
+
return append(issueUris, pullUris...), nil
38
+
}
39
+
40
+
func findIssueReferences(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) {
41
+
if len(refLinks) == 0 {
42
+
return nil, nil
43
+
}
44
+
vals := make([]string, len(refLinks))
45
+
args := make([]any, 0, len(refLinks)*4)
46
+
for i, ref := range refLinks {
47
+
vals[i] = "(?, ?, ?, ?)"
48
+
args = append(args, ref.Handle, ref.Repo, ref.SubjectId, ref.CommentId)
49
+
}
50
+
query := fmt.Sprintf(
51
+
`with input(owner_did, name, issue_id, comment_id) as (
52
+
values %s
53
+
)
54
+
select
55
+
i.did, i.rkey,
56
+
c.did, c.rkey
57
+
from input inp
58
+
join repos r
59
+
on r.did = inp.owner_did
60
+
and r.name = inp.name
61
+
join issues i
62
+
on i.repo_at = r.at_uri
63
+
and i.issue_id = inp.issue_id
64
+
left join issue_comments c
65
+
on inp.comment_id is not null
66
+
and c.issue_at = i.at_uri
67
+
and c.id = inp.comment_id
68
+
`,
69
+
strings.Join(vals, ","),
70
+
)
71
+
rows, err := e.Query(query, args...)
72
+
if err != nil {
73
+
return nil, err
74
+
}
75
+
defer rows.Close()
76
+
77
+
var uris []syntax.ATURI
78
+
79
+
for rows.Next() {
80
+
// Scan rows
81
+
var issueOwner, issueRkey string
82
+
var commentOwner, commentRkey sql.NullString
83
+
var uri syntax.ATURI
84
+
if err := rows.Scan(&issueOwner, &issueRkey, &commentOwner, &commentRkey); err != nil {
85
+
return nil, err
86
+
}
87
+
if commentOwner.Valid && commentRkey.Valid {
88
+
uri = syntax.ATURI(fmt.Sprintf(
89
+
"at://%s/%s/%s",
90
+
commentOwner.String,
91
+
tangled.RepoIssueCommentNSID,
92
+
commentRkey.String,
93
+
))
94
+
} else {
95
+
uri = syntax.ATURI(fmt.Sprintf(
96
+
"at://%s/%s/%s",
97
+
issueOwner,
98
+
tangled.RepoIssueNSID,
99
+
issueRkey,
100
+
))
101
+
}
102
+
uris = append(uris, uri)
103
+
}
104
+
return uris, nil
105
+
}
106
+
107
+
func findPullReferences(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) {
108
+
if len(refLinks) == 0 {
109
+
return nil, nil
110
+
}
111
+
vals := make([]string, len(refLinks))
112
+
args := make([]any, 0, len(refLinks)*4)
113
+
for i, ref := range refLinks {
114
+
vals[i] = "(?, ?, ?, ?)"
115
+
args = append(args, ref.Handle, ref.Repo, ref.SubjectId, ref.CommentId)
116
+
}
117
+
query := fmt.Sprintf(
118
+
`with input(owner_did, name, pull_id, comment_id) as (
119
+
values %s
120
+
)
121
+
select
122
+
p.owner_did, p.rkey,
123
+
c.owner_did, c.rkey
124
+
from input inp
125
+
join repos r
126
+
on r.did = inp.owner_did
127
+
and r.name = inp.name
128
+
join pulls p
129
+
on p.repo_at = r.at_uri
130
+
and p.pull_id = inp.pull_id
131
+
left join pull_comments c
132
+
on inp.comment_id is not null
133
+
and c.repo_at = r.at_uri and c.pull_id = p.pull_id
134
+
and c.id = inp.comment_id
135
+
`,
136
+
strings.Join(vals, ","),
137
+
)
138
+
rows, err := e.Query(query, args...)
139
+
if err != nil {
140
+
return nil, err
141
+
}
142
+
defer rows.Close()
143
+
144
+
var uris []syntax.ATURI
145
+
146
+
for rows.Next() {
147
+
// Scan rows
148
+
var pullOwner, pullRkey string
149
+
var commentOwner, commentRkey sql.NullString
150
+
var uri syntax.ATURI
151
+
if err := rows.Scan(&pullOwner, &pullRkey, &commentOwner, &commentRkey); err != nil {
152
+
return nil, err
153
+
}
154
+
if commentOwner.Valid && commentRkey.Valid {
155
+
uri = syntax.ATURI(fmt.Sprintf(
156
+
"at://%s/%s/%s",
157
+
commentOwner.String,
158
+
tangled.RepoPullCommentNSID,
159
+
commentRkey.String,
160
+
))
161
+
} else {
162
+
uri = syntax.ATURI(fmt.Sprintf(
163
+
"at://%s/%s/%s",
164
+
pullOwner,
165
+
tangled.RepoPullNSID,
166
+
pullRkey,
167
+
))
168
+
}
169
+
uris = append(uris, uri)
170
+
}
171
+
return uris, nil
172
+
}
+10
-20
appview/issues/issues.go
+10
-20
appview/issues/issues.go
···
23
"tangled.org/core/appview/notify"
24
"tangled.org/core/appview/oauth"
25
"tangled.org/core/appview/pages"
26
-
"tangled.org/core/appview/pages/markup"
27
"tangled.org/core/appview/pages/repoinfo"
28
"tangled.org/core/appview/pagination"
29
"tangled.org/core/appview/reporesolver"
30
"tangled.org/core/appview/validator"
31
"tangled.org/core/idresolver"
···
39
enforcer *rbac.Enforcer
40
pages *pages.Pages
41
idResolver *idresolver.Resolver
42
db *db.DB
43
config *config.Config
44
notifier notify.Notifier
···
53
enforcer *rbac.Enforcer,
54
pages *pages.Pages,
55
idResolver *idresolver.Resolver,
56
db *db.DB,
57
config *config.Config,
58
notifier notify.Notifier,
···
66
enforcer: enforcer,
67
pages: pages,
68
idResolver: idResolver,
69
db: db,
70
config: config,
71
notifier: notifier,
···
391
replyTo = &replyToUri
392
}
393
394
comment := models.IssueComment{
395
Did: user.Did,
396
Rkey: tid.TID(),
···
447
// notify about the new comment
448
comment.Id = commentId
449
450
-
rawMentions := markup.FindUserMentions(comment.Body)
451
-
idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions)
452
-
l.Debug("parsed mentions", "raw", rawMentions, "idents", idents)
453
-
var mentions []syntax.DID
454
-
for _, ident := range idents {
455
-
if ident != nil && !ident.Handle.IsInvalidHandle() {
456
-
mentions = append(mentions, ident.DID)
457
-
}
458
-
}
459
rp.notifier.NewIssueComment(r.Context(), &comment, mentions)
460
461
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
···
870
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
871
})
872
case http.MethodPost:
873
issue := &models.Issue{
874
RepoAt: f.RepoAt(),
875
Rkey: tid.TID(),
876
Title: r.FormValue("title"),
877
-
Body: r.FormValue("body"),
878
Open: true,
879
Did: user.Did,
880
Created: time.Now(),
···
946
// everything is successful, do not rollback the atproto record
947
atUri = ""
948
949
-
rawMentions := markup.FindUserMentions(issue.Body)
950
-
idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions)
951
-
l.Debug("parsed mentions", "raw", rawMentions, "idents", idents)
952
-
var mentions []syntax.DID
953
-
for _, ident := range idents {
954
-
if ident != nil && !ident.Handle.IsInvalidHandle() {
955
-
mentions = append(mentions, ident.DID)
956
-
}
957
-
}
958
rp.notifier.NewIssue(r.Context(), issue, mentions)
959
960
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
···
23
"tangled.org/core/appview/notify"
24
"tangled.org/core/appview/oauth"
25
"tangled.org/core/appview/pages"
26
"tangled.org/core/appview/pages/repoinfo"
27
"tangled.org/core/appview/pagination"
28
+
"tangled.org/core/appview/refresolver"
29
"tangled.org/core/appview/reporesolver"
30
"tangled.org/core/appview/validator"
31
"tangled.org/core/idresolver"
···
39
enforcer *rbac.Enforcer
40
pages *pages.Pages
41
idResolver *idresolver.Resolver
42
+
refResolver *refresolver.Resolver
43
db *db.DB
44
config *config.Config
45
notifier notify.Notifier
···
54
enforcer *rbac.Enforcer,
55
pages *pages.Pages,
56
idResolver *idresolver.Resolver,
57
+
refResolver *refresolver.Resolver,
58
db *db.DB,
59
config *config.Config,
60
notifier notify.Notifier,
···
68
enforcer: enforcer,
69
pages: pages,
70
idResolver: idResolver,
71
+
refResolver: refResolver,
72
db: db,
73
config: config,
74
notifier: notifier,
···
394
replyTo = &replyToUri
395
}
396
397
+
mentions, _ := rp.refResolver.Resolve(r.Context(), body)
398
+
399
comment := models.IssueComment{
400
Did: user.Did,
401
Rkey: tid.TID(),
···
452
// notify about the new comment
453
comment.Id = commentId
454
455
rp.notifier.NewIssueComment(r.Context(), &comment, mentions)
456
457
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
···
866
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
867
})
868
case http.MethodPost:
869
+
body := r.FormValue("body")
870
+
mentions, _ := rp.refResolver.Resolve(r.Context(), body)
871
+
872
issue := &models.Issue{
873
RepoAt: f.RepoAt(),
874
Rkey: tid.TID(),
875
Title: r.FormValue("title"),
876
+
Body: body,
877
Open: true,
878
Did: user.Did,
879
Created: time.Now(),
···
945
// everything is successful, do not rollback the atproto record
946
atUri = ""
947
948
rp.notifier.NewIssue(r.Context(), issue, mentions)
949
950
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
+18
appview/models/reference.go
+18
appview/models/reference.go
···
···
1
+
package models
2
+
3
+
type RefKind int
4
+
5
+
const (
6
+
RefKindIssue RefKind = iota
7
+
RefKindPull
8
+
)
9
+
10
+
// /@alice.com/cool-proj/issues/123
11
+
// /@alice.com/cool-proj/issues/123#comment-321
12
+
type ReferenceLink struct {
13
+
Handle string
14
+
Repo string
15
+
Kind RefKind
16
+
SubjectId int
17
+
CommentId *int
18
+
}
-24
appview/pages/markup/markdown.go
-24
appview/pages/markup/markdown.go
···
304
return path.Join(rctx.CurrentDir, dst)
305
}
306
307
-
// FindUserMentions returns Set of user handles from given markup soruce.
308
-
// It doesn't guarntee unique DIDs
309
-
func FindUserMentions(source string) []string {
310
-
var (
311
-
mentions []string
312
-
mentionsSet = make(map[string]struct{})
313
-
md = NewMarkdown()
314
-
sourceBytes = []byte(source)
315
-
root = md.Parser().Parse(text.NewReader(sourceBytes))
316
-
)
317
-
ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
318
-
if entering && n.Kind() == textension.KindAt {
319
-
handle := n.(*textension.AtNode).Handle
320
-
mentionsSet[handle] = struct{}{}
321
-
return ast.WalkSkipChildren, nil
322
-
}
323
-
return ast.WalkContinue, nil
324
-
})
325
-
for handle := range mentionsSet {
326
-
mentions = append(mentions, handle)
327
-
}
328
-
return mentions
329
-
}
330
-
331
func isAbsoluteUrl(link string) bool {
332
parsed, err := url.Parse(link)
333
if err != nil {
+124
appview/pages/markup/reference_link.go
+124
appview/pages/markup/reference_link.go
···
···
1
+
package markup
2
+
3
+
import (
4
+
"maps"
5
+
"net/url"
6
+
"path"
7
+
"slices"
8
+
"strconv"
9
+
"strings"
10
+
11
+
"github.com/yuin/goldmark/ast"
12
+
"github.com/yuin/goldmark/text"
13
+
"tangled.org/core/appview/models"
14
+
textension "tangled.org/core/appview/pages/markup/extension"
15
+
)
16
+
17
+
// FindReferences collects all links referencing tangled-related objects
18
+
// like issues, PRs, comments or even @-mentions
19
+
// This funciton doesn't actually check for the existence of records in the DB
20
+
// or the PDS; it merely returns a list of what are presumed to be references.
21
+
func FindReferences(baseUrl string, source string) ([]string, []models.ReferenceLink) {
22
+
var (
23
+
refLinkSet = make(map[models.ReferenceLink]struct{})
24
+
mentionsSet = make(map[string]struct{})
25
+
md = NewMarkdown()
26
+
sourceBytes = []byte(source)
27
+
root = md.Parser().Parse(text.NewReader(sourceBytes))
28
+
)
29
+
// trim url scheme. the SSL shouldn't matter
30
+
baseUrl = strings.TrimPrefix(baseUrl, "https://")
31
+
baseUrl = strings.TrimPrefix(baseUrl, "http://")
32
+
33
+
ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
34
+
if !entering {
35
+
return ast.WalkContinue, nil
36
+
}
37
+
switch n.Kind() {
38
+
case textension.KindAt:
39
+
handle := n.(*textension.AtNode).Handle
40
+
mentionsSet[handle] = struct{}{}
41
+
return ast.WalkSkipChildren, nil
42
+
case ast.KindLink:
43
+
dest := string(n.(*ast.Link).Destination)
44
+
ref := parseTangledLink(baseUrl, dest)
45
+
if ref != nil {
46
+
refLinkSet[*ref] = struct{}{}
47
+
}
48
+
return ast.WalkSkipChildren, nil
49
+
case ast.KindAutoLink:
50
+
an := n.(*ast.AutoLink)
51
+
if an.AutoLinkType == ast.AutoLinkURL {
52
+
dest := string(an.URL(sourceBytes))
53
+
ref := parseTangledLink(baseUrl, dest)
54
+
if ref != nil {
55
+
refLinkSet[*ref] = struct{}{}
56
+
}
57
+
}
58
+
return ast.WalkSkipChildren, nil
59
+
}
60
+
return ast.WalkContinue, nil
61
+
})
62
+
mentions := slices.Collect(maps.Keys(mentionsSet))
63
+
references := slices.Collect(maps.Keys(refLinkSet))
64
+
return mentions, references
65
+
}
66
+
67
+
func parseTangledLink(baseHost string, urlStr string) *models.ReferenceLink {
68
+
u, err := url.Parse(urlStr)
69
+
if err != nil {
70
+
return nil
71
+
}
72
+
73
+
if u.Host != "" && !strings.EqualFold(u.Host, baseHost) {
74
+
return nil
75
+
}
76
+
77
+
p := path.Clean(u.Path)
78
+
parts := strings.FieldsFunc(p, func(r rune) bool { return r == '/' })
79
+
if len(parts) < 4 {
80
+
// need at least: handle / repo / kind / id
81
+
return nil
82
+
}
83
+
84
+
var (
85
+
handle = parts[0]
86
+
repo = parts[1]
87
+
kindSeg = parts[2]
88
+
subjectSeg = parts[3]
89
+
)
90
+
91
+
handle = strings.TrimPrefix(handle, "@")
92
+
93
+
var kind models.RefKind
94
+
switch kindSeg {
95
+
case "issues":
96
+
kind = models.RefKindIssue
97
+
case "pulls":
98
+
kind = models.RefKindPull
99
+
default:
100
+
return nil
101
+
}
102
+
103
+
subjectId, err := strconv.Atoi(subjectSeg)
104
+
if err != nil {
105
+
return nil
106
+
}
107
+
var commentId *int
108
+
if u.Fragment != "" {
109
+
if strings.HasPrefix(u.Fragment, "comment-") {
110
+
commentIdStr := u.Fragment[len("comment-"):]
111
+
if id, err := strconv.Atoi(commentIdStr); err == nil {
112
+
commentId = &id
113
+
}
114
+
}
115
+
}
116
+
117
+
return &models.ReferenceLink{
118
+
Handle: handle,
119
+
Repo: repo,
120
+
Kind: kind,
121
+
SubjectId: subjectId,
122
+
CommentId: commentId,
123
+
}
124
+
}
+42
appview/pages/markup/reference_link_test.go
+42
appview/pages/markup/reference_link_test.go
···
···
1
+
package markup_test
2
+
3
+
import (
4
+
"testing"
5
+
6
+
"github.com/stretchr/testify/assert"
7
+
"tangled.org/core/appview/models"
8
+
"tangled.org/core/appview/pages/markup"
9
+
)
10
+
11
+
func TestMarkupParsing(t *testing.T) {
12
+
tests := []struct {
13
+
name string
14
+
source string
15
+
wantHandles []string
16
+
wantRefLinks []models.ReferenceLink
17
+
}{
18
+
{
19
+
name: "normal link",
20
+
source: `[link](http://127.0.0.1:3000/alice.pds.tngl.boltless.dev/coolproj/issues/1)`,
21
+
wantHandles: make([]string, 0),
22
+
wantRefLinks: []models.ReferenceLink{
23
+
{Handle: "alice.pds.tngl.boltless.dev", Repo: "coolproj", Kind: models.RefKindIssue, SubjectId: 1, CommentId: nil},
24
+
},
25
+
},
26
+
{
27
+
name: "commonmark style autolink",
28
+
source: `<http://127.0.0.1:3000/alice.pds.tngl.boltless.dev/coolproj/issues/1>`,
29
+
wantHandles: make([]string, 0),
30
+
wantRefLinks: []models.ReferenceLink{
31
+
{Handle: "alice.pds.tngl.boltless.dev", Repo: "coolproj", Kind: models.RefKindIssue, SubjectId: 1, CommentId: nil},
32
+
},
33
+
},
34
+
}
35
+
for _, tt := range tests {
36
+
t.Run(tt.name, func(t *testing.T) {
37
+
handles, refLinks := markup.FindReferences("http://127.0.0.1:3000", tt.source)
38
+
assert.ElementsMatch(t, tt.wantHandles, handles)
39
+
assert.ElementsMatch(t, tt.wantRefLinks, refLinks)
40
+
})
41
+
}
42
+
}
+6
-10
appview/pulls/pulls.go
+6
-10
appview/pulls/pulls.go
···
24
"tangled.org/core/appview/pages"
25
"tangled.org/core/appview/pages/markup"
26
"tangled.org/core/appview/pages/repoinfo"
27
"tangled.org/core/appview/reporesolver"
28
"tangled.org/core/appview/validator"
29
"tangled.org/core/appview/xrpcclient"
···
46
repoResolver *reporesolver.RepoResolver
47
pages *pages.Pages
48
idResolver *idresolver.Resolver
49
db *db.DB
50
config *config.Config
51
notifier notify.Notifier
···
60
repoResolver *reporesolver.RepoResolver,
61
pages *pages.Pages,
62
resolver *idresolver.Resolver,
63
db *db.DB,
64
config *config.Config,
65
notifier notify.Notifier,
···
73
repoResolver: repoResolver,
74
pages: pages,
75
idResolver: resolver,
76
db: db,
77
config: config,
78
notifier: notifier,
···
678
}
679
680
func (s *Pulls) PullComment(w http.ResponseWriter, r *http.Request) {
681
-
l := s.logger.With("handler", "PullComment")
682
user := s.oauth.GetUser(r)
683
f, err := s.repoResolver.Resolve(r)
684
if err != nil {
···
716
s.pages.Notice(w, "pull", "Comment body is required")
717
return
718
}
719
720
// Start a transaction
721
tx, err := s.db.BeginTx(r.Context(), nil)
···
776
return
777
}
778
779
-
rawMentions := markup.FindUserMentions(comment.Body)
780
-
idents := s.idResolver.ResolveIdents(r.Context(), rawMentions)
781
-
l.Debug("parsed mentions", "raw", rawMentions, "idents", idents)
782
-
var mentions []syntax.DID
783
-
for _, ident := range idents {
784
-
if ident != nil && !ident.Handle.IsInvalidHandle() {
785
-
mentions = append(mentions, ident.DID)
786
-
}
787
-
}
788
s.notifier.NewPullComment(r.Context(), comment, mentions)
789
790
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
···
24
"tangled.org/core/appview/pages"
25
"tangled.org/core/appview/pages/markup"
26
"tangled.org/core/appview/pages/repoinfo"
27
+
"tangled.org/core/appview/refresolver"
28
"tangled.org/core/appview/reporesolver"
29
"tangled.org/core/appview/validator"
30
"tangled.org/core/appview/xrpcclient"
···
47
repoResolver *reporesolver.RepoResolver
48
pages *pages.Pages
49
idResolver *idresolver.Resolver
50
+
refResolver *refresolver.Resolver
51
db *db.DB
52
config *config.Config
53
notifier notify.Notifier
···
62
repoResolver *reporesolver.RepoResolver,
63
pages *pages.Pages,
64
resolver *idresolver.Resolver,
65
+
refResolver *refresolver.Resolver,
66
db *db.DB,
67
config *config.Config,
68
notifier notify.Notifier,
···
76
repoResolver: repoResolver,
77
pages: pages,
78
idResolver: resolver,
79
+
refResolver: refResolver,
80
db: db,
81
config: config,
82
notifier: notifier,
···
682
}
683
684
func (s *Pulls) PullComment(w http.ResponseWriter, r *http.Request) {
685
user := s.oauth.GetUser(r)
686
f, err := s.repoResolver.Resolve(r)
687
if err != nil {
···
719
s.pages.Notice(w, "pull", "Comment body is required")
720
return
721
}
722
+
723
+
mentions, _ := s.refResolver.Resolve(r.Context(), body)
724
725
// Start a transaction
726
tx, err := s.db.BeginTx(r.Context(), nil)
···
781
return
782
}
783
784
s.notifier.NewPullComment(r.Context(), comment, mentions)
785
786
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
+65
appview/refresolver/resolver.go
+65
appview/refresolver/resolver.go
···
···
1
+
package refresolver
2
+
3
+
import (
4
+
"context"
5
+
"log/slog"
6
+
7
+
"github.com/bluesky-social/indigo/atproto/syntax"
8
+
"tangled.org/core/appview/config"
9
+
"tangled.org/core/appview/db"
10
+
"tangled.org/core/appview/models"
11
+
"tangled.org/core/appview/pages/markup"
12
+
"tangled.org/core/idresolver"
13
+
)
14
+
15
+
type Resolver struct {
16
+
config *config.Config
17
+
idResolver *idresolver.Resolver
18
+
execer db.Execer
19
+
logger *slog.Logger
20
+
}
21
+
22
+
func New(
23
+
config *config.Config,
24
+
idResolver *idresolver.Resolver,
25
+
execer db.Execer,
26
+
logger *slog.Logger,
27
+
) *Resolver {
28
+
return &Resolver{
29
+
config,
30
+
idResolver,
31
+
execer,
32
+
logger,
33
+
}
34
+
}
35
+
36
+
func (r *Resolver) Resolve(ctx context.Context, source string) ([]syntax.DID, []syntax.ATURI) {
37
+
l := r.logger.With("method", "find_references")
38
+
rawMentions, rawRefs := markup.FindReferences(r.config.Core.AppviewHost, source)
39
+
l.Debug("found possible references", "mentions", rawMentions, "refs", rawRefs)
40
+
idents := r.idResolver.ResolveIdents(ctx, rawMentions)
41
+
var mentions []syntax.DID
42
+
for _, ident := range idents {
43
+
if ident != nil && !ident.Handle.IsInvalidHandle() {
44
+
mentions = append(mentions, ident.DID)
45
+
}
46
+
}
47
+
l.Debug("found mentions", "mentions", mentions)
48
+
49
+
var resolvedRefs []models.ReferenceLink
50
+
for _, rawRef := range rawRefs {
51
+
ident, err := r.idResolver.ResolveIdent(ctx, rawRef.Handle)
52
+
if err != nil || ident == nil || ident.Handle.IsInvalidHandle() {
53
+
continue
54
+
}
55
+
rawRef.Handle = string(ident.DID)
56
+
resolvedRefs = append(resolvedRefs, rawRef)
57
+
}
58
+
aturiRefs, err := db.FindReferences(r.execer, resolvedRefs)
59
+
if err != nil {
60
+
l.Error("failed running query", "err", err)
61
+
}
62
+
l.Debug("found references", "refs", aturiRefs)
63
+
64
+
return mentions, aturiRefs
65
+
}
+2
appview/state/router.go
+2
appview/state/router.go
+5
appview/state/state.go
+5
appview/state/state.go
···
21
phnotify "tangled.org/core/appview/notify/posthog"
22
"tangled.org/core/appview/oauth"
23
"tangled.org/core/appview/pages"
24
"tangled.org/core/appview/reporesolver"
25
"tangled.org/core/appview/validator"
26
xrpcclient "tangled.org/core/appview/xrpcclient"
···
49
enforcer *rbac.Enforcer
50
pages *pages.Pages
51
idResolver *idresolver.Resolver
52
posthog posthog.Client
53
jc *jetstream.JetstreamClient
54
config *config.Config
···
97
validator := validator.New(d, res, enforcer)
98
99
repoResolver := reporesolver.New(config, enforcer, d)
100
101
wrapper := db.DbWrapper{Execer: d}
102
jc, err := jetstream.NewJetstreamClient(
···
178
enforcer,
179
pages,
180
res,
181
posthog,
182
jc,
183
config,
···
21
phnotify "tangled.org/core/appview/notify/posthog"
22
"tangled.org/core/appview/oauth"
23
"tangled.org/core/appview/pages"
24
+
"tangled.org/core/appview/refresolver"
25
"tangled.org/core/appview/reporesolver"
26
"tangled.org/core/appview/validator"
27
xrpcclient "tangled.org/core/appview/xrpcclient"
···
50
enforcer *rbac.Enforcer
51
pages *pages.Pages
52
idResolver *idresolver.Resolver
53
+
refResolver *refresolver.Resolver
54
posthog posthog.Client
55
jc *jetstream.JetstreamClient
56
config *config.Config
···
99
validator := validator.New(d, res, enforcer)
100
101
repoResolver := reporesolver.New(config, enforcer, d)
102
+
103
+
refResolver := refresolver.New(config, res, d, log.SubLogger(logger, "refResolver"))
104
105
wrapper := db.DbWrapper{Execer: d}
106
jc, err := jetstream.NewJetstreamClient(
···
182
enforcer,
183
pages,
184
res,
185
+
refResolver,
186
posthog,
187
jc,
188
config,