Monorepo for Tangled
0

Configure Feed

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

wip: appview/urlmaker

Signed-off-by: Seongmin Lee <git@boltless.me>

Seongmin Lee (May 11, 2026, 1:52 PM +0900) 26ff328f 1a459399

+139
+139
appview/urlmaker/urlmaker.go
··· 1 + package urlmaker 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + "net/url" 8 + "path" 9 + "strconv" 10 + 11 + "github.com/bluesky-social/indigo/atproto/identity" 12 + "github.com/bluesky-social/indigo/atproto/syntax" 13 + "github.com/redis/go-redis/v9" 14 + "tangled.org/core/api/tangled" 15 + "tangled.org/core/appview/config" 16 + "tangled.org/core/appview/db" 17 + "tangled.org/core/appview/models" 18 + "tangled.org/core/appview/pagination" 19 + "tangled.org/core/orm" 20 + ) 21 + 22 + type UrlMaker struct { 23 + appviewHost string 24 + db *sql.DB 25 + dir identity.Directory 26 + rdb *redis.Client 27 + } 28 + 29 + func NewUrlMaker(cfg *config.Config, db *sql.DB, dir identity.Directory, rdb *redis.Client) *UrlMaker { 30 + return &UrlMaker{ 31 + appviewHost: cfg.Core.AppviewHost, 32 + db: db, 33 + dir: dir, 34 + rdb: rdb, 35 + } 36 + } 37 + 38 + type ctxKeyDepth struct{} 39 + 40 + const ( 41 + AppViewUrlByAtUri = "appview_url:aturi:%s" 42 + ) 43 + 44 + func (m *UrlMaker) MakeUrl(ctx context.Context, aturi syntax.ATURI) (string, error) { 45 + if m.rdb != nil { 46 + cachedUrl, err := m.rdb.Get(ctx, fmt.Sprintf(AppViewUrlByAtUri, aturi)).Result() 47 + if err == nil { 48 + return cachedUrl, nil 49 + } 50 + } 51 + 52 + switch aturi.Collection() { 53 + case tangled.FeedCommentNSID, tangled.RepoIssueCommentNSID, tangled.RepoPullCommentNSID: 54 + // /{subject}#comment-{rkey} 55 + comment, err := db.GetComment(m.db, orm.FilterEq("at_uri", aturi)) 56 + if err != nil { 57 + return "", fmt.Errorf("db: %w", err) 58 + } 59 + 60 + subjectAt := syntax.ATURI(comment.Subject.Uri) 61 + 62 + subjectUrl, err := m.MakeUrl(ctx, subjectAt) 63 + if err != nil { 64 + return "", fmt.Errorf("subject: %w", err) 65 + } 66 + 67 + u, err := url.Parse(subjectUrl) 68 + if err != nil { 69 + return "", fmt.Errorf("url: %w", err) 70 + } 71 + 72 + if subjectAt.Collection() == tangled.RepoPullNSID && comment.PullRoundIdx != nil { 73 + u = u.JoinPath("round", strconv.Itoa(*comment.PullRoundIdx)) 74 + } 75 + 76 + u.Fragment = fmt.Sprintf("comment-%s", comment.Rkey) 77 + return u.Path, nil 78 + 79 + case tangled.RepoNSID: 80 + // /{owner}/{repo} 81 + repo, err := db.GetRepoByAtUri(m.db, aturi.String()) 82 + if err != nil { 83 + return "", fmt.Errorf("db: %w", err) 84 + } 85 + id, err := m.dir.LookupDID(ctx, syntax.DID(repo.Did)) 86 + if err != nil { 87 + return "", fmt.Errorf("dir: %w", err) 88 + } 89 + return path.Join("/", id.Handle.String(), repo.Name), nil 90 + 91 + case tangled.RepoIssueNSID: 92 + // /{owner}/{repo}/issues/{issue} 93 + issue, err := func(aturi syntax.ATURI) (*models.Issue, error) { 94 + issues, err := db.GetIssuesPaginated(m.db, pagination.Page{Limit: 1}) 95 + if err != nil { 96 + return nil, err 97 + } 98 + if len(issues) != 1 { 99 + return nil, sql.ErrNoRows 100 + } 101 + return &issues[0], nil 102 + }(aturi) 103 + if err != nil { 104 + return "", fmt.Errorf("db: %w", err) 105 + } 106 + 107 + repoUrl, err := m.MakeUrl(ctx, issue.RepoAt) 108 + if err != nil { 109 + return "", fmt.Errorf("subject: %w", err) 110 + } 111 + 112 + return path.Join(repoUrl, "issue", strconv.Itoa(issue.IssueId)), nil 113 + 114 + case tangled.RepoPullNSID: 115 + // /{owner}/{repo}/pulls/{pull} 116 + // NOTE: won't include the round 117 + pull, err := db.GetPull(m.db, orm.FilterEq("at_uri", aturi)) 118 + if err != nil { 119 + return "", fmt.Errorf("db: %w", err) 120 + } 121 + 122 + repoUrl, err := m.MakeUrl(ctx, pull.RepoAt) 123 + if err != nil { 124 + return "", fmt.Errorf("subject: %w", err) 125 + } 126 + 127 + return path.Join(repoUrl, "pull", strconv.Itoa(pull.PullId)), nil 128 + 129 + case tangled.StringNSID: 130 + // /strings/{owner}/{rkey} 131 + str, err := db.GetString(m.db, orm.FilterEq("at_uri", aturi)) 132 + if err != nil { 133 + return "", fmt.Errorf("db: %w", err) 134 + } 135 + 136 + return path.Join("/strings", str.Rkey.String()), nil 137 + } 138 + return "", fmt.Errorf("unknown NSID '%s'", aturi.Collection()) 139 + }