diff --git a/app/main.go b/app/main.go index a9ff038d..e3ea2162 100644 --- a/app/main.go +++ b/app/main.go @@ -14,6 +14,7 @@ import ( "github.com/umputun/remark/app/migrator" "github.com/umputun/remark/app/rest" "github.com/umputun/remark/app/rest/auth" + "github.com/umputun/remark/app/rest/common" "github.com/umputun/remark/app/store" ) @@ -39,6 +40,8 @@ var opts struct { GithubCSEC string `long:"github-csec" env:"REMARK_GITHUB_CSEC" description:"Github OAuth client secret"` FacebookCID string `long:"facebook-cid" env:"REMARK_FACEBOOK_CID" description:"Facebook OAuth client ID"` FacebookCSEC string `long:"facebook-csec" env:"REMARK_FACEBOOK_CSEC" description:"Facebook OAuth client secret"` + + Port int `long:"port" env:"REMARK_PORT" default:"8080" description:"port"` } `command:"server" description:"run server"` ImportCommand struct { @@ -80,36 +83,33 @@ func main() { return } + srvOpts := opts.ServerCommand dataService := store.Service{Interface: dataStore, EditDuration: 5 * time.Minute} - sessionStore := sessions.NewFilesystemStore(opts.ServerCommand.SessionStore, []byte(opts.ServerCommand.StoreKey)) + sessionStore := sessions.NewFilesystemStore(srvOpts.SessionStore, []byte(srvOpts.StoreKey)) sessionStore.Options.HttpOnly = true exporter := migrator.Remark{DataStore: dataStore} + authProviders := []auth.Provider{ + auth.NewGoogle(auth.Params{ + Cid: srvOpts.GoogleCID, Csecret: srvOpts.GoogleCSEC, SessionStore: sessionStore, RemarkURL: opts.RemarkURL, + }), + auth.NewGithub(auth.Params{ + Cid: srvOpts.GithubCID, Csecret: srvOpts.GithubCSEC, SessionStore: sessionStore, RemarkURL: opts.RemarkURL, + }), + auth.NewFacebook(auth.Params{ + Cid: srvOpts.FacebookCID, Csecret: srvOpts.FacebookCSEC, SessionStore: sessionStore, RemarkURL: opts.RemarkURL, + }), + } + srv := rest.Server{ - Version: revision, - DataService: dataService, - SessionStore: sessionStore, - Admins: opts.Admins, - DevMode: opts.DevMode, - Exporter: &exporter, - AuthGoogle: auth.NewGoogle(auth.Params{ - Cid: opts.ServerCommand.GoogleCID, - Csecret: opts.ServerCommand.GoogleCSEC, - SessionStore: sessionStore, - RemarkURL: opts.RemarkURL, - }), - AuthGithub: auth.NewGithub(auth.Params{ - Cid: opts.ServerCommand.GithubCID, - Csecret: opts.ServerCommand.GithubCSEC, - SessionStore: sessionStore, - RemarkURL: opts.RemarkURL, - }), - AuthFacebook: auth.NewFacebook(auth.Params{ - Cid: opts.ServerCommand.FacebookCID, - Csecret: opts.ServerCommand.FacebookCSEC, - SessionStore: sessionStore, - RemarkURL: opts.RemarkURL, - }), + Version: revision, + DataService: dataService, + SessionStore: sessionStore, + Admins: opts.Admins, + DevMode: opts.DevMode, + Exporter: &exporter, + Cache: common.NewLoadingCache(4*time.Hour, 15*time.Minute), + AuthProviders: authProviders, } if opts.DevMode { @@ -126,7 +126,7 @@ func main() { }.Do() } - srv.Run(8080) + srv.Run(srvOpts.Port) } // makeBoltStore creates store for all sites diff --git a/app/rest/admin.go b/app/rest/admin.go index cb8ac711..81faa18e 100644 --- a/app/rest/admin.go +++ b/app/rest/admin.go @@ -22,7 +22,7 @@ type admin struct { dataService store.Service exporter migrator.Exporter importer migrator.Importer - respCache *loadingCache + cache common.LoadingCache } func (a *admin) routes() chi.Router { @@ -48,7 +48,7 @@ func (a *admin) deleteCommentCtrl(w http.ResponseWriter, r *http.Request) { common.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't delete comment") return } - a.respCache.flush() + a.cache.Flush() render.Status(r, http.StatusOK) render.JSON(w, r, JSON{"id": id, "loc": locator}) } @@ -63,7 +63,7 @@ func (a *admin) setBlockCtrl(w http.ResponseWriter, r *http.Request) { common.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set blocking status") return } - a.respCache.flush() + a.cache.Flush() render.JSON(w, r, JSON{"user_id": userID, "site_id": siteID, "block": blockStatus}) } @@ -78,7 +78,7 @@ func (a *admin) setPinCtrl(w http.ResponseWriter, r *http.Request) { common.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set pin status") return } - a.respCache.flush() + a.cache.Flush() render.JSON(w, r, JSON{"id": commentID, "loc": locator, "pin": pinStatus}) } @@ -107,7 +107,7 @@ func (a *admin) importCtrl(w http.ResponseWriter, r *http.Request) { if err := a.importer.Import(r.Body, siteID); err != nil { common.SendErrorJSON(w, r, http.StatusBadRequest, err, "import failed") } - a.respCache.flush() + a.cache.Flush() } func (a *admin) checkBlocked(siteID string, user store.User) bool { diff --git a/app/rest/auth/auth.go b/app/rest/auth/auth.go index f29b182f..6c5ad65b 100644 --- a/app/rest/auth/auth.go +++ b/app/rest/auth/auth.go @@ -43,7 +43,7 @@ type Params struct { } // newProvider makes auth for given provider -func initProvider(p Params, provider Provider) *Provider { +func initProvider(p Params, provider Provider) Provider { log.Printf("[INFO] create %s auth, id=%s, redir: %s", provider.Name, p.Cid, provider.RedirectURL) conf := oauth2.Config{ @@ -56,7 +56,7 @@ func initProvider(p Params, provider Provider) *Provider { provider.conf = &conf provider.Store = p.SessionStore - return &provider + return provider } // Routes returns auth routes for given provider diff --git a/app/rest/auth/providers.go b/app/rest/auth/providers.go index a057d108..6fb9a260 100644 --- a/app/rest/auth/providers.go +++ b/app/rest/auth/providers.go @@ -12,7 +12,7 @@ import ( ) // NewGoogle makes google oauth2 provider -func NewGoogle(p Params) *Provider { +func NewGoogle(p Params) Provider { return initProvider(p, Provider{ Name: "google", Endpoint: google.Endpoint, @@ -37,7 +37,7 @@ func NewGoogle(p Params) *Provider { } // NewGithub makes github oauth2 provider -func NewGithub(p Params) *Provider { +func NewGithub(p Params) Provider { return initProvider(p, Provider{ Name: "github", Endpoint: github.Endpoint, @@ -62,7 +62,7 @@ func NewGithub(p Params) *Provider { } // NewFacebook makes facebook oauth2 provider -func NewFacebook(p Params) *Provider { +func NewFacebook(p Params) Provider { return initProvider(p, Provider{ Name: "facebook", Endpoint: facebook.Endpoint, diff --git a/app/rest/cache.go b/app/rest/common/cache.go similarity index 52% rename from app/rest/cache.go rename to app/rest/common/cache.go index 78698f78..5c7e6017 100644 --- a/app/rest/cache.go +++ b/app/rest/common/cache.go @@ -1,4 +1,4 @@ -package rest +package common import ( "log" @@ -7,15 +7,23 @@ import ( cache "github.com/patrickmn/go-cache" ) +// LoadingCache defines interface for caching +type LoadingCache interface { + Get(key string, ttl time.Duration, fn func() ([]byte, error)) (data []byte, err error) + Flush() +} + +// loadingCache implements LoadingCache interface on top of cache.Cache type loadingCache struct { bytesCache *cache.Cache } -func newLoadingCache(defaultExpiration, cleanupInterval time.Duration) *loadingCache { +// NewLoadingCache makes loadingCache implementation +func NewLoadingCache(defaultExpiration, cleanupInterval time.Duration) LoadingCache { return &loadingCache{bytesCache: cache.New(defaultExpiration, cleanupInterval)} } -func (lc *loadingCache) get(key string, ttl time.Duration, fn func() ([]byte, error)) (data []byte, err error) { +func (lc *loadingCache) Get(key string, ttl time.Duration, fn func() ([]byte, error)) (data []byte, err error) { if b, ok := lc.bytesCache.Get(key); ok { log.Printf("[DEBUG] cache hit %s", key) return b.([]byte), nil @@ -29,6 +37,6 @@ func (lc *loadingCache) get(key string, ttl time.Duration, fn func() ([]byte, er return data, nil } -func (lc *loadingCache) flush() { +func (lc *loadingCache) Flush() { lc.bytesCache.Flush() } diff --git a/app/rest/server.go b/app/rest/server.go index 8b6f1a90..ca96b823 100644 --- a/app/rest/server.go +++ b/app/rest/server.go @@ -17,30 +17,28 @@ import ( "github.com/gorilla/context" "github.com/gorilla/sessions" "github.com/pkg/errors" + "gopkg.in/russross/blackfriday.v2" "github.com/umputun/remark/app/migrator" "github.com/umputun/remark/app/rest/auth" "github.com/umputun/remark/app/rest/common" "github.com/umputun/remark/app/rest/format" "github.com/umputun/remark/app/store" - "gopkg.in/russross/blackfriday.v2" ) // Server is a rest access server type Server struct { - Version string - DataService store.Service - Admins []string - AuthGoogle *auth.Provider - AuthGithub *auth.Provider - AuthFacebook *auth.Provider - SessionStore *sessions.FilesystemStore - Exporter migrator.Exporter - DevMode bool + Version string + DataService store.Service + Admins []string + AuthProviders []auth.Provider + SessionStore *sessions.FilesystemStore + Exporter migrator.Exporter + Cache common.LoadingCache + DevMode bool httpServer *http.Server mod admin - respCache *loadingCache } // Run the lister and request's router, activate rest server @@ -60,9 +58,6 @@ func (s *Server) Run(port int) { log.Printf("[DEBUG] admins %+v", s.Admins) } - // cache for responses. Flushes completely on any modification - s.respCache = newLoadingCache(4*time.Hour, 15*time.Minute) - router := chi.NewRouter() router.Use(middleware.RealIP, Recoverer) router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second)) @@ -73,10 +68,10 @@ func (s *Server) Run(port int) { // auth routes for all providers router.Route("/auth", func(r chi.Router) { - r.Mount("/google", s.AuthGoogle.Routes()) - r.Mount("/github", s.AuthGithub.Routes()) - r.Mount("/facebook", s.AuthFacebook.Routes()) - r.Get("/logout", s.AuthGoogle.LogoutHandler) // shortcut, can be any of providers, all logouts do the same + for _, provider := range s.AuthProviders { + r.Mount("/"+provider.Name, provider.Routes()) + } + r.Get("/logout", s.AuthProviders[0].LogoutHandler) // shortcut, can be any of providers, all logouts do the same }) // api routes @@ -96,7 +91,7 @@ func (s *Server) Run(port int) { rauth.Put("/vote/{id}", s.voteCtrl) // admin routes, admin users only - s.mod = admin{dataService: s.DataService, exporter: s.Exporter, respCache: s.respCache} + s.mod = admin{dataService: s.DataService, exporter: s.Exporter, cache: s.Cache} rauth.Mount("/admin", s.mod.routes()) }) }) @@ -157,7 +152,7 @@ func (s *Server) createCommentCtrl(w http.ResponseWriter, r *http.Request) { return } - s.respCache.flush() // reset all caches + s.Cache.Flush() // reset all caches render.Status(r, http.StatusCreated) render.JSON(w, r, JSON{"id": id, "loc": comment.Locator}) @@ -206,7 +201,7 @@ func (s *Server) updateCommentCtrl(w http.ResponseWriter, r *http.Request) { return } - s.respCache.flush() // reset all caches + s.Cache.Flush() // reset all caches render.JSON(w, r, res) } @@ -216,7 +211,7 @@ func (s *Server) findCommentsCtrl(w http.ResponseWriter, r *http.Request) { locator := store.Locator{SiteID: r.URL.Query().Get("site"), URL: r.URL.Query().Get("url")} log.Printf("[DEBUG] get comments for %+v", locator) - data, err := s.respCache.get(r.URL.String(), time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(r.URL.String(), time.Hour, func() ([]byte, error) { comments, e := s.DataService.Find(locator, r.URL.Query().Get("sort")) if e != nil { return nil, e @@ -249,7 +244,7 @@ func (s *Server) lastCommentsCtrl(w http.ResponseWriter, r *http.Request) { max = 0 } - data, err := s.respCache.get(r.URL.String(), time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(r.URL.String(), time.Hour, func() ([]byte, error) { comments, e := s.DataService.Last(r.URL.Query().Get("site"), max) if e != nil { return nil, e @@ -291,7 +286,7 @@ func (s *Server) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) { log.Printf("[DEBUG] get comments for userID %s, %s", userID, siteID) - data, err := s.respCache.get(r.URL.String(), time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(r.URL.String(), time.Hour, func() ([]byte, error) { comments, e := s.DataService.User(siteID, userID) if e != nil { return nil, e @@ -331,7 +326,7 @@ func (s *Server) countCtrl(w http.ResponseWriter, r *http.Request) { func (s *Server) listCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") - data, err := s.respCache.get(r.URL.String(), 8*time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(r.URL.String(), 8*time.Hour, func() ([]byte, error) { posts, e := s.DataService.List(siteID) if e != nil { return nil, e @@ -365,7 +360,7 @@ func (s *Server) voteCtrl(w http.ResponseWriter, r *http.Request) { common.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't vote for comment") return } - s.respCache.flush() + s.Cache.Flush() render.JSON(w, r, JSON{"id": comment.ID, "score": comment.Score}) }