From 26ff328ff3f896d1efde1a5f8f10afbba8c53f76 Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Mon, 11 May 2026 13:51:49 +0900 Subject: [PATCH] wip: appview/urlmaker Signed-off-by: Seongmin Lee --- appview/urlmaker/urlmaker.go | 139 +++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 appview/urlmaker/urlmaker.go diff --git a/appview/urlmaker/urlmaker.go b/appview/urlmaker/urlmaker.go new file mode 100644 index 00000000..a4b379b1 --- /dev/null +++ b/appview/urlmaker/urlmaker.go @@ -0,0 +1,139 @@ +package urlmaker + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "path" + "strconv" + + "github.com/bluesky-social/indigo/atproto/identity" + "github.com/bluesky-social/indigo/atproto/syntax" + "github.com/redis/go-redis/v9" + "tangled.org/core/api/tangled" + "tangled.org/core/appview/config" + "tangled.org/core/appview/db" + "tangled.org/core/appview/models" + "tangled.org/core/appview/pagination" + "tangled.org/core/orm" +) + +type UrlMaker struct { + appviewHost string + db *sql.DB + dir identity.Directory + rdb *redis.Client +} + +func NewUrlMaker(cfg *config.Config, db *sql.DB, dir identity.Directory, rdb *redis.Client) *UrlMaker { + return &UrlMaker{ + appviewHost: cfg.Core.AppviewHost, + db: db, + dir: dir, + rdb: rdb, + } +} + +type ctxKeyDepth struct{} + +const ( + AppViewUrlByAtUri = "appview_url:aturi:%s" +) + +func (m *UrlMaker) MakeUrl(ctx context.Context, aturi syntax.ATURI) (string, error) { + if m.rdb != nil { + cachedUrl, err := m.rdb.Get(ctx, fmt.Sprintf(AppViewUrlByAtUri, aturi)).Result() + if err == nil { + return cachedUrl, nil + } + } + + switch aturi.Collection() { + case tangled.FeedCommentNSID, tangled.RepoIssueCommentNSID, tangled.RepoPullCommentNSID: + // /{subject}#comment-{rkey} + comment, err := db.GetComment(m.db, orm.FilterEq("at_uri", aturi)) + if err != nil { + return "", fmt.Errorf("db: %w", err) + } + + subjectAt := syntax.ATURI(comment.Subject.Uri) + + subjectUrl, err := m.MakeUrl(ctx, subjectAt) + if err != nil { + return "", fmt.Errorf("subject: %w", err) + } + + u, err := url.Parse(subjectUrl) + if err != nil { + return "", fmt.Errorf("url: %w", err) + } + + if subjectAt.Collection() == tangled.RepoPullNSID && comment.PullRoundIdx != nil { + u = u.JoinPath("round", strconv.Itoa(*comment.PullRoundIdx)) + } + + u.Fragment = fmt.Sprintf("comment-%s", comment.Rkey) + return u.Path, nil + + case tangled.RepoNSID: + // /{owner}/{repo} + repo, err := db.GetRepoByAtUri(m.db, aturi.String()) + if err != nil { + return "", fmt.Errorf("db: %w", err) + } + id, err := m.dir.LookupDID(ctx, syntax.DID(repo.Did)) + if err != nil { + return "", fmt.Errorf("dir: %w", err) + } + return path.Join("/", id.Handle.String(), repo.Name), nil + + case tangled.RepoIssueNSID: + // /{owner}/{repo}/issues/{issue} + issue, err := func(aturi syntax.ATURI) (*models.Issue, error) { + issues, err := db.GetIssuesPaginated(m.db, pagination.Page{Limit: 1}) + if err != nil { + return nil, err + } + if len(issues) != 1 { + return nil, sql.ErrNoRows + } + return &issues[0], nil + }(aturi) + if err != nil { + return "", fmt.Errorf("db: %w", err) + } + + repoUrl, err := m.MakeUrl(ctx, issue.RepoAt) + if err != nil { + return "", fmt.Errorf("subject: %w", err) + } + + return path.Join(repoUrl, "issue", strconv.Itoa(issue.IssueId)), nil + + case tangled.RepoPullNSID: + // /{owner}/{repo}/pulls/{pull} + // NOTE: won't include the round + pull, err := db.GetPull(m.db, orm.FilterEq("at_uri", aturi)) + if err != nil { + return "", fmt.Errorf("db: %w", err) + } + + repoUrl, err := m.MakeUrl(ctx, pull.RepoAt) + if err != nil { + return "", fmt.Errorf("subject: %w", err) + } + + return path.Join(repoUrl, "pull", strconv.Itoa(pull.PullId)), nil + + case tangled.StringNSID: + // /strings/{owner}/{rkey} + str, err := db.GetString(m.db, orm.FilterEq("at_uri", aturi)) + if err != nil { + return "", fmt.Errorf("db: %w", err) + } + + return path.Join("/strings", str.Rkey.String()), nil + } + return "", fmt.Errorf("unknown NSID '%s'", aturi.Collection()) +} -- knotmirror 0.0.1