Get rid of github.com/go-chi/render use (#1919)
Replace go-chi/render with go-pkgz/rest for JSON responses and custom helpers for HTML/plain text responses. Key changes: - Replace render.JSON/render.Status with rest.RenderJSON and explicit w.WriteHeader() calls - Replace render.DecodeJSON with json.NewDecoder().Decode() - Add SendErrorJSON helper that sets Content-Type header before WriteHeader (required since rest.RenderJSON can't set headers after WriteHeader is called) - Add HTMLResponse and PlainTextResponse helpers Fix export double-execution in migrator.go: The original code called Export twice - once to io.Discard to check for errors, then again to actually write. This was wasteful and had a race condition risk. Now file mode buffers to memory first for atomic success/failure, while stream mode writes directly with proper error handling.
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/go-pkgz/auth/v2"
|
||||
cache "github.com/go-pkgz/lcw/v2"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
@@ -54,8 +53,7 @@ func (a *admin) deleteCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.SiteID, locator.URL, lastCommentsScope))
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, R.JSON{"id": id, "locator": locator})
|
||||
R.RenderJSON(w, R.JSON{"id": id, "locator": locator})
|
||||
}
|
||||
|
||||
// DELETE /user/{userid}?site=side-id - delete all user comments for requested userid
|
||||
@@ -69,8 +67,7 @@ func (a *admin) deleteUserCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
a.cache.Flush(cache.Flusher(siteID).Scopes(userID, siteID, lastCommentsScope))
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, R.JSON{"user_id": userID, "site_id": siteID})
|
||||
R.RenderJSON(w, R.JSON{"user_id": userID, "site_id": siteID})
|
||||
}
|
||||
|
||||
// GET /user/{userid}?site=side-id - get user info for requested userid
|
||||
@@ -84,8 +81,7 @@ func (a *admin) getUserInfoCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get user info", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, ucomments[0].User)
|
||||
R.RenderJSON(w, ucomments[0].User)
|
||||
}
|
||||
|
||||
// GET /deleteme?token=jwt - delete all user comments and details by user's request. Gets info about deleted used from provided token
|
||||
@@ -135,8 +131,7 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
a.cache.Flush(cache.Flusher(audience).Scopes(audience, claims.User.ID, lastCommentsScope))
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, R.JSON{"user_id": claims.User.ID, "site_id": claims.Audience})
|
||||
R.RenderJSON(w, R.JSON{"user_id": claims.User.ID, "site_id": claims.Audience})
|
||||
}
|
||||
|
||||
// PUT /user/{userid}?site=side-id&block=1&ttl=7d - block or unblock user
|
||||
@@ -164,7 +159,7 @@ func (a *admin) setBlockCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
a.cache.Flush(cache.Flusher(siteID).Scopes(userID, siteID, lastCommentsScope))
|
||||
render.JSON(w, r, R.JSON{"user_id": userID, "site_id": siteID, "block": blockStatus})
|
||||
R.RenderJSON(w, R.JSON{"user_id": userID, "site_id": siteID, "block": blockStatus})
|
||||
}
|
||||
|
||||
// GET /blocked?site=siteID - list blocked users
|
||||
@@ -175,7 +170,7 @@ func (a *admin) blockedUsersCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get blocked users", rest.ErrSiteNotFound)
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, users)
|
||||
R.RenderJSON(w, users)
|
||||
}
|
||||
|
||||
// PUT /readonly?site=siteID&url=post-url&ro=1 - set or reset read-only status for the post
|
||||
@@ -202,7 +197,7 @@ func (a *admin) setReadOnlyCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL, locator.SiteID))
|
||||
render.JSON(w, r, R.JSON{"locator": locator, "read-only": roStatus})
|
||||
R.RenderJSON(w, R.JSON{"locator": locator, "read-only": roStatus})
|
||||
}
|
||||
|
||||
// PUT /title/{id}?site=siteID&url=post-url - set comment PostTitle to page's title
|
||||
@@ -218,8 +213,7 @@ func (a *admin) setTitleCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("[INFO] set comment's title %s to %q", id, c.PostTitle)
|
||||
|
||||
a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL, lastCommentsScope))
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, R.JSON{"id": id, "locator": locator})
|
||||
R.RenderJSON(w, R.JSON{"id": id, "locator": locator})
|
||||
}
|
||||
|
||||
// PUT /verify?site=siteID&url=post-url&ro=1 - set or reset read-only status for the post
|
||||
@@ -233,7 +227,7 @@ func (a *admin) setVerifyCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
a.cache.Flush(cache.Flusher(siteID).Scopes(siteID, userID))
|
||||
render.JSON(w, r, R.JSON{"user": userID, "verified": verifyStatus})
|
||||
R.RenderJSON(w, R.JSON{"user": userID, "verified": verifyStatus})
|
||||
}
|
||||
|
||||
// PUT /pin/{id}?site=siteID&url=post-url&pin=1
|
||||
@@ -248,5 +242,5 @@ func (a *admin) setPinCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL))
|
||||
render.JSON(w, r, R.JSON{"id": commentID, "locator": locator, "pin": pinStatus})
|
||||
R.RenderJSON(w, R.JSON{"id": commentID, "locator": locator, "pin": pinStatus})
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
cache "github.com/go-pkgz/lcw/v2"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
R "github.com/go-pkgz/rest"
|
||||
@@ -58,8 +59,8 @@ func (m *Migrator) importCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
go m.runImport(siteID, r.URL.Query().Get("provider"), tmpfile) // import runs in background and sets busy flag for site
|
||||
|
||||
render.Status(r, http.StatusAccepted)
|
||||
render.JSON(w, r, R.JSON{"status": "import request accepted"})
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
R.RenderJSON(w, R.JSON{"status": "import request accepted"})
|
||||
}
|
||||
|
||||
// POST /import/form?secret=key&site=site-id&provider=disqus|remark|wordpress
|
||||
@@ -93,8 +94,8 @@ func (m *Migrator) importFormCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
go m.runImport(siteID, r.URL.Query().Get("provider"), tmpfile) // import runs in background and sets busy flag for site
|
||||
|
||||
render.Status(r, http.StatusAccepted)
|
||||
render.JSON(w, r, R.JSON{"status": "import request accepted"})
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
R.RenderJSON(w, R.JSON{"status": "import request accepted"})
|
||||
}
|
||||
|
||||
// GET /wait?site=site-id
|
||||
@@ -114,14 +115,13 @@ func (m *Migrator) waitCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
render.JSON(w, r, R.JSON{"status": "timeout expired", "site_id": siteID})
|
||||
w.WriteHeader(http.StatusGatewayTimeout)
|
||||
R.RenderJSON(w, R.JSON{"status": "timeout expired", "site_id": siteID})
|
||||
return
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, R.JSON{"status": "completed", "site_id": siteID})
|
||||
R.RenderJSON(w, R.JSON{"status": "completed", "site_id": siteID})
|
||||
}
|
||||
|
||||
// GET /export?site=site-id&secret=12345&?mode=file|stream
|
||||
@@ -129,23 +129,32 @@ func (m *Migrator) waitCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
func (m *Migrator) exportCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
siteID := r.URL.Query().Get("site")
|
||||
|
||||
var writer io.Writer = w
|
||||
if r.URL.Query().Get("mode") == "file" {
|
||||
// buffer to memory to handle errors before committing to response
|
||||
var buf bytes.Buffer
|
||||
gzWriter := gzip.NewWriter(&buf)
|
||||
if _, err := m.NativeExporter.Export(gzWriter, siteID); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "export failed", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
if err := gzWriter.Close(); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "export failed", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
|
||||
exportFile := fmt.Sprintf("%s-%s.json.gz", siteID, time.Now().Format("20060102"))
|
||||
w.Header().Set("Content-Type", "application/gzip")
|
||||
w.Header().Set("Content-Disposition", "attachment;filename="+exportFile)
|
||||
gzWriter := gzip.NewWriter(w)
|
||||
defer func() {
|
||||
if e := gzWriter.Close(); e != nil {
|
||||
log.Printf("[WARN] can't close gzip writer, %s", e)
|
||||
}
|
||||
}()
|
||||
writer = gzWriter
|
||||
w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
|
||||
if _, err := io.Copy(w, &buf); err != nil {
|
||||
log.Printf("[WARN] failed to write export response: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := m.NativeExporter.Export(writer, siteID); err != nil {
|
||||
// stream mode - write directly to response
|
||||
if _, err := m.NativeExporter.Export(w, siteID); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "export failed", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,8 +210,8 @@ func (m *Migrator) remapCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("[DEBUG] convert request completed. site=%s, comments=%d", siteID, size)
|
||||
}()
|
||||
|
||||
render.Status(r, http.StatusAccepted)
|
||||
render.JSON(w, r, R.JSON{"status": "convert request accepted"})
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
R.RenderJSON(w, R.JSON{"status": "convert request accepted"})
|
||||
}
|
||||
|
||||
// runImport reads from tmpfile and import for given siteID and provider
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/go-pkgz/auth/v2"
|
||||
"github.com/go-pkgz/lcw/v2"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
@@ -473,8 +472,7 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
if cnf.Admins == nil { // prevent json serialization to nil
|
||||
cnf.Admins = []string{}
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, cnf)
|
||||
R.RenderJSON(w, cnf)
|
||||
}
|
||||
|
||||
// serves static files from the webRoot directory or files embedded into the compiled binary if that directory is absent
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/go-pkgz/auth/v2"
|
||||
"github.com/go-pkgz/auth/v2/token"
|
||||
cache "github.com/go-pkgz/lcw/v2"
|
||||
@@ -77,7 +76,7 @@ func (s *private) previewCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
user := rest.MustGetUserInfo(r)
|
||||
|
||||
comment := store.Comment{}
|
||||
if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &comment); err != nil {
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, hardBodyLimit)).Decode(&comment); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment", rest.ErrDecode)
|
||||
return
|
||||
}
|
||||
@@ -100,14 +99,13 @@ func (s *private) previewCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
render.HTML(w, r, comment.Text)
|
||||
rest.HTMLResponse(w, http.StatusOK, comment.Text)
|
||||
}
|
||||
|
||||
// POST /comment - adds comment, resets all immutable fields
|
||||
func (s *private) createCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
comment := store.Comment{}
|
||||
if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &comment); err != nil {
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, hardBodyLimit)).Decode(&comment); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment", rest.ErrDecode)
|
||||
return
|
||||
}
|
||||
@@ -176,8 +174,8 @@ func (s *private) createCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
log.Printf("[DEBUG] created comment %+v", finalComment)
|
||||
|
||||
render.Status(r, http.StatusCreated)
|
||||
render.JSON(w, r, &finalComment)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
R.RenderJSON(w, &finalComment)
|
||||
}
|
||||
|
||||
// PUT /comment/{id}?site=siteID&url=post-url - update comment
|
||||
@@ -188,7 +186,7 @@ func (s *private) updateCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
Delete bool
|
||||
}{}
|
||||
|
||||
if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &edit); err != nil {
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, hardBodyLimit)).Decode(&edit); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't read comment details from body", rest.ErrDecode)
|
||||
return
|
||||
}
|
||||
@@ -233,7 +231,7 @@ func (s *private) updateCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
s.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.SiteID, locator.URL, lastCommentsScope, user.ID))
|
||||
render.JSON(w, r, res)
|
||||
R.RenderJSON(w, res)
|
||||
}
|
||||
|
||||
// GET /user?site=siteID - returns user info
|
||||
@@ -251,7 +249,7 @@ func (s *private) userInfoCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
render.JSON(w, r, user)
|
||||
R.RenderJSON(w, user)
|
||||
}
|
||||
|
||||
// PUT /vote/{id}?site=siteID&url=post-url&vote=1 - vote for/against comment
|
||||
@@ -292,7 +290,7 @@ func (s *private) voteCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
s.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL, comment.User.ID))
|
||||
render.JSON(w, r, R.JSON{"id": comment.ID, "score": comment.Score})
|
||||
R.RenderJSON(w, R.JSON{"id": comment.ID, "score": comment.Score})
|
||||
}
|
||||
|
||||
// getEmailCtrl gets email address for authenticated user.
|
||||
@@ -305,7 +303,7 @@ func (s *private) getEmailCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("[WARN] can't read email for %s, %v", user.ID, err)
|
||||
}
|
||||
|
||||
render.JSON(w, r, R.JSON{"user": user, "address": address})
|
||||
R.RenderJSON(w, R.JSON{"user": user, "address": address})
|
||||
}
|
||||
|
||||
// sendEmailConfirmationCtrl gets address and siteID from query, makes confirmation token and sends it to user.
|
||||
@@ -322,7 +320,7 @@ func (s *private) sendEmailConfirmationCtrl(w http.ResponseWriter, r *http.Reque
|
||||
Address string
|
||||
autoConfirm bool
|
||||
}{autoConfirm: true}
|
||||
if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &subscribe); err != nil {
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, hardBodyLimit)).Decode(&subscribe); err != nil {
|
||||
if err != io.EOF {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't parse request body", rest.ErrDecode)
|
||||
return
|
||||
@@ -385,7 +383,7 @@ func (s *private) sendEmailConfirmationCtrl(w http.ResponseWriter, r *http.Reque
|
||||
},
|
||||
)
|
||||
|
||||
render.JSON(w, r, R.JSON{"user": user, "address": subscribe.Address, "updated": false})
|
||||
R.RenderJSON(w, R.JSON{"user": user, "address": subscribe.Address, "updated": false})
|
||||
}
|
||||
|
||||
// telegramSubscribeCtrl generates and verifies telegram notification request
|
||||
@@ -423,7 +421,7 @@ func (s *private) telegramSubscribeCtrl(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
s.telegramService.AddToken(tkn, user.ID, siteID, expires)
|
||||
|
||||
render.JSON(w, r, R.JSON{"token": tkn, "bot": s.telegramService.GetBotUsername()})
|
||||
R.RenderJSON(w, R.JSON{"token": tkn, "bot": s.telegramService.GetBotUsername()})
|
||||
|
||||
return
|
||||
}
|
||||
@@ -445,7 +443,7 @@ func (s *private) telegramSubscribeCtrl(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
render.JSON(w, r, R.JSON{"updated": true, "address": val})
|
||||
R.RenderJSON(w, R.JSON{"updated": true, "address": val})
|
||||
}
|
||||
|
||||
// setConfirmedEmailCtrl uses provided token parameter (generated by sendEmailConfirmationCtrl) to set email and add it to user token
|
||||
@@ -457,7 +455,7 @@ func (s *private) setConfirmedEmailCtrl(w http.ResponseWriter, r *http.Request)
|
||||
Site string
|
||||
Token string
|
||||
}{}
|
||||
if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &confirm); err != nil {
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, hardBodyLimit)).Decode(&confirm); err != nil {
|
||||
if err != io.EOF {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't parse request body", rest.ErrDecode)
|
||||
return
|
||||
@@ -513,7 +511,7 @@ func (s *private) setEmail(w http.ResponseWriter, r *http.Request, userID, siteI
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "failed to set token", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, R.JSON{"updated": true, "address": val})
|
||||
R.RenderJSON(w, R.JSON{"updated": true, "address": val})
|
||||
}
|
||||
|
||||
// POST/GET /email/unsubscribe.html?site=siteID&tkn=jwt - unsubscribe the user in token from email notifications
|
||||
@@ -597,7 +595,7 @@ func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl := template.Must(template.New("unsubscribe").Parse(tmplstr))
|
||||
msg := bytes.Buffer{}
|
||||
MustExecute(tmpl, &msg, nil)
|
||||
render.HTML(w, r, msg.String())
|
||||
rest.HTMLResponse(w, http.StatusOK, msg.String())
|
||||
}
|
||||
|
||||
// DELETE /email?site=siteID - removes user's email
|
||||
@@ -624,7 +622,7 @@ func (s *private) deleteEmailCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
render.JSON(w, r, R.JSON{"deleted": true})
|
||||
R.RenderJSON(w, R.JSON{"deleted": true})
|
||||
}
|
||||
|
||||
// DELETE /telegram?site=siteID - removes user's telegram
|
||||
@@ -638,7 +636,7 @@ func (s *private) deleteTelegramCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete telegram for user", code)
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, R.JSON{"deleted": true})
|
||||
R.RenderJSON(w, R.JSON{"deleted": true})
|
||||
}
|
||||
|
||||
// GET /userdata?site=siteID - exports all data about the user as a json with user info and list of all comments
|
||||
@@ -726,7 +724,7 @@ func (s *private) deleteMeCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
link := fmt.Sprintf("%s/web/deleteme.html?token=%s", s.remarkURL, tokenStr)
|
||||
render.JSON(w, r, R.JSON{"site": siteID, "user_id": user.ID, "token": tokenStr, "link": link})
|
||||
R.RenderJSON(w, R.JSON{"site": siteID, "user_id": user.ID, "token": tokenStr, "link": link})
|
||||
}
|
||||
|
||||
// POST /image - save image with form request
|
||||
@@ -751,7 +749,7 @@ func (s *private) savePictureCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
render.JSON(w, r, R.JSON{"id": id})
|
||||
R.RenderJSON(w, R.JSON{"id": id})
|
||||
}
|
||||
|
||||
func (s *private) isReadOnly(locator store.Locator) bool {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
"github.com/go-pkgz/auth/v2/token"
|
||||
"github.com/go-pkgz/lgr"
|
||||
R "github.com/go-pkgz/rest"
|
||||
@@ -970,7 +969,7 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
require.NoError(t, resp.Body.Close())
|
||||
require.Equal(t, http.StatusCreated, resp.StatusCode, string(body))
|
||||
parentComment := store.Comment{}
|
||||
require.NoError(t, render.DecodeJSON(strings.NewReader(string(body)), &parentComment))
|
||||
require.NoError(t, json.Unmarshal(body, &parentComment))
|
||||
// wait for mock notification Submit to kick off
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 1, len(mockDestination.Get()))
|
||||
@@ -1220,7 +1219,7 @@ func TestRest_TelegramNotification(t *testing.T) {
|
||||
require.NoError(t, resp.Body.Close())
|
||||
require.Equal(t, http.StatusCreated, resp.StatusCode, string(body))
|
||||
parentComment := store.Comment{}
|
||||
require.NoError(t, render.DecodeJSON(strings.NewReader(string(body)), &parentComment))
|
||||
require.NoError(t, json.Unmarshal(body, &parentComment))
|
||||
// wait for mock notification Submit to kick off
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 1, len(mockDestination.Get()))
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/sha1" // nolint
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
cache "github.com/go-pkgz/lcw/v2"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
R "github.com/go-pkgz/rest"
|
||||
@@ -232,7 +232,6 @@ func (s *public) commentByIDCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get comment by id", rest.ErrCommentNotFound)
|
||||
return
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
|
||||
if err = R.RenderJSONWithHTML(w, r, comment); err != nil {
|
||||
log.Printf("[WARN] can't render last comments for url=%s, id=%s", url, id)
|
||||
@@ -297,7 +296,7 @@ func (s *public) countCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get count", rest.ErrPostNotFound)
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, R.JSON{"count": count, "locator": locator})
|
||||
R.RenderJSON(w, R.JSON{"count": count, "locator": locator})
|
||||
}
|
||||
|
||||
// POST /counts?site=siteID - get number of comments for posts from post body
|
||||
@@ -305,7 +304,7 @@ func (s *public) countMultiCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
const countBodyLimit int64 = 1024 * 128 // count request can be big for some site because it lists all urls
|
||||
siteID := r.URL.Query().Get("site")
|
||||
posts := []string{}
|
||||
if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, countBodyLimit), &posts); err != nil {
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, countBodyLimit)).Decode(&posts); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get list of posts from request", rest.ErrSiteNotFound)
|
||||
return
|
||||
}
|
||||
@@ -393,13 +392,14 @@ func (s *public) loadPictureCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// GET /robots.txt
|
||||
func (s *public) robotsCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *public) robotsCtrl(w http.ResponseWriter, _ *http.Request) {
|
||||
allowed := []string{"/find", "/last", "/id", "/count", "/counts", "/list", "/config", "/user",
|
||||
"/img", "/avatar", "/picture"}
|
||||
for i := range allowed {
|
||||
allowed[i] = "Allow: /api/v1" + allowed[i]
|
||||
}
|
||||
render.PlainText(w, r, "User-agent: *\nDisallow: /auth/\nDisallow: /api/\n"+strings.Join(allowed, "\n")+"\n")
|
||||
responseText := fmt.Sprintf("User-agent: *\nDisallow: /auth/\nDisallow: /api/\n%s\n", strings.Join(allowed, "\n"))
|
||||
rest.PlainTextResponse(w, http.StatusOK, responseText)
|
||||
}
|
||||
|
||||
// GET /qr/telegram - generates QR for provided URL, used for Telegram auth and notifications subscription. The first
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
"github.com/go-pkgz/rest"
|
||||
|
||||
@@ -67,20 +66,36 @@ func SendErrorHTML(w http.ResponseWriter, r *http.Request, httpStatusCode int, e
|
||||
tmplstr := MustRead("error_response.html.tmpl")
|
||||
tmpl := template.Must(template.New("error").Parse(tmplstr))
|
||||
log.Printf("[WARN] %s", errDetailsMsg(r, httpStatusCode, err, details, errCode))
|
||||
render.Status(r, httpStatusCode)
|
||||
|
||||
msg := bytes.Buffer{}
|
||||
MustExecute(tmpl, &msg, errTmplData{
|
||||
Error: err.Error(),
|
||||
Details: details,
|
||||
})
|
||||
render.HTML(w, r, msg.String())
|
||||
|
||||
HTMLResponse(w, httpStatusCode, msg.String())
|
||||
}
|
||||
|
||||
// SendErrorJSON makes {error: blah, details: blah, code: 42} json body and responds with error code
|
||||
func SendErrorJSON(w http.ResponseWriter, r *http.Request, httpStatusCode int, err error, details string, errCode int) {
|
||||
log.Printf("[WARN] %s", errDetailsMsg(r, httpStatusCode, err, details, errCode))
|
||||
render.Status(r, httpStatusCode)
|
||||
render.JSON(w, r, rest.JSON{"error": err.Error(), "details": details, "code": errCode})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(httpStatusCode)
|
||||
rest.RenderJSON(w, rest.JSON{"error": err.Error(), "details": details, "code": errCode})
|
||||
}
|
||||
|
||||
// HTMLResponse writes HTML content with the given status code
|
||||
func HTMLResponse(w http.ResponseWriter, status int, html string) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write([]byte(html))
|
||||
}
|
||||
|
||||
// PlainTextResponse writes plain text content with the given status code
|
||||
func PlainTextResponse(w http.ResponseWriter, status int, text string) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write([]byte(text))
|
||||
}
|
||||
|
||||
func errDetailsMsg(r *http.Request, httpStatusCode int, err error, details string, errCode int) string {
|
||||
|
||||
+1
-1
@@ -10,7 +10,6 @@ require (
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-pkgz/auth/v2 v2.0.1-0.20250415030422-4f9f2c5e3b0d
|
||||
github.com/go-pkgz/jrpc v0.3.1
|
||||
github.com/go-pkgz/lcw/v2 v2.0.0
|
||||
@@ -48,6 +47,7 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/didip/tollbooth/v8 v8.0.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.3 // indirect
|
||||
github.com/go-pkgz/email v0.5.0 // indirect
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect
|
||||
|
||||
Reference in New Issue
Block a user