diff --git a/app/main.go b/app/main.go index 661b4586..fc7861db 100644 --- a/app/main.go +++ b/app/main.go @@ -18,6 +18,7 @@ import ( "github.com/umputun/remark/app/rest" "github.com/umputun/remark/app/rest/api" "github.com/umputun/remark/app/rest/auth" + "github.com/umputun/remark/app/rest/avatar" "github.com/umputun/remark/app/store" ) @@ -102,7 +103,7 @@ func main() { } go importSrv.Run(opts.Port + 1) - avatarProxy := &auth.AvatarProxy{ + avatarProxy := &avatar.Proxy{ StorePath: opts.AvatarStore, RoutePath: "/api/v1/avatar", RemarkURL: strings.TrimSuffix(opts.RemarkURL, "/"), @@ -181,7 +182,7 @@ func makeDirs(dirs ...string) error { return nil } -func makeAuthProviders(sessionStore sessions.Store, avatarProxy *auth.AvatarProxy) (providers []auth.Provider) { +func makeAuthProviders(sessionStore sessions.Store, avatarProxy *avatar.Proxy) (providers []auth.Provider) { makeParams := func(cid, secret string) auth.Params { return auth.Params{ diff --git a/app/rest/api/rest.go b/app/rest/api/rest.go index 8405c530..af7912bf 100644 --- a/app/rest/api/rest.go +++ b/app/rest/api/rest.go @@ -82,7 +82,7 @@ func (s *Rest) Run(port int) { } }) - router.Mount(s.Authenticator.AvatarProxy.Routes()) + router.Mount(s.Authenticator.AvatarProxy.Routes()) // mount avatars controller to /api/v1/avatar/{file.img} // api routes router.Route("/api/v1", func(rapi chi.Router) { diff --git a/app/rest/api/rest_test.go b/app/rest/api/rest_test.go index 38d2d939..413ef483 100644 --- a/app/rest/api/rest_test.go +++ b/app/rest/api/rest_test.go @@ -17,6 +17,7 @@ import ( "github.com/gorilla/sessions" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/umputun/remark/app/rest/avatar" "github.com/umputun/remark/app/migrator" "github.com/umputun/remark/app/rest/auth" @@ -468,7 +469,7 @@ func prep(t *testing.T) (srv *Rest, port int) { SessionStore: sessions.NewFilesystemStore("/tmp", []byte("blah")), DevPasswd: "password", Providers: nil, - AvatarProxy: &auth.AvatarProxy{StorePath: "/tmp", RoutePath: "/api/v1/avatar"}, + AvatarProxy: &avatar.Proxy{StorePath: "/tmp", RoutePath: "/api/v1/avatar"}, Admins: []string{"a1", "a2"}, }, Exporter: &migrator.Remark{DataStore: dataStore}, diff --git a/app/rest/auth/auth.go b/app/rest/auth/auth.go index b9ca39d1..13d81b7b 100644 --- a/app/rest/auth/auth.go +++ b/app/rest/auth/auth.go @@ -11,13 +11,14 @@ import ( "github.com/pkg/errors" "github.com/umputun/remark/app/rest" + "github.com/umputun/remark/app/rest/avatar" "github.com/umputun/remark/app/store" ) // Authenticator is top level auth object providing middlewares type Authenticator struct { SessionStore sessions.Store - AvatarProxy *AvatarProxy + AvatarProxy *avatar.Proxy Admins []string Providers []Provider diff --git a/app/rest/auth/provider.go b/app/rest/auth/provider.go index d15477bd..5e2a997d 100644 --- a/app/rest/auth/provider.go +++ b/app/rest/auth/provider.go @@ -15,6 +15,7 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/render" "github.com/gorilla/sessions" + "github.com/umputun/remark/app/rest/avatar" "golang.org/x/oauth2" "github.com/umputun/remark/app/rest" @@ -32,7 +33,7 @@ type Provider struct { Scopes []string MapUser func(userData, []byte) store.User // map info from InfoURL to User - avatarProxy *AvatarProxy + avatarProxy *avatar.Proxy conf *oauth2.Config } @@ -42,7 +43,7 @@ type Params struct { Csecret string SessionStore sessions.Store RemarkURL string - AvatarProxy *AvatarProxy + AvatarProxy *avatar.Proxy } type userData map[string]interface{} diff --git a/app/rest/auth/avatar.go b/app/rest/avatar/avatar.go similarity index 86% rename from app/rest/auth/avatar.go rename to app/rest/avatar/avatar.go index 961e0dcf..79753631 100644 --- a/app/rest/auth/avatar.go +++ b/app/rest/avatar/avatar.go @@ -1,4 +1,6 @@ -package auth +// Package avatar provides file-system store and http handler for avatars +// On user login auth will call Put and it will retrieve and save picture locally. +package avatar import ( "fmt" @@ -21,8 +23,8 @@ import ( "github.com/umputun/remark/app/store" ) -// AvatarProxy provides avatar store and http handler for avatars -type AvatarProxy struct { +// Proxy is avatar store and http handler for avatars +type Proxy struct { StorePath string RoutePath string RemarkURL string @@ -34,7 +36,7 @@ type AvatarProxy struct { const imgSfx = ".image" // Put stores retrieved avatar to StorePath. Gets image from user info. Returns proxied url -func (p *AvatarProxy) Put(u store.User) (avatarURL string, err error) { +func (p *Proxy) Put(u store.User) (avatarURL string, err error) { // no picture for user, try default avatar if u.Picture == "" { @@ -83,7 +85,7 @@ func (p *AvatarProxy) Put(u store.User) (avatarURL string, err error) { } // Routes returns auth routes for given provider -func (p *AvatarProxy) Routes() (string, chi.Router) { +func (p *Proxy) Routes() (string, chi.Router) { router := chi.NewRouter() // GET /123456789.image @@ -91,7 +93,7 @@ func (p *AvatarProxy) Routes() (string, chi.Router) { avatar := chi.URLParam(r, "avatar") - // client-side caching + // enforce client-side caching etag := `"` + avatar + `"` w.Header().Set("Etag", etag) w.Header().Set("Cache-Control", "max-age=2592000") // 30 days @@ -133,10 +135,10 @@ func (p *AvatarProxy) Routes() (string, chi.Router) { return p.RoutePath, router } -// get location for user id by adding partition to final path in order to keep files +// get location (directory) for user id by adding partition to final path in order to keep files // in different subdirectories and avoid too many files in a single place. // the end result is a full path like this - /tmp/avatars.test/92 -func (p *AvatarProxy) location(id string) string { +func (p *Proxy) location(id string) string { p.once.Do(func() { p.ctcTable = crc64.MakeTable(crc64.ECMA) }) checksum64 := crc64.Checksum([]byte(id), p.ctcTable) partition := checksum64 % 100 diff --git a/app/rest/auth/avatar_test.go b/app/rest/avatar/avatar_test.go similarity index 90% rename from app/rest/auth/avatar_test.go rename to app/rest/avatar/avatar_test.go index 9b22790c..9070b93b 100644 --- a/app/rest/auth/avatar_test.go +++ b/app/rest/avatar/avatar_test.go @@ -1,4 +1,4 @@ -package auth +package avatar import ( "bytes" @@ -26,7 +26,7 @@ func TestPut(t *testing.T) { })) defer ts.Close() - p := AvatarProxy{StorePath: "/tmp/avatars.test", RoutePath: "/avatar", RemarkURL: "http://localhost:8080"} + p := Proxy{StorePath: "/tmp/avatars.test", RoutePath: "/avatar", RemarkURL: "http://localhost:8080"} os.MkdirAll("/tmp/avatars.test", 0700) defer os.RemoveAll("/tmp/avatars.test") @@ -48,7 +48,7 @@ func TestPut(t *testing.T) { } func TestPutNoAvatar(t *testing.T) { - p := AvatarProxy{StorePath: "/tmp/avatars.test", RoutePath: "/avatar"} + p := Proxy{StorePath: "/tmp/avatars.test", RoutePath: "/avatar"} u := store.User{ID: "user1", Name: "user1 name"} _, err := p.Put(u) assert.Error(t, err) @@ -66,7 +66,7 @@ func TestRoutes(t *testing.T) { })) defer ts.Close() - p := AvatarProxy{StorePath: "/tmp/avatars.test", RoutePath: "/avatar"} + p := Proxy{StorePath: "/tmp/avatars.test", RoutePath: "/avatar"} os.MkdirAll("/tmp/avatars.test", 0700) defer os.RemoveAll("/tmp/avatars.test") @@ -98,7 +98,7 @@ func TestRoutes(t *testing.T) { } func TestLocation(t *testing.T) { - p := AvatarProxy{StorePath: "/tmp/avatars.test"} + p := Proxy{StorePath: "/tmp/avatars.test"} tbl := []struct { id string