add support for avatar store uri

This commit is contained in:
Umputun
2019-08-24 01:59:24 -05:00
parent 141c75401a
commit a6149ae064
11 changed files with 85 additions and 9 deletions
+5 -2
View File
@@ -34,7 +34,7 @@ Example with chi router:
```go
func main() {
/// define options
// define options
options := auth.Opts{
SecretReader: token.SecretFunc(func(id string) (string, error) { // secret key for JWT
return "secret", nil
@@ -129,7 +129,10 @@ Direct links to avatars won't survive any real-life usage if they linked from a
- `avatar.GridFS` - external [GridFS](https://docs.mongodb.com/manual/core/gridfs/) (mongo db).
- In case of need custom implementations of other stores can be passed in and used by `auth` library. Each store has to implement `avatar.Store` [interface](https://github.com/go-pkgz/auth/blob/master/avatar/store.go#L25).
- All avatar-related setup done as a part of `auth.Opts` and needs:
- `AvatarStore` - avatar store to use, i.e. `avatar.NewLocalFS("/tmp/avatars")`
- `AvatarStore` - avatar store to use, i.e. `avatar.NewLocalFS("/tmp/avatars")` or more generic `avatar.NewStore(uri)`
- file system uri - `file:///tmp/location` or just `/tmp/location`
- boltdb - `bolt://tmp/avatars.bdb`
- mongo - `"mongodb://127.0.0.1:27017/test?ava_db=db1&ava_coll=coll1`
- `AvatarRoutePath` - route prefix for direct links to proxied avatar. For example `/api/v1/avatars` will make full links like this - `http://example.com/api/v1/avatars/1234567890123.image`. The url will be stored in user's token and retrieved by middleware (see "User Info")
- `AvatarResizeLimit` - size (in pixels) used to resize the avatar. Pls note - resize happens once as a part of `Put` call, i.e. on login. 0 size (default) disables resizing.
+5
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
@@ -126,6 +127,10 @@ func (b *BoltDB) Close() error {
return errors.Wrapf(b.db.Close(), "failed to close %s", b.fileName)
}
func (b *BoltDB) String() string {
return fmt.Sprintf("boltdb, path=%s", b.fileName)
}
func (b *BoltDB) sha1(data []byte, avatarID string) (id string) {
h := sha1.New()
if _, err := h.Write(data); err != nil {
+5
View File
@@ -2,6 +2,7 @@ package avatar
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
@@ -116,3 +117,7 @@ func (gf *GridFS) List() (ids []string, err error) {
func (gf *GridFS) Close() error {
return nil
}
func (gf *GridFS) String() string {
return fmt.Sprintf("mongo (grid fs), conn=%s", gf.Connection)
}
+4
View File
@@ -108,6 +108,10 @@ func (fs *LocalFS) Close() error {
return nil
}
func (fs *LocalFS) String() string {
return fmt.Sprintf("localfs, path=%s", fs.storePath)
}
// 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
+54
View File
@@ -4,15 +4,21 @@ package avatar
import (
"crypto/sha1"
"fmt"
_ "image/gif" // initializing packages for supporting GIF
_ "image/jpeg" // initializing packages for supporting JPEG.
_ "image/png" // initializing packages for supporting PNG.
"io"
"log"
"net/url"
"regexp"
"strings"
"time"
bolt "github.com/coreos/bbolt"
"github.com/go-pkgz/auth/token"
"github.com/go-pkgz/mongo"
"github.com/pkg/errors"
)
// imgSfx for avatars
@@ -22,6 +28,7 @@ var reValidAvatarID = regexp.MustCompile(`^[a-fA-F0-9]{40}\.image$`)
// Store defines interface to store and and load avatars
type Store interface {
fmt.Stringer
Put(userID string, reader io.Reader) (avatarID string, err error) // save avatar data from the reader and return base name
Get(avatarID string) (reader io.ReadCloser, size int, err error) // load avatar via reader
ID(avatarID string) (id string) // unique id of stored avatar's data
@@ -30,6 +37,30 @@ type Store interface {
Close() error // close store
}
// NewStore provides factory for all supported stores making the one
// based on uri protocol. Default (no protocol) is file-system
func NewStore(uri string) (Store, error) {
switch {
case strings.HasPrefix(uri, "file://"):
return NewLocalFS(strings.TrimPrefix(uri, "file://")), nil
case !strings.Contains(uri, "://"):
return NewLocalFS(uri), nil
case strings.HasPrefix(uri, "mongodb://"):
db, coll, u, err := parseExtMongoURI(uri)
if err != nil {
return nil, errors.Wrapf(err, "can't parse mongo store uri %s", uri)
}
mg, err := mongo.NewServerWithURL(u, time.Second)
if err != nil {
return nil, errors.Wrap(err, "failed to make mongo server")
}
return NewGridFS(mongo.NewConnection(mg, db, coll)), nil
case strings.HasPrefix(uri, "bolt://"):
return NewBoltDB(strings.TrimPrefix(uri, "bolt://"), bolt.Options{})
}
return nil, errors.Errorf("can't parse store url %s", uri)
}
// Migrate avatars between stores
func Migrate(dst, src Store) (int, error) {
ids, err := src.List()
@@ -59,3 +90,26 @@ func encodeID(id string) string {
}
return token.HashID(sha1.New(), id)
}
// parseExtMongoURI extracts extra params ava_db and ava_coll and remove
// from the url. Input example: mongodb://user:password@127.0.0.1:27017/test?ssl=true&ava_db=db1&ava_coll=coll1
func parseExtMongoURI(uri string) (db, collection, cleanURI string, err error) {
db, collection = "test", "avatars_fs"
u, err := url.Parse(uri)
if err != nil {
return "", "", "", err
}
if val := u.Query().Get("ava_db"); val != "" {
db = val
}
if val := u.Query().Get("ava_coll"); val != "" {
collection = val
}
q := u.Query()
q.Del("ava_db")
q.Del("ava_coll")
u.RawQuery = q.Encode()
return db, collection, u.String(), nil
}