Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
uses: actions/setup-go@v5
with:
cache-dependency-path: backend/
go-version: "1.22"
go-version: "1.23"
id: go

- name: launch mongodb
Expand Down
12 changes: 4 additions & 8 deletions backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
module github.com/ukeeper/ukeeper-redabilty/backend

go 1.22
toolchain go1.24.1
go 1.23.0

toolchain go1.24.2

require (
github.com/PuerkitoBio/goquery v1.10.2
github.com/didip/tollbooth/v7 v7.0.2
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/render v1.0.3
github.com/go-pkgz/lgr v0.12.0
github.com/go-pkgz/rest v1.20.3
github.com/go-pkgz/routegroup v1.3.1
github.com/jessevdk/go-flags v1.6.1
github.com/kennygrant/sanitize v1.2.4
github.com/mauidude/go-readability v0.0.0-20220221173116-a9b3620098b7
Expand All @@ -20,10 +18,8 @@ require (
)

require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
Expand Down
21 changes: 2 additions & 19 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
Expand All @@ -45,29 +43,19 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi69p4=
github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc=
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw=
github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec=
github.com/go-pkgz/lgr v0.12.0 h1:uoSCLdiMocZDa+L66DavHG5UIkOJvWKOVqt6sNQllw0=
github.com/go-pkgz/lgr v0.12.0/go.mod h1:A4AxjOthFVFK6jRnVYMeusno5SeDAxcLVHd0kI/lN/Y=
github.com/go-pkgz/rest v1.20.3 h1:oGGfM8XTnvwek29q1OAhcI1nkKKOpurRFApBiYH44Fk=
github.com/go-pkgz/rest v1.20.3/go.mod h1:NC2xNN/y1rIs0PY13FowKoH8rk9RhJNJ0tTbkBg8Yks=
github.com/go-pkgz/routegroup v1.3.1 h1:XAVWskX8Iup6HoQD9zv+gJx4DOJC2DSkKBHCMeeW8/s=
github.com/go-pkgz/routegroup v1.3.1/go.mod h1:kDDPDRLRiRY1vnENrZJw1jQAzQX7fvsbsHGRQFNQfKc=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
Expand Down Expand Up @@ -112,10 +100,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
Expand Down Expand Up @@ -201,7 +186,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
Expand Down Expand Up @@ -409,7 +393,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
1 change: 0 additions & 1 deletion backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ func main() {
if _, err := flags.Parse(&opts); err != nil {
os.Exit(1)
}
// Setup logging
var options []log.Option
if opts.Debug {
options = []log.Option{log.Debug, log.CallerFile}
Expand Down
91 changes: 37 additions & 54 deletions backend/rest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@ import (
"strings"
"time"

"github.com/didip/tollbooth/v7"
"github.com/didip/tollbooth_chi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
log "github.com/go-pkgz/lgr"
"github.com/go-pkgz/rest"
"github.com/go-pkgz/rest/logger"
"github.com/go-pkgz/routegroup"
"go.mongodb.org/mongo-driver/bson/primitive"

"github.com/ukeeper/ukeeper-redabilty/backend/datastore"
Expand Down Expand Up @@ -67,42 +63,36 @@ func (s *Server) Run(ctx context.Context, address string, port int, frontendDir
log.Printf("[WARN] http server terminated, %s", httpServer.ListenAndServe())
}

func (s *Server) routes(frontendDir string) chi.Router {
router := chi.NewRouter()
func (s *Server) routes(frontendDir string) http.Handler {
router := routegroup.New(http.NewServeMux())

router.Use(middleware.RequestID, middleware.RealIP, rest.Recoverer(log.Default()))
router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second))
router.Use(rest.Recoverer(log.Default()))
router.Use(rest.RealIP)
router.Use(rest.AppInfo("ureadability", "Umputun", s.Version), rest.Ping)
router.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(50, nil)))

router.Use(rest.Throttle(50))
router.Use(logger.New(logger.Log(log.Default()), logger.WithBody, logger.Prefix("[INFO]")).Handler)

router.Route("/api", func(r chi.Router) {
r.Get("/content/v1/parser", s.extractArticleEmulateReadability)
r.Post("/extract", s.extractArticle)
r.Post("/auth", s.authFake)

r.Group(func(protected chi.Router) {
protected.Use(basicAuth("ureadability", s.Credentials))
protected.Post("/rule", s.saveRule)
protected.Post("/toggle-rule/{id}", s.toggleRule)
protected.Post("/preview", s.handlePreview)
router.Route(func(api *routegroup.Bundle) {
api.Mount("/api").Route(func(api *routegroup.Bundle) {
api.HandleFunc("GET /content/v1/parser", s.extractArticleEmulateReadability)
api.HandleFunc("POST /extract", s.extractArticle)
api.HandleFunc("POST /auth", s.authFake)

// add protected group with its own set of middlewares
protectedGroup := api.Group()
protectedGroup.Use(basicAuth("ureadability", s.Credentials))
protectedGroup.HandleFunc("POST /rule", s.saveRule)
protectedGroup.HandleFunc("POST /toggle-rule/{id}", s.toggleRule)
protectedGroup.HandleFunc("POST /preview", s.handlePreview)
})
})

router.Get("/", s.handleIndex)
router.Get("/add/", s.handleAdd)
router.Get("/edit/{id}", s.handleEdit)
router.HandleFunc("GET /", s.handleIndex)
router.HandleFunc("GET /add/", s.handleAdd)
router.HandleFunc("GET /edit/{id}", s.handleEdit)

_ = os.Mkdir(filepath.Join(frontendDir, "static"), 0o700)
fs, err := rest.NewFileServer("/", filepath.Join(frontendDir, "static"), rest.FsOptSPA)
if err != nil {
log.Printf("[ERROR] unable to create file server, %v", err)
return nil
}
router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
router.HandleFiles("/", http.Dir(filepath.Join(frontendDir, "static")))
return router
}

Expand Down Expand Up @@ -140,7 +130,7 @@ func (s *Server) handleAdd(w http.ResponseWriter, _ *http.Request) {
}

func (s *Server) handleEdit(w http.ResponseWriter, r *http.Request) {
id := getBid(chi.URLParam(r, "id"))
id := getBid(r.PathValue("id"))
rule, found := s.Readability.Rules.GetByID(r.Context(), id)
if !found {
http.Error(w, "Rule not found", http.StatusNotFound)
Expand All @@ -163,59 +153,52 @@ func (s *Server) handleEdit(w http.ResponseWriter, r *http.Request) {

func (s *Server) extractArticle(w http.ResponseWriter, r *http.Request) {
artRequest := extractor.Response{}
if err := render.DecodeJSON(r.Body, &artRequest); err != nil {
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, JSON{"error": err.Error()})
if err := rest.DecodeJSON(r, &artRequest); err != nil {
rest.SendErrorJSON(w, r, log.Default(), http.StatusInternalServerError, err, "can't parse request")
return
}

if artRequest.URL == "" {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, JSON{"error": "url parameter is required"})
rest.SendErrorJSON(w, r, log.Default(), http.StatusBadRequest, nil, "url parameter is required")
return
}

res, err := s.Readability.Extract(r.Context(), artRequest.URL)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, JSON{"error": err.Error()})
rest.SendErrorJSON(w, r, log.Default(), http.StatusBadRequest, err, "can't extract content")
return
}

render.JSON(w, r, &res)
rest.RenderJSON(w, &res)
}

// extractArticleEmulateReadability emulates readability API parse - https://www.readability.com/api/content/v1/parser?token=%s&url=%s
// if token is not set for application, it won't be checked
func (s *Server) extractArticleEmulateReadability(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if s.Token != "" && token == "" {
render.Status(r, http.StatusExpectationFailed)
render.JSON(w, r, JSON{"error": "no token passed"})
rest.SendErrorJSON(w, r, log.Default(), http.StatusExpectationFailed, nil, "no token passed")
return
}

if s.Token != "" && s.Token != token {
render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, JSON{"error": "wrong token passed"})
rest.SendErrorJSON(w, r, log.Default(), http.StatusUnauthorized, nil, "wrong token passed")
return
}

extractURL := r.URL.Query().Get("url")
if extractURL == "" {
render.Status(r, http.StatusExpectationFailed)
render.JSON(w, r, JSON{"error": "no url passed"})
rest.SendErrorJSON(w, r, log.Default(), http.StatusExpectationFailed, nil, "no url passed")
return
}

res, err := s.Readability.Extract(r.Context(), extractURL)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, JSON{"error": err.Error()})
rest.SendErrorJSON(w, r, log.Default(), http.StatusBadRequest, err, "can't extract content")
return
}

render.JSON(w, r, &res)
rest.RenderJSON(w, &res)
}

// generates previews for the provided test URLs
Expand Down Expand Up @@ -322,11 +305,11 @@ func (s *Server) saveRule(w http.ResponseWriter, r *http.Request) {
}

w.Header().Set("HX-Redirect", "/")
render.JSON(w, r, &srule)
rest.RenderJSON(w, &srule)
}

func (s *Server) toggleRule(w http.ResponseWriter, r *http.Request) {
id := getBid(chi.URLParam(r, "id"))
id := getBid(r.PathValue("id"))
rule, found := s.Readability.Rules.GetByID(r.Context(), id)
if !found {
log.Printf("[WARN] rule not found for id: %s", id.Hex())
Expand Down Expand Up @@ -356,9 +339,9 @@ func (s *Server) toggleRule(w http.ResponseWriter, r *http.Request) {
}

// authFake just a dummy post request used for external check for protected resource
func (s *Server) authFake(w http.ResponseWriter, r *http.Request) {
func (s *Server) authFake(w http.ResponseWriter, _ *http.Request) {
t := time.Now()
render.JSON(w, r, JSON{"pong": t.Format("20060102150405")})
rest.RenderJSON(w, JSON{"pong": t.Format("20060102150405")})
}

func getBid(id string) primitive.ObjectID {
Expand Down
2 changes: 1 addition & 1 deletion backend/rest/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func TestServer_RuleUnhappyFlow(t *testing.T) {

// get supposed to fail
_, code := get(t, ts.URL+"/api/rule")
assert.Equal(t, http.StatusMethodNotAllowed, code)
assert.Equal(t, http.StatusNotFound, code)

// get rule by non-existent ID
_, code = get(t, ts.URL+"/api/rule/nonexistent")
Expand Down
25 changes: 0 additions & 25 deletions backend/vendor/github.com/ajg/form/.travis.yml

This file was deleted.

27 changes: 0 additions & 27 deletions backend/vendor/github.com/ajg/form/LICENSE

This file was deleted.

Loading
Loading