Monorepo for Tangled tangled.org

appview/pages/markup: smart commit autolink renderer #1003

merged opened by boltless.me targeting master from sl/uupvwnkzxzom
Labels

None yet.

assignee

None yet.

Participants 3
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3mcufej76qu22
+43 -37
Interdiff #1 โ†’ #2
+1 -1
appview/pages/markup/extension/tangledlink.go
··· 78 78 79 79 func (t *tangledLinkTransformer) parseLinkCommitSha(raw string) string { 80 80 u, err := url.Parse(raw) 81 - if err != nil || u.Host != "tangled.org" { 81 + if err != nil || u.Host != t.host { 82 82 return "" 83 83 } 84 84
+4 -3
appview/pages/markup/markdown.go
··· 46 46 CamoSecret string 47 47 repoinfo.RepoInfo 48 48 IsDev bool 49 + Hostname string 49 50 RendererType RendererType 50 51 Sanitizer Sanitizer 51 52 Files fs.FS 52 53 } 53 54 54 - func NewMarkdown() goldmark.Markdown { 55 + func NewMarkdown(hostname string) goldmark.Markdown { 55 56 md := goldmark.New( 56 57 goldmark.WithExtensions( 57 58 extension.GFM, ··· 67 68 ), 68 69 callout.CalloutExtention, 69 70 textension.AtExt, 70 - textension.NewTangledLinkExt("tangled.org"), 71 + textension.NewTangledLinkExt(hostname), 71 72 emoji.Emoji, 72 73 ), 73 74 goldmark.WithParserOptions( ··· 79 80 } 80 81 81 82 func (rctx *RenderContext) RenderMarkdown(source string) string { 82 - return rctx.RenderMarkdownWith(source, NewMarkdown()) 83 + return rctx.RenderMarkdownWith(source, NewMarkdown(rctx.Hostname)) 83 84 } 84 85 85 86 func (rctx *RenderContext) RenderMarkdownWith(source string, md goldmark.Markdown) string {
+11
appview/config/config.go
··· 25 25 TmpAltAppPassword string `env:"ALT_APP_PASSWORD"` 26 26 } 27 27 28 + func (c *CoreConfig) UseTLS() bool { 29 + return !c.Dev 30 + } 31 + 32 + func (c *CoreConfig) Url() string { 33 + if c.UseTLS() { 34 + return "https://" + c.AppviewHost 35 + } 36 + return "http://" + c.AppviewHost 37 + } 38 + 28 39 type OAuthConfig struct { 29 40 ClientSecret string `env:"CLIENT_SECRET"` 30 41 ClientKid string `env:"CLIENT_KID"`
+2 -5
appview/oauth/oauth.go
··· 37 37 38 38 func New(config *config.Config, ph posthog.Client, db *db.DB, enforcer *rbac.Enforcer, res *idresolver.Resolver, logger *slog.Logger) (*OAuth, error) { 39 39 var oauthConfig oauth.ClientConfig 40 - var clientUri string 40 + clientUri := config.Core.Url() 41 + callbackUri := clientUri + "/oauth/callback" 41 42 if config.Core.Dev { 42 - clientUri = "http://127.0.0.1:3000" 43 - callbackUri := clientUri + "/oauth/callback" 44 43 oauthConfig = oauth.NewLocalhostConfig(callbackUri, []string{"atproto", "transition:generic"}) 45 44 } else { 46 - clientUri = config.Core.AppviewHost 47 45 clientId := fmt.Sprintf("%s/oauth/client-metadata.json", clientUri) 48 - callbackUri := clientUri + "/oauth/callback" 49 46 oauthConfig = oauth.NewPublicConfig(clientId, callbackUri, []string{"atproto", "transition:generic"}) 50 47 } 51 48
+2 -2
appview/pages/markup/markdown_test.go
··· 50 50 51 51 for _, tt := range tests { 52 52 t.Run(tt.name, func(t *testing.T) { 53 - md := NewMarkdown() 53 + md := NewMarkdown("tangled.org") 54 54 55 55 var buf bytes.Buffer 56 56 if err := md.Convert([]byte(tt.markdown), &buf); err != nil { ··· 105 105 106 106 for _, tt := range tests { 107 107 t.Run(tt.name, func(t *testing.T) { 108 - md := NewMarkdown() 108 + md := NewMarkdown("tangled.org") 109 109 110 110 var buf bytes.Buffer 111 111 if err := md.Convert([]byte(tt.markdown), &buf); err != nil {
+4 -7
appview/pages/markup/reference_link.go
··· 18 18 // like issues, PRs, comments or even @-mentions 19 19 // This funciton doesn't actually check for the existence of records in the DB 20 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) { 21 + func FindReferences(host string, source string) ([]string, []models.ReferenceLink) { 22 22 var ( 23 23 refLinkSet = make(map[models.ReferenceLink]struct{}) 24 24 mentionsSet = make(map[string]struct{}) 25 - md = NewMarkdown() 25 + md = NewMarkdown(host) 26 26 sourceBytes = []byte(source) 27 27 root = md.Parser().Parse(text.NewReader(sourceBytes)) 28 28 ) 29 - // trim url scheme. the SSL shouldn't matter 30 - baseUrl = strings.TrimPrefix(baseUrl, "https://") 31 - baseUrl = strings.TrimPrefix(baseUrl, "http://") 32 29 33 30 ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 34 31 if !entering { ··· 41 38 return ast.WalkSkipChildren, nil 42 39 case ast.KindLink: 43 40 dest := string(n.(*ast.Link).Destination) 44 - ref := parseTangledLink(baseUrl, dest) 41 + ref := parseTangledLink(host, dest) 45 42 if ref != nil { 46 43 refLinkSet[*ref] = struct{}{} 47 44 } ··· 50 47 an := n.(*ast.AutoLink) 51 48 if an.AutoLinkType == ast.AutoLinkURL { 52 49 dest := string(an.URL(sourceBytes)) 53 - ref := parseTangledLink(baseUrl, dest) 50 + ref := parseTangledLink(host, dest) 54 51 if ref != nil { 55 52 refLinkSet[*ref] = struct{}{} 56 53 }
+1
appview/pages/pages.go
··· 53 53 // initialized with safe defaults, can be overriden per use 54 54 rctx := &markup.RenderContext{ 55 55 IsDev: config.Core.Dev, 56 + Hostname: config.Core.AppviewHost, 56 57 CamoUrl: config.Camo.Host, 57 58 CamoSecret: config.Camo.SharedSecret, 58 59 Sanitizer: markup.NewSanitizer(),
+1 -1
appview/repo/archive.go
··· 66 66 if link := resp.Header.Get("Link"); link != "" { 67 67 if resolvedRef, err := extractImmutableLink(link); err == nil { 68 68 newLink := fmt.Sprintf("<%s/%s/archive/%s.tar.gz>; rel=\"immutable\"", 69 - rp.config.Core.AppviewHost, f.DidSlashRepo(), resolvedRef) 69 + rp.config.Core.Url(), f.DidSlashRepo(), resolvedRef) 70 70 w.Header().Set("Link", newLink) 71 71 } 72 72 }
+4 -4
appview/repo/feed.go
··· 37 37 38 38 feed := &feeds.Feed{ 39 39 Title: fmt.Sprintf("activity feed for @%s", ownerSlashRepo), 40 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, ownerSlashRepo), Type: "text/html", Rel: "alternate"}, 40 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.Url(), ownerSlashRepo), Type: "text/html", Rel: "alternate"}, 41 41 Items: make([]*feeds.Item, 0), 42 42 Updated: time.UnixMilli(0), 43 43 } ··· 86 86 mainItem := &feeds.Item{ 87 87 Title: fmt.Sprintf("[PR #%d] %s", pull.PullId, pull.Title), 88 88 Description: description, 89 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, ownerSlashRepo, pull.PullId)}, 89 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.Url(), ownerSlashRepo, pull.PullId)}, 90 90 Created: pull.Created, 91 91 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 92 92 } ··· 100 100 roundItem := &feeds.Item{ 101 101 Title: fmt.Sprintf("[PR #%d] %s (round #%d)", pull.PullId, pull.Title, round.RoundNumber), 102 102 Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in @%s", owner.Handle, round.RoundNumber, pull.PullId, ownerSlashRepo), 103 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, ownerSlashRepo, pull.PullId, round.RoundNumber)}, 103 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.Url(), ownerSlashRepo, pull.PullId, round.RoundNumber)}, 104 104 Created: round.Created, 105 105 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 106 106 } ··· 124 124 return &feeds.Item{ 125 125 Title: fmt.Sprintf("[Issue #%d] %s", issue.IssueId, issue.Title), 126 126 Description: fmt.Sprintf("@%s %s issue #%d in @%s", owner.Handle, state, issue.IssueId, ownerSlashRepo), 127 - Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, ownerSlashRepo, issue.IssueId)}, 127 + Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.Url(), ownerSlashRepo, issue.IssueId)}, 128 128 Created: issue.Created, 129 129 Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)}, 130 130 }, nil
+7 -8
appview/settings/settings.go
··· 298 298 } 299 299 300 300 func (s *Settings) verifyUrl(did string, email string, code string) string { 301 - var appUrl string 302 - if s.Config.Core.Dev { 303 - appUrl = "http://" + s.Config.Core.ListenAddr 304 - } else { 305 - appUrl = s.Config.Core.AppviewHost 306 - } 307 - 308 - return fmt.Sprintf("%s/settings/emails/verify?did=%s&email=%s&code=%s", appUrl, url.QueryEscape(did), url.QueryEscape(email), url.QueryEscape(code)) 301 + return fmt.Sprintf( 302 + "%s/settings/emails/verify?did=%s&email=%s&code=%s", 303 + s.Config.Core.Url(), 304 + url.QueryEscape(did), 305 + url.QueryEscape(email), 306 + url.QueryEscape(code), 307 + ) 309 308 } 310 309 311 310 func (s *Settings) emailsVerify(w http.ResponseWriter, r *http.Request) {
+4 -4
appview/state/profile.go
··· 415 415 416 416 feed := feeds.Feed{ 417 417 Title: fmt.Sprintf("%s's timeline", author.Name), 418 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s", s.config.Core.AppviewHost, id.Handle), Type: "text/html", Rel: "alternate"}, 418 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s", s.config.Core.Url(), id.Handle), Type: "text/html", Rel: "alternate"}, 419 419 Items: make([]*feeds.Item, 0), 420 420 Updated: time.UnixMilli(0), 421 421 Author: author, ··· 483 483 func (s *State) createPullRequestItem(pull *models.Pull, owner *identity.Identity, author *feeds.Author) *feeds.Item { 484 484 return &feeds.Item{ 485 485 Title: fmt.Sprintf("%s created pull request '%s' in @%s/%s", author.Name, pull.Title, owner.Handle, pull.Repo.Name), 486 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.AppviewHost, owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"}, 486 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.Url(), owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"}, 487 487 Created: pull.Created, 488 488 Author: author, 489 489 } ··· 492 492 func (s *State) createIssueItem(issue *models.Issue, owner *identity.Identity, author *feeds.Author) *feeds.Item { 493 493 return &feeds.Item{ 494 494 Title: fmt.Sprintf("%s created issue '%s' in @%s/%s", author.Name, issue.Title, owner.Handle, issue.Repo.Name), 495 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.AppviewHost, owner.Handle, issue.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"}, 495 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.Url(), owner.Handle, issue.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"}, 496 496 Created: issue.Created, 497 497 Author: author, 498 498 } ··· 512 512 513 513 return &feeds.Item{ 514 514 Title: title, 515 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.AppviewHost, author.Name[1:], repo.Repo.Name), Type: "text/html", Rel: "alternate"}, // Remove @ prefix 515 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.Url(), author.Name[1:], repo.Repo.Name), Type: "text/html", Rel: "alternate"}, // Remove @ prefix 516 516 Created: repo.Repo.Created, 517 517 Author: author, 518 518 }, nil

History

5 rounds 11 comments
sign up or login to add to the discussion
2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
3/3 success
expand
expand 2 comments

@oppi.li makes sense. I completely forgot the indigo oauth behavior. I reverted the change.

lgtm, this bug is still present:

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org

but should be a quick fix, i can apply that on master!

pull request successfully merged
2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
expand 3 comments

when using dev, the callback URL should always be http://127.0.0.1/oauth/callback (or http://localhost), regardless of what the AppviewHost is set to. the port is ignored iirc.

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org (without the scheme).

i think BaseUrl is good naming choice. happy with that!

when using dev, the callback URL should always be http://127.0.0.1/oauth/callback (or http://localhost), regardless of what the AppviewHost is set to. the port is ignored iirc.

If someone set AppviewHost, I suspect they are testing on that host. I used to test on my own domain long ago. http://127.0.0.1:3000/oauth/callback is what was used in original code.

next, in appview/config/config.go, the default value for AppivewHost should be set to tangled.org

will do!

if you are using oauth.NewLocalhostConfig (as we do in dev), the redirect URL's host cannot be anything other than localhost or 127.0.0.1, if it is something else, you will get an error when performing the PAR request:

URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname at body.redirect_uri]"

when in dev, we should just ignore AppviewHost and use one of the predefined hosts. in the original code, the host is 127.0.0.1 which is one of the predefined hosts. the port is ignored anyway.

2 commits
expand
appview/pages/markup: smart commit autolink renderer
appview: strip scheme from CoreConfig.AppviewHost
3/3 success
expand
expand 5 comments
  • here, i think Url is a bit ambiguous to have on config, could use a better name here

rest of the changeset lgtm, will give this a test, thanks!

after some local testing, this seems to oauth, i am unable to login!

2026/01/21 06:25:06 WARN auth server request failed request=PAR statusCode=400 body="map[error:invalid_request error_description:Invalid authorization request: URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname at body.redirect_uri]"
2026/01/21 06:25:06 ERRO appview: failed to start auth handler=Login err="auth request failed: PAR request failed (HTTP 400): invalid_request"

@oppi.li I can't reproduce the oauth issue. Have you tried after setting TANGLED_APPVIEWHOST=127.0.0.1? you should remove the scheme now.

for Url() naming, would BaseUrl() be fine enough?

Also naming/consistency nit: tangledlink.go โ†’ tangled_link.go? Keeping in line with reference_link.go. Rest looks OK!

@anirudh.fi I'm matching with extension/atlink.go. Doesn't reference_link.go violates the golang conventions? yes, I'm the one who wrote both... inconsistencies all over the place ๐Ÿ™ˆ

1 commit
expand
appview/pages/markup: smart commit autolink renderer
3/3 success
expand
expand 1 comment

would be good to avoid hardcoding the appview host here!

1 commit
expand
appview/pages/markup: smart commit autolink renderer
1/3 failed, 2/3 success
expand
expand 0 comments