diff --git a/app/main.go b/app/main.go index 4029ffff..7a4857cc 100644 --- a/app/main.go +++ b/app/main.go @@ -66,6 +66,7 @@ func main() { dataStore := makeBoltStore() if p.Active != nil && p.Command.Find("import") == p.Active { + // import mode params := migrator.ImportParams{ DataStore: dataStore, InputFile: opts.ImportCommand.InputFile, @@ -138,6 +139,7 @@ func makeBoltStore() store.Interface { return result } +// mkdir -p for all dirs func makeDirs(dirs ...string) error { // exists returns whether the given file or directory exists or not diff --git a/app/rest/admin.go b/app/rest/admin.go index 662e8c3f..fd89d3f3 100644 --- a/app/rest/admin.go +++ b/app/rest/admin.go @@ -17,7 +17,7 @@ import ( "github.com/umputun/remark/app/store" ) -// admin provides router for all requests available for admin only +// admin provides router for all requests available for admin users only type admin struct { dataService store.Service exporter migrator.Exporter @@ -68,7 +68,8 @@ func (a *admin) setBlockCtrl(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, JSON{"user_id": userID, "site_id": siteID, "block": blockStatus}) } -// PUT /pin/{id}?site=siteID&url=post-url&pin=1 - mark/unmark comment as a special +// PUT /pin/{id}?site=siteID&url=post-url&pin=1 +// mark/unmark comment as a special func (a *admin) setPinCtrl(w http.ResponseWriter, r *http.Request) { commentID := chi.URLParam(r, "id") locator := store.Locator{SiteID: r.URL.Query().Get("site"), URL: r.URL.Query().Get("url")} @@ -82,7 +83,8 @@ func (a *admin) setPinCtrl(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, JSON{"id": commentID, "loc": locator, "pin": pinStatus}) } -// GET /export?site=site-id?mode=file|stream - exports all comments for siteID as json stream or file +// GET /export?site=site-id?mode=file|stream +// exports all comments for siteID as json stream or gz file func (a *admin) exportCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") var writer io.Writer = w @@ -100,6 +102,7 @@ func (a *admin) exportCtrl(w http.ResponseWriter, r *http.Request) { } // POST /import?site=site-id +// imports comments from post body. func (a *admin) importCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") if err := a.importer.Import(r.Body, siteID); err != nil { @@ -112,6 +115,8 @@ func (a *admin) checkBlocked(locator store.Locator, user store.User) bool { return a.dataService.IsBlocked(locator, user.ID) } +// processes comments and hides text of all comments for blocked users. +// resets score and votes too func (a *admin) maskBlockedUsers(comments []store.Comment) (res []store.Comment) { for _, c := range comments { if a.dataService.IsBlocked(c.Locator, c.User.ID) { diff --git a/app/rest/format/tree.go b/app/rest/format/tree.go index d0f4f502..6133874c 100644 --- a/app/rest/format/tree.go +++ b/app/rest/format/tree.go @@ -57,6 +57,7 @@ func (t *Tree) filter(comments []store.Comment, parentID string) (f []store.Comm return f } +// sort list of nodes, i.e. top-level comments func (t *Tree) sortNodes(sortType string) { sort.Slice(t.Nodes, func(i, j int) bool { diff --git a/app/rest/middleware.go b/app/rest/middleware.go index 4c9dd78c..3c0e38ea 100644 --- a/app/rest/middleware.go +++ b/app/rest/middleware.go @@ -60,7 +60,7 @@ func Limiter(recSec int, excludeIps ...string) func(http.Handler) http.Handler { } } -// AppInfo adds custom app-info to header +// AppInfo adds custom app-info to the response header func AppInfo(app string, version string) func(http.Handler) http.Handler { f := func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { @@ -77,7 +77,7 @@ func AppInfo(app string, version string) func(http.Handler) http.Handler { return f } -// Ping middleware response with pong. Stops chain if ping request detected +// Ping middleware response with pong to /ping. Stops chain if ping request detected func Ping(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { @@ -104,10 +104,8 @@ func Recoverer(next http.Handler) http.Handler { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() - next.ServeHTTP(w, r) } - return http.HandlerFunc(fn) } diff --git a/app/rest/server.go b/app/rest/server.go index df364722..dcc33bf5 100644 --- a/app/rest/server.go +++ b/app/rest/server.go @@ -64,6 +64,7 @@ func (s *Server) Run() { // If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler router.Use(context.ClearHandler) + // auth routes for all providers router.Route("/auth", func(r chi.Router) { r.Mount("/google", s.AuthGoogle.Routes()) r.Mount("/github", s.AuthGithub.Routes()) @@ -71,6 +72,7 @@ func (s *Server) Run() { r.Get("/logout", s.AuthGoogle.LogoutHandler) // shortcut, can be any of providers, does the same }) + // api routes router.Route("/api/v1", func(rapi chi.Router) { rapi.Get("/find", s.findCommentsCtrl) rapi.Get("/id/{id}", s.commentByIDCtrl) @@ -78,13 +80,13 @@ func (s *Server) Run() { rapi.Get("/last/{max}", s.lastCommentsCtrl) rapi.Get("/count", s.countCtrl) - // require auth + // protected routes, require auth rapi.With(auth.Auth(s.SessionStore, s.Admins, maybeDevMode(auth.Full))).Group(func(rauth chi.Router) { rauth.Post("/comment", s.createCommentCtrl) rauth.Get("/user", s.userInfoCtrl) rauth.Put("/vote/{id}", s.voteCtrl) - // require admin + // admin routes, admin users only s.mod = admin{dataService: s.DataService, exporter: s.Exporter, respCache: s.respCache} rauth.Mount("/admin", s.mod.routes()) }) @@ -93,7 +95,6 @@ func (s *Server) Run() { router.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) { render.PlainText(w, r, "User-agent: *\nDisallow: /auth/\nDisallow: /api/\n") - }) s.addFileServer(router, "/web", http.Dir(filepath.Join(".", "web"))) diff --git a/app/store/store.go b/app/store/store.go index a9567b96..9b8e033c 100644 --- a/app/store/store.go +++ b/app/store/store.go @@ -60,24 +60,25 @@ type Interface interface { // Accessor defines all usual access ops avail for regular user type Accessor interface { - Create(comment Comment) (commentID string, err error) - Get(locator Locator, commentID string) (comment Comment, err error) - Put(locator Locator, comment Comment) error - Find(request Request) ([]Comment, error) - Last(locator Locator, max int) ([]Comment, error) - GetByID(locator Locator, commentID string) (Comment, error) - GetByUser(locator Locator, userID string) ([]Comment, error) - Count(locator Locator) (int, error) - List(locator Locator) ([]string, error) + Create(comment Comment) (commentID string, err error) // create new comment, avoid dups by ID + Get(locator Locator, commentID string) (comment Comment, err error) // get comment by ID + Put(locator Locator, comment Comment) error // update comment, mutable parts only + Find(request Request) ([]Comment, error) // find comments for request + Last(locator Locator, max int) ([]Comment, error) // last comments for given site + GetByID(locator Locator, commentID string) (Comment, error) // comment by id + GetByUser(locator Locator, userID string) ([]Comment, error) // comment by user + Count(locator Locator) (int, error) // number of comments for the post + List(locator Locator) ([]string, error) // list of commented posts } // Admin defines all store ops avail for admin only type Admin interface { - Delete(locator Locator, commentID string) error - SetBlock(locator Locator, userID string, status bool) error - IsBlocked(locator Locator, userID string) bool + Delete(locator Locator, commentID string) error // delete comment by id + SetBlock(locator Locator, userID string, status bool) error // block or unblock user + IsBlocked(locator Locator, userID string) bool // check if user blocked } +// makeCommentID generates sha1(random) string func makeCommentID() string { b := make([]byte, 64) if _, err := rand.Read(b); err != nil { @@ -90,6 +91,7 @@ func makeCommentID() string { return fmt.Sprintf("%x", s.Sum(nil)) } +// clean dangerous html/js from the comment func sanitizeComment(comment Comment) Comment { p := bluemonday.UGCPolicy() comment.Text = p.Sanitize(comment.Text)