Skip to content

Commit

Permalink
feat: add local file system storage provider (casdoor#224)
Browse files Browse the repository at this point in the history
Signed-off-by: sh1luo <[email protected]>
  • Loading branch information
sh1luo authored and hsluoyz committed Aug 14, 2021
1 parent 44150a6 commit c55fa4f
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ tmp/
tmpFiles/
*.tmp
logs/
files/
lastupdate.tmp
commentsRouter*.go
4 changes: 2 additions & 2 deletions controllers/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,13 @@ func (c *ApiController) UploadAvatar() {
}

dist, _ := base64.StdEncoding.DecodeString(avatarBase64[index+1:])
err := object.UploadAvatar(provider, user.GetId(), dist)
fileUrl, err := object.UploadAvatar(provider, user.GetId(), dist)
if err != nil {
c.ResponseError(err.Error())
return
}

user.Avatar = fmt.Sprintf("%s/%s.png?time=%s", util.UrlJoin(provider.Domain, "/avatar"), user.GetId(), util.GetCurrentUnixTime())
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user)

resp = Response{Status: "ok", Msg: ""}
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func main() {
beego.SetStaticPath("/static", "web/build/static")
beego.BConfig.WebConfig.DirectoryIndex = true
beego.SetStaticPath("/swagger", "swagger")
beego.SetStaticPath("/files", "files")
// https://studygolang.com/articles/2303
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
Expand Down
25 changes: 22 additions & 3 deletions object/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ package object
import (
"bytes"
"fmt"
"strings"

"github.com/casbin/casdoor/storage"
"github.com/casbin/casdoor/util"
)

func UploadAvatar(provider *Provider, username string, avatar []byte) error {
func UploadAvatar(provider *Provider, username string, avatar []byte) (string, error) {
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
if storageProvider == nil {
return fmt.Errorf("the provider type: %s is not supported", provider.Type)
return "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
}

if provider.Domain == "" {
Expand All @@ -34,5 +36,22 @@ func UploadAvatar(provider *Provider, username string, avatar []byte) error {

path := fmt.Sprintf("%s/%s.png", util.UrlJoin(util.GetUrlPath(provider.Domain), "/avatar"), username)
_, err := storageProvider.Put(path, bytes.NewReader(avatar))
return err
if err != nil {
return "", err
}

host := ""
if provider.Type != "Local File System" {
// provider.Domain = "https://cdn.casbin.com/casdoor/"
host = util.GetUrlHost(provider.Domain)
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = fmt.Sprintf("https://%s", host)
}
} else {
// provider.Domain = "http://localhost:8000" or "https://door.casbin.com"
host = util.UrlJoin(provider.Domain, "/files")
}

fileUrl := fmt.Sprintf("%s?time=%s", util.UrlJoin(host, path), util.GetCurrentUnixTime())
return fileUrl, nil
}
129 changes: 129 additions & 0 deletions storage/local_file_system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package storage

import (
"io"
"os"
"path/filepath"
"strings"

"github.com/qor/oss"
)

var baseFolder = "files"

// FileSystem file system storage
type FileSystem struct {
Base string
}

// NewFileSystem initialize the local file system storage
func NewFileSystem(base string) *FileSystem {
absBase, err := filepath.Abs(base)
if err != nil {
panic("local file system storage's base folder is not initialized")
}

return &FileSystem{Base: absBase}
}

// GetFullPath get full path from absolute/relative path
func (fileSystem FileSystem) GetFullPath(path string) string {
fullPath := path
if !strings.HasPrefix(path, fileSystem.Base) {
fullPath, _ = filepath.Abs(filepath.Join(fileSystem.Base, path))
}
return fullPath
}

// Get receive file with given path
func (fileSystem FileSystem) Get(path string) (*os.File, error) {
return os.Open(fileSystem.GetFullPath(path))
}

// GetStream get file as stream
func (fileSystem FileSystem) GetStream(path string) (io.ReadCloser, error) {
return os.Open(fileSystem.GetFullPath(path))
}

// Put store a reader into given path
func (fileSystem FileSystem) Put(path string, reader io.Reader) (*oss.Object, error) {
var (
fullPath = fileSystem.GetFullPath(path)
err = os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
)

if err != nil {
return nil, err
}

dst, err := os.Create(fullPath)

if err == nil {
if seeker, ok := reader.(io.ReadSeeker); ok {
seeker.Seek(0, 0)
}
_, err = io.Copy(dst, reader)
}

return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: fileSystem}, err
}

// Delete delete file
func (fileSystem FileSystem) Delete(path string) error {
return os.Remove(fileSystem.GetFullPath(path))
}

// List list all objects under current path
func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
var (
objects []*oss.Object
fullPath = fileSystem.GetFullPath(path)
)

filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
if path == fullPath {
return nil
}

if err == nil && !info.IsDir() {
modTime := info.ModTime()
objects = append(objects, &oss.Object{
Path: strings.TrimPrefix(path, fileSystem.Base),
Name: info.Name(),
LastModified: &modTime,
StorageInterface: fileSystem,
})
}
return nil
})

return objects, nil
}

// GetEndpoint get endpoint, FileSystem's endpoint is /
func (fileSystem FileSystem) GetEndpoint() string {
return "/"
}

// GetURL get public accessible URL
func (fileSystem FileSystem) GetURL(path string) (url string, err error) {
return path, nil
}

func NewLocalFileSystemStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
return NewFileSystem(baseFolder)
}
2 changes: 2 additions & 0 deletions storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import "github.com/qor/oss"

func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
switch providerType {
case "Local File System":
return NewLocalFileSystemStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Aliyun OSS":
Expand Down
9 changes: 5 additions & 4 deletions util/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ func FileExist(path string) bool {
}

func UrlJoin(base string, path string) string {
if !strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://") {
base = fmt.Sprintf("https://%s", base)
}

res := fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(path, "/"))
return res
}
Expand All @@ -41,3 +37,8 @@ func GetUrlPath(urlString string) string {
u, _ := url.Parse(urlString)
return u.Path
}

func GetUrlHost(urlString string) string {
u, _ := url.Parse(urlString)
return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
}
10 changes: 8 additions & 2 deletions web/src/ProviderEditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ class ProviderEditPage extends React.Component {
} else if (value === "SMS") {
this.updateProviderField('type', 'Aliyun SMS');
} else if (value === "Storage") {
this.updateProviderField('type', 'Aliyun OSS');
this.updateProviderField('type', 'Local File System');
this.updateProviderField('domain', Setting.getFullServerUrl());
}
})}>
{
Expand All @@ -167,7 +168,12 @@ class ProviderEditPage extends React.Component {
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.type} onChange={(value => {this.updateProviderField('type', value);})}>
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.type} onChange={(value => {
this.updateProviderField('type', value);
if (value === "Local File System") {
this.updateProviderField('domain', Setting.getFullServerUrl());
}
})}>
{
this.getProviderTypeOptions(this.state.provider).map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
}
Expand Down
8 changes: 8 additions & 0 deletions web/src/Setting.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ function isLocalhost() {
return hostname === "localhost";
}

export function getFullServerUrl() {
let fullServerUrl = window.location.origin;
if (fullServerUrl === "http://localhost:7001") {
fullServerUrl = "http://localhost:8000";
}
return fullServerUrl;
}

export function isProviderVisible(providerItem) {
if (providerItem.provider === undefined || providerItem.provider === null) {
return false;
Expand Down

0 comments on commit c55fa4f

Please sign in to comment.