Implement external exchanges (#99)

* Implement external exchanges

* test: translateUrl test cases

* fix: double slash bug in translateUrl

---------

Co-authored-by: httpjamesm <github@httpjames.space>
This commit is contained in:
Matt Fellenz 2024-03-25 10:05:24 -07:00 committed by GitHub
parent 89126a7377
commit 94032f4f90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 21 deletions

View File

@ -20,7 +20,41 @@ type urlConversionRequest struct {
URL string `form:"url" binding:"required"` URL string `form:"url" binding:"required"`
} }
var stackExchangeRegex = regexp.MustCompile(`https://(.+).stackexchange.com/questions/`) var coreRegex = regexp.MustCompile(`(?:https?://)?(?:www\.)?([^/]+)(/(?:questions|q|a)/.+)`)
// Will return `nil` if `rawUrl` is invalid.
func translateUrl(rawUrl string) string {
coreMatches := coreRegex.FindStringSubmatch(rawUrl)
if coreMatches == nil {
return ""
}
domain := coreMatches[1]
rest := coreMatches[2]
exchange := ""
if domain == "stackoverflow.com" {
// No exchange parameter needed.
} else if sub, found := strings.CutSuffix(domain, ".stackexchange.com"); found {
if sub == "" {
return ""
} else if strings.Contains(sub, ".") {
// Anything containing dots is interpreted as a full domain, so we use the correct full domain.
exchange = domain
} else {
exchange = sub
}
} else {
exchange = domain
}
// Ensure we properly format the return string to avoid double slashes
if exchange == "" {
return rest
} else {
return fmt.Sprintf("/exchange/%s%s", exchange, rest)
}
}
func PostHome(c *gin.Context) { func PostHome(c *gin.Context) {
body := urlConversionRequest{} body := urlConversionRequest{}
@ -33,16 +67,9 @@ func PostHome(c *gin.Context) {
return return
} }
soLink := body.URL translated := translateUrl(body.URL)
// remove the www. if translated == "" {
soLink = strings.ReplaceAll(soLink, "www.", "")
// validate URL
isStackOverflow := strings.HasPrefix(soLink, "https://stackoverflow.com/questions/")
isShortenedStackOverflow := strings.HasPrefix(soLink, "https://stackoverflow.com/a/") || strings.HasPrefix(soLink, "https://stackoverflow.com/q/")
isStackExchange := stackExchangeRegex.MatchString(soLink)
if !isStackExchange && !isStackOverflow && !isShortenedStackOverflow {
c.HTML(400, "home.html", gin.H{ c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid stack overflow/exchange URL", "errorMessage": "Invalid stack overflow/exchange URL",
"theme": c.MustGet("theme").(string), "theme": c.MustGet("theme").(string),
@ -50,14 +77,5 @@ func PostHome(c *gin.Context) {
return return
} }
// if stack overflow, trim https://stackoverflow.com c.Redirect(302, translated)
if isStackOverflow || isShortenedStackOverflow {
c.Redirect(302, strings.TrimPrefix(soLink, "https://stackoverflow.com"))
return
}
// if stack exchange, extract the subdomain
sub := stackExchangeRegex.FindStringSubmatch(soLink)[1]
c.Redirect(302, fmt.Sprintf("/exchange/%s/%s", sub, strings.TrimPrefix(soLink, fmt.Sprintf("https://%s.stackexchange.com", sub))))
} }

32
src/routes/home_test.go Normal file
View File

@ -0,0 +1,32 @@
package routes
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTranslateUrl(t *testing.T) {
assert := assert.New(t)
// Test with a Valid StackOverflow URL
assert.Equal("/questions/example-question", translateUrl("https://stackoverflow.com/questions/example-question"), "StackOverflow URL should not be modified")
// Test with Complex Subdomain
assert.Equal("/exchange/meta.math.stackexchange.com/q/example-question", translateUrl("https://meta.math.stackexchange.com/q/example-question"), "Complex StackExchange subdomain should be used as full exchange")
// Test with Non-StackExchange Domain
assert.Equal("/exchange/example.com/questions/example-question", translateUrl("https://example.com/questions/example-question"), "Non-StackExchange domain should be detected as exchange")
// Test with Invalid URL
assert.Equal("", translateUrl("This is not a URL"), "Invalid URL should return an empty string")
// Test with Empty String
assert.Equal("", translateUrl(""), "Empty string should return an empty string")
// Test with Missing Path
assert.Equal("", translateUrl("https://stackoverflow.com"), "URL without path should return an empty string")
// Test with Valid URL but Root Domain for StackExchange
assert.Equal("", translateUrl("https://stackexchange.com"), "Root StackExchange domain without subdomain should return an empty string")
}

View File

@ -47,7 +47,9 @@ func ViewQuestion(c *gin.Context) {
domain := "stackoverflow.com" domain := "stackoverflow.com"
if params.Sub != "" { if strings.Contains(params.Sub, ".") {
domain = params.Sub
} else if params.Sub != "" {
domain = fmt.Sprintf("%s.stackexchange.com", params.Sub) domain = fmt.Sprintf("%s.stackexchange.com", params.Sub)
} }