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:
parent
89126a7377
commit
94032f4f90
@ -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
32
src/routes/home_test.go
Normal 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")
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user