diff --git a/src/middleware/ratelimit.go b/src/middleware/ratelimit.go index d83c901..cfedfb2 100644 --- a/src/middleware/ratelimit.go +++ b/src/middleware/ratelimit.go @@ -2,6 +2,7 @@ package middleware import ( "anonymousoverflow/config" + "anonymousoverflow/src/utils" "os" "strings" "sync" @@ -45,9 +46,8 @@ func Ratelimit() gin.HandlerFunc { // if they exceed 30 requests in 1 minute, return a 429 if val.(int) > 30 { - c.HTML(429, "home.html", gin.H{ + utils.Render(c, 429, "home", gin.H{ "errorMessage": "You have exceeded the request limit. Please try again in a minute.", - "version": config.Version, }) c.Abort() return diff --git a/src/routes/home.go b/src/routes/home.go index 78202e1..bc514c8 100644 --- a/src/routes/home.go +++ b/src/routes/home.go @@ -1,7 +1,6 @@ package routes import ( - "anonymousoverflow/config" "anonymousoverflow/src/utils" "fmt" "regexp" @@ -11,11 +10,7 @@ import ( ) func GetHome(c *gin.Context) { - theme := utils.GetThemeFromEnv() - c.HTML(200, "home.html", gin.H{ - "version": config.Version, - "theme": theme, - }) + utils.Render(c, 200, "home", nil) } type urlConversionRequest struct { @@ -62,7 +57,7 @@ func PostHome(c *gin.Context) { body := urlConversionRequest{} if err := c.ShouldBind(&body); err != nil { - c.HTML(400, "home.html", gin.H{ + utils.Render(c, 400, "home", gin.H{ "errorMessage": "Invalid request body", }) return @@ -71,10 +66,8 @@ func PostHome(c *gin.Context) { translated := translateUrl(body.URL) if translated == "" { - theme := utils.GetThemeFromEnv() - c.HTML(400, "home.html", gin.H{ + utils.Render(c, 400, "home", gin.H{ "errorMessage": "Invalid stack overflow/exchange URL", - "theme": theme, }) return } diff --git a/src/routes/image.go b/src/routes/image.go index 252b12d..eb2023b 100644 --- a/src/routes/image.go +++ b/src/routes/image.go @@ -2,12 +2,12 @@ package routes import ( "anonymousoverflow/src/types" + "anonymousoverflow/src/utils" "fmt" "os" "time" "github.com/gin-gonic/gin" - "github.com/go-resty/resty/v2" "github.com/golang-jwt/jwt/v4" ) @@ -51,33 +51,15 @@ func GetImage(c *gin.Context) { } // download the image - var resp *resty.Response = nil - client := resty.New() - - if flareurl := os.Getenv("FLARERESOLVER"); flareurl == "" { - resp, err = client.R().Get(claims.ImageURL) - } else { - client.R().SetHeader("Content-Type", "application/json") - client.R().SetBody(struct { - Cmd string `json:"cmd"` - Url string `json:"url"` - MaxTimeout int `json:"maxTimeout"` - }{ - Cmd: "request.get", - Url: claims.ImageURL, - MaxTimeout: 60000, - }) - resp, err = client.R().Post(claims.ImageURL) - } - + body, _, headers, err := utils.GET(claims.ImageURL) if err != nil { c.AbortWithStatus(500) return } // set the content type - c.Header("Content-Type", resp.Header().Get("Content-Type")) + c.Header("Content-Type", headers.Get("Content-Type")) // write the image to the response - c.Writer.Write(resp.Body()) + c.Writer.Write(body) } diff --git a/src/routes/options.go b/src/routes/options.go index c0c8718..22edc4d 100644 --- a/src/routes/options.go +++ b/src/routes/options.go @@ -1,7 +1,6 @@ package routes import ( - "anonymousoverflow/config" "anonymousoverflow/src/utils" "fmt" @@ -14,16 +13,16 @@ func ChangeOptions(c *gin.Context) { switch name { case "images": text := "disabled" + if c.MustGet("disable_images").(bool) { text = "enabled" } + c.SetCookie("disable_images", fmt.Sprintf("%t", !c.MustGet("disable_images").(bool)), 60*60*24*365*10, "/", "", false, true) - theme := utils.GetThemeFromEnv() - c.HTML(200, "home.html", gin.H{ + utils.Render(c, 200, "home", gin.H{ "successMessage": "Images are now " + text, - "version": config.Version, - "theme": theme, }) + default: c.String(400, "400 Bad Request") } diff --git a/src/routes/question.go b/src/routes/question.go index e921215..f5791fb 100644 --- a/src/routes/question.go +++ b/src/routes/question.go @@ -3,6 +3,7 @@ package routes import ( "anonymousoverflow/config" "anonymousoverflow/src/utils" + "bytes" "fmt" "html" "html/template" @@ -15,7 +16,6 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gin-gonic/gin" - "github.com/go-resty/resty/v2" ) var codeBlockRegex = regexp.MustCompile(`(?s)
(.+?)<\/code><\/pre>`)
@@ -32,7 +32,7 @@ func ViewQuestion(c *gin.Context) {
 
 	questionId := c.Param("id")
 	if _, err := strconv.Atoi(questionId); err != nil {
-		c.HTML(400, "home.html", gin.H{
+		utils.Render(c, 400, "home", gin.H{
 			"errorMessage": "Invalid question ID",
 			"version":      config.Version,
 		})
@@ -53,44 +53,37 @@ func ViewQuestion(c *gin.Context) {
 	}
 
 	soLink := fmt.Sprintf("https://%s/questions/%s/%s?answertab=%s", domain, questionId, params.QuestionTitle, params.SoSortValue)
+	body, code, _, err := utils.GET(soLink)
 
-	resp, err := fetchQuestionData(soLink)
-
-	if resp.StatusCode() != 200 {
-		c.HTML(500, "home.html", gin.H{
-			"errorMessage": fmt.Sprintf("Received a non-OK status code %d", resp.StatusCode()),
-			"version":      config.Version,
+	if code != 200 {
+		utils.Render(c, 500, "home", gin.H{
+			"errorMessage": fmt.Sprintf("Received a non-OK status code %d", code),
 		})
 		return
 	}
 
-	respBody := resp.String()
-
-	respBodyReader := strings.NewReader(respBody)
+	respBodyReader := bytes.NewReader(body)
 
 	doc, err := goquery.NewDocumentFromReader(respBodyReader)
 	if err != nil {
-		c.HTML(500, "home.html", gin.H{
+		utils.Render(c, 500, "home", gin.H{
 			"errorMessage": "Unable to parse question data",
-			"version":      config.Version,
 		})
 		return
 	}
 
 	newFilteredQuestion, err := extractQuestionData(doc, domain)
 	if err != nil {
-		c.HTML(500, "home.html", gin.H{
+		utils.Render(c, 500, "home", gin.H{
 			"errorMessage": "Failed to extract question data",
-			"version":      config.Version,
 		})
 		return
 	}
 
 	answers, err := extractAnswersData(doc, domain)
 	if err != nil {
-		c.HTML(500, "home.html", gin.H{
+		utils.Render(c, 500, "home", gin.H{
 			"errorMessage": "Failed to extract answer data",
-			"version":      config.Version,
 		})
 		return
 	}
@@ -101,18 +94,14 @@ func ViewQuestion(c *gin.Context) {
 		imagePolicy = "'self'"
 	}
 
-	theme := utils.GetThemeFromEnv()
-
-	c.HTML(200, "question.html", gin.H{
+	utils.Render(c, 200, "question", gin.H{
 		"question":    newFilteredQuestion,
 		"answers":     answers,
 		"imagePolicy": imagePolicy,
 		"currentUrl":  fmt.Sprintf("%s%s", os.Getenv("APP_URL"), c.Request.URL.Path),
 		"sortValue":   params.SoSortValue,
 		"domain":      domain,
-		"theme":       theme,
 	})
-
 }
 
 type viewQuestionInputs struct {
@@ -127,9 +116,8 @@ func parseAndValidateParameters(c *gin.Context) (inputs viewQuestionInputs, err
 
 	questionId := c.Param("id")
 	if _, err = strconv.Atoi(questionId); err != nil {
-		c.HTML(400, "home.html", gin.H{
+		utils.Render(c, 400, "home", gin.H{
 			"errorMessage": "Invalid question ID",
-			"version":      config.Version,
 		})
 		return
 	}
@@ -155,29 +143,6 @@ func parseAndValidateParameters(c *gin.Context) (inputs viewQuestionInputs, err
 	return
 }
 
-// fetchQuestionData sends the request to StackOverflow.
-func fetchQuestionData(soLink string) (*resty.Response, error) {
-	flareurl := ""
-	client := resty.New()
-
-	if flareurl = os.Getenv("FLARERESOLVER"); flareurl == "" {
-		return client.R().Get(soLink)
-	}
-
-	client.R().SetHeader("Content-Type", "application/json")
-	client.R().SetBody(struct {
-		Cmd        string `json:"cmd"`
-		Url        string `json:"url"`
-		MaxTimeout int    `json:"maxTimeout"`
-	}{
-		Cmd:        "request.get",
-		Url:        soLink,
-		MaxTimeout: 60000,
-	})
-
-	return client.R().Post(flareurl)
-}
-
 // extractQuestionData parses the HTML document and extracts question data.
 func extractQuestionData(doc *goquery.Document, domain string) (question types.FilteredQuestion, err error) {
 	// Extract the question title.
diff --git a/src/routes/shortened.go b/src/routes/shortened.go
index 494b5bc..7f37299 100644
--- a/src/routes/shortened.go
+++ b/src/routes/shortened.go
@@ -1,6 +1,7 @@
 package routes
 
 import (
+	"anonymousoverflow/src/utils"
 	"fmt"
 	"net/http"
 	"os"
@@ -31,14 +32,14 @@ func RedirectShortenedOverflowURL(c *gin.Context) {
 	}
 	resp, err := client.R().Get(fmt.Sprintf("https://%s/a/%s/%s", domain, id, answerId))
 	if err != nil {
-		c.HTML(400, "home.html", gin.H{
+		utils.Render(c, 400, "home", gin.H{
 			"errorMessage": "Unable to fetch stack overflow URL",
 		})
 		return
 	}
 
 	if resp.StatusCode() != 302 {
-		c.HTML(400, "home.html", gin.H{
+		utils.Render(c, 400, "home", gin.H{
 			"errorMessage": fmt.Sprintf("Unexpected HTTP status from origin: %d", resp.StatusCode()),
 		})
 		return
diff --git a/src/utils/render.go b/src/utils/render.go
new file mode 100644
index 0000000..efdd13d
--- /dev/null
+++ b/src/utils/render.go
@@ -0,0 +1,24 @@
+package utils
+
+import (
+	"anonymousoverflow/config"
+
+	"github.com/gin-gonic/gin"
+)
+
+func Render(c *gin.Context, code int, temp string, _data ...gin.H) {
+	var data gin.H = nil
+
+	if len(_data) > 0 {
+		data = _data[0]
+	}
+
+	if data == nil {
+		data = gin.H{}
+	}
+
+	data["version"] = config.Version
+	data["theme"] = GetThemeFromEnv()
+
+	c.HTML(code, temp+".html", data)
+}
diff --git a/src/utils/request.go b/src/utils/request.go
new file mode 100644
index 0000000..e08f760
--- /dev/null
+++ b/src/utils/request.go
@@ -0,0 +1,63 @@
+package utils
+
+import (
+	"net/http"
+	"os"
+
+	"github.com/go-resty/resty/v2"
+)
+
+// https://github.com/FlareSolverr/FlareSolverr#-requestget
+type request struct {
+	Cmd        string `json:"cmd"`
+	Url        string `json:"url"`
+	MaxTimeout int    `json:"maxTimeout"`
+}
+
+type solution struct {
+	Status   int               `json:"status"`
+	Response []byte            `json:"response"`
+	Headers  map[string]string `json:"headers"`
+}
+
+type response struct {
+	Solution solution `json:"solution"`
+}
+
+func GET(url string) ([]byte, int, http.Header, error) {
+	var (
+		client *resty.Client   = resty.New()
+		res    *resty.Response = nil
+		frurl  string          = ""
+		err    error
+	)
+
+	if frurl = os.Getenv("FLARERESOLVER"); frurl == "" {
+		if res, err := client.R().Get(url); err != nil {
+			return nil, 0, nil, err
+		} else {
+			return res.Body(), res.StatusCode(), res.Header(), nil
+		}
+	}
+
+	client.R().SetHeader("Content-Type", "application/json")
+	client.R().SetBody(request{
+		Cmd:        "request.get",
+		Url:        url,
+		MaxTimeout: 60000,
+	})
+	client.R().SetResult(&response{})
+
+	if res, err = client.R().Post(url); err != nil {
+		return nil, 0, nil, err
+	}
+
+	response := res.Result().(*response)
+	headers := http.Header{}
+
+	for k, v := range response.Solution.Headers {
+		headers.Add(k, v)
+	}
+
+	return response.Solution.Response, response.Solution.Status, headers, nil
+}