mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-02 03:54:19 +08:00
feature (plugin): API to develop plugin
This commit is contained in:
@ -42,6 +42,10 @@ RUN mkdir -p /tmp/go/src/github.com/mickael-kerjean/ && \
|
|||||||
cd ../ && \
|
cd ../ && \
|
||||||
GOPATH=/tmp/go go build -o ./dist/nuage ./server/main.go && \
|
GOPATH=/tmp/go go build -o ./dist/nuage ./server/main.go && \
|
||||||
#################
|
#################
|
||||||
|
# Compile Plugins
|
||||||
|
cd /tmp/go/src/github.com/mickael-kerjean/nuage/server/plugin &&
|
||||||
|
cd image && ./build.sh && cd ../ &&
|
||||||
|
#################
|
||||||
# Finalise the build
|
# Finalise the build
|
||||||
apk --no-cache add ca-certificates && \
|
apk --no-cache add ca-certificates && \
|
||||||
mv dist /app && \
|
mv dist /app && \
|
||||||
|
|||||||
@ -1,48 +1,8 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Config *Config
|
Config *Config
|
||||||
Helpers *Helpers
|
|
||||||
Backend IBackend
|
Backend IBackend
|
||||||
Body map[string]interface{}
|
Body map[string]interface{}
|
||||||
Session map[string]string
|
Session map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCurrentDir() string {
|
|
||||||
ex, _ := os.Executable()
|
|
||||||
return filepath.Dir(ex)
|
|
||||||
}
|
|
||||||
|
|
||||||
var HTTPClient = http.Client{
|
|
||||||
Timeout: 5 * time.Hour,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Dial: (&net.Dialer{
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
KeepAlive: 10 * time.Second,
|
|
||||||
}).Dial,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
IdleConnTimeout: 60 * time.Second,
|
|
||||||
ResponseHeaderTimeout: 60 * time.Second,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var HTTP = http.Client{
|
|
||||||
Timeout: 800 * time.Millisecond,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Dial: (&net.Dialer{
|
|
||||||
Timeout: 500 * time.Millisecond,
|
|
||||||
KeepAlive: 500 * time.Millisecond,
|
|
||||||
}).Dial,
|
|
||||||
TLSHandshakeTimeout: 500 * time.Millisecond,
|
|
||||||
IdleConnTimeout: 500 * time.Millisecond,
|
|
||||||
ResponseHeaderTimeout: 500 * time.Millisecond,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,27 +1,52 @@
|
|||||||
package backend
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/mickael-kerjean/nuage/server/common"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Nothing struct {
|
const BACKEND_NIL = "_nothing_"
|
||||||
|
|
||||||
|
var Backend = NewDriver()
|
||||||
|
|
||||||
|
func NewDriver() Driver {
|
||||||
|
return Driver{make(map[string]IBackend)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNothing(params map[string]string, app *App) (*Nothing, error) {
|
type Driver struct {
|
||||||
|
ds map[string]IBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Register(name string, driver IBackend) {
|
||||||
|
if driver == nil {
|
||||||
|
panic("backend: register invalid nil backend")
|
||||||
|
}
|
||||||
|
if d.ds[name] != nil {
|
||||||
|
panic("backend: register already exist")
|
||||||
|
}
|
||||||
|
d.ds[name] = driver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Get(name string) IBackend {
|
||||||
|
b := d.ds[name]
|
||||||
|
if b == nil || name == BACKEND_NIL {
|
||||||
|
return Nothing{}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nothing struct {}
|
||||||
|
|
||||||
|
func (b Nothing) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
return &Nothing{}, nil
|
return &Nothing{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Nothing) Info() string {
|
func (b Nothing) Info() string {
|
||||||
return "N/A"
|
return "N/A"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Nothing) Ls(path string) ([]os.FileInfo, error) {
|
func (b Nothing) Ls(path string) ([]os.FileInfo, error) {
|
||||||
return nil, NewError("", 401)
|
return nil, NewError("", 401)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Nothing) Cat(path string) (io.Reader, error) {
|
func (b Nothing) Cat(path string) (io.Reader, error) {
|
||||||
return strings.NewReader(""), NewError("", 401)
|
return strings.NewReader(""), NewError("", 401)
|
||||||
}
|
}
|
||||||
@ -1,198 +1,188 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/fsnotify/fsnotify"
|
"io"
|
||||||
"log"
|
"io/ioutil"
|
||||||
"os"
|
"github.com/tidwall/gjson"
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var configPath string = filepath.Join(GetCurrentDir(), CONFIG_PATH + "config.json")
|
||||||
CONFIG_PATH = "data/config/"
|
|
||||||
APP_VERSION = "v0.3"
|
func init() {
|
||||||
)
|
c := NewConfig()
|
||||||
|
// Let's initialise all our json config stuff
|
||||||
|
// For some reasons the file will be written bottom up so we start from the end moving up to the top
|
||||||
|
|
||||||
|
// Connections
|
||||||
|
if c.Get("connections.0.type").Interface() == nil {
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "webdav", "label": "Webdav"})
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "ftp", "label": "FTP"})
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "sftp", "label": "SFTP"})
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "git", "label": "GIT"})
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "s3", "label": "S3"})
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "dropbox", "label": "Dropbox"})
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "gdrive", "label": "Drive"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuth credentials
|
||||||
|
c.Get("oauth").Default("")
|
||||||
|
|
||||||
|
// Log
|
||||||
|
c.Get("log.telemetry").Default(true)
|
||||||
|
c.Get("log.level").Default("INFO")
|
||||||
|
c.Get("log.enable").Default(true)
|
||||||
|
|
||||||
|
// Email
|
||||||
|
c.Get("email.from").Default("username@gmail.com")
|
||||||
|
c.Get("email.password").Default("password")
|
||||||
|
c.Get("email.username").Default("username@gmail.com")
|
||||||
|
c.Get("email.port").Default(587)
|
||||||
|
c.Get("email.server").Default("smtp.google.com")
|
||||||
|
|
||||||
|
// General
|
||||||
|
c.Get("general.remember_me").Default(true)
|
||||||
|
c.Get("general.auto_connect").Default(false)
|
||||||
|
c.Get("general.display_hidden").Default(true)
|
||||||
|
c.Get("general.fork_button").Default(true)
|
||||||
|
c.Get("general.editor").Default("emacs")
|
||||||
|
if c.Get("general.secret_key").String() == "" {
|
||||||
|
c.Get("general.secret_key").Default(RandomString(16))
|
||||||
|
}
|
||||||
|
if env := os.Getenv("APPLICATION_URL"); env != "" {
|
||||||
|
c.Get("general.host").Set(env)
|
||||||
|
} else {
|
||||||
|
c.Get("general.host").Default("http://127.0.0.1:8334")
|
||||||
|
}
|
||||||
|
c.Get("general.port").Default(8334)
|
||||||
|
c.Get("general.name").Default("Nuage")
|
||||||
|
}
|
||||||
|
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
c := Config{}
|
a := Config{}
|
||||||
c.Initialise()
|
return a.load()
|
||||||
return &c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
General struct {
|
mu sync.Mutex
|
||||||
Name string `json:"name"`
|
path *string
|
||||||
Port int `json:"port"`
|
json string
|
||||||
Host string `json:"host"`
|
reader gjson.Result
|
||||||
SecretKey string `json:"secret_key"`
|
|
||||||
Editor string `json:"editor"`
|
|
||||||
ForkButton bool `json:"fork_button"`
|
|
||||||
DisplayHidden bool `json:"display_hidden"`
|
|
||||||
AutoConnect bool `json:"auto_connect"`
|
|
||||||
RememberMe *bool `json:"remember_me"`
|
|
||||||
} `json:"general"`
|
|
||||||
Log struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
Telemetry bool `json:"telemetry"`
|
|
||||||
} `json:"log"`
|
|
||||||
Email struct {
|
|
||||||
Server string `json:"server"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
From string `json:"from"`
|
|
||||||
} `json:"email"`
|
|
||||||
OAuthProvider struct {
|
|
||||||
Dropbox struct {
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
} `json:"dropbox"`
|
|
||||||
GoogleDrive struct {
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
ClientSecret string `json:"client_secret"`
|
|
||||||
} `json:"gdrive"`
|
|
||||||
Custom struct {
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
ClientSecret string `json:"client_secret"`
|
|
||||||
} `json:"custom"`
|
|
||||||
} `json:"oauth"`
|
|
||||||
Connections []struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Hostname *string `json:"hostname,omitempty"`
|
|
||||||
Username *string `json:"username,omitempty"`
|
|
||||||
Password *string `json:"password,omitempty"`
|
|
||||||
Url *string `json:"url,omitempty"`
|
|
||||||
Advanced *bool `json:"advanced,omitempty"`
|
|
||||||
Port *string `json:"port,omitempty"`
|
|
||||||
Path *string `json:"path,omitempty"`
|
|
||||||
Passphrase *string `json:"passphrase,omitempty"`
|
|
||||||
Conn *string `json:"conn,omitempty"`
|
|
||||||
SecretAccessKey *string `json:"secret_access_key,omitempty"`
|
|
||||||
AccessKeyId *string `json:"access_key_id,omitempty"`
|
|
||||||
Endpoint *string `json:"endpoint,omitempty"`
|
|
||||||
Commit *string `json:"commit,omitempty"`
|
|
||||||
Branch *string `json:"branch,omitempty"`
|
|
||||||
AuthorEmail *string `json:"author_email,omitempty"`
|
|
||||||
AuthorName *string `json:"author_name,omitempty"`
|
|
||||||
CommitterEmail *string `json:"committer_email,omitempty"`
|
|
||||||
CommitterName *string `json:"committter_name,omitempty"`
|
|
||||||
} `json:"connections"`
|
|
||||||
Runtime struct {
|
|
||||||
Dirname string
|
|
||||||
ConfigPath string
|
|
||||||
FirstSetup bool
|
|
||||||
} `json:"-"`
|
|
||||||
MimeTypes map[string]string `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Initialise() {
|
func (this *Config) load() *Config {
|
||||||
c.Runtime.Dirname = GetCurrentDir()
|
if f, err := os.OpenFile(configPath, os.O_RDONLY, os.ModePerm); err == nil {
|
||||||
c.Runtime.ConfigPath = filepath.Join(c.Runtime.Dirname, CONFIG_PATH)
|
j, _ := ioutil.ReadAll(f)
|
||||||
os.MkdirAll(c.Runtime.ConfigPath, os.ModePerm)
|
this.json = string(j)
|
||||||
if err := c.loadConfig(filepath.Join(c.Runtime.ConfigPath, "config.json")); err != nil {
|
f.Close()
|
||||||
log.Println("> Can't load configuration file: ", err)
|
} else {
|
||||||
|
this.json = `{}`
|
||||||
}
|
}
|
||||||
if err := c.loadMimeType(filepath.Join(c.Runtime.ConfigPath, "mime.json")); err != nil {
|
if gjson.Valid(this.json) == true {
|
||||||
log.Println("> Can't load mimetype config")
|
this.reader = gjson.Parse(this.json)
|
||||||
}
|
}
|
||||||
go c.ChangeListener()
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) loadConfig(path string) error {
|
func (this *Config) Get(path string) *Config {
|
||||||
file, err := os.Open(path)
|
this.path = &path
|
||||||
defer file.Close()
|
return this
|
||||||
if err != nil {
|
|
||||||
c = &Config{}
|
|
||||||
log.Println("can't load config file: ", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
decoder := json.NewDecoder(file)
|
|
||||||
err = decoder.Decode(&c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.populateDefault(path)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) ChangeListener() {
|
func (this *Config) Default(value interface{}) *Config {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
if this.path == nil {
|
||||||
if err != nil {
|
return this
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
defer watcher.Close()
|
|
||||||
done := make(chan bool)
|
if val := this.reader.Get(*this.path).Value(); val == nil {
|
||||||
go func() {
|
this.mu.Lock()
|
||||||
for {
|
this.json, _ = sjson.Set(this.json, *this.path, value)
|
||||||
select {
|
this.reader = gjson.Parse(this.json)
|
||||||
case event := <-watcher.Events:
|
this.save()
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
this.mu.Unlock()
|
||||||
config_path := filepath.Join(c.Runtime.ConfigPath, "config.json")
|
}
|
||||||
if err = c.loadConfig(config_path); err != nil {
|
return this
|
||||||
log.Println("can't load config file")
|
|
||||||
} else {
|
|
||||||
c.populateDefault(config_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_ = watcher.Add(c.Runtime.ConfigPath)
|
|
||||||
<-done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) populateDefault(path string) {
|
func (this *Config) Set(value interface{}) *Config {
|
||||||
if c.General.Port == 0 {
|
if this.path == nil {
|
||||||
c.General.Port = 8334
|
return this
|
||||||
}
|
}
|
||||||
if c.General.Name == "" {
|
|
||||||
c.General.Name = "Nuage"
|
this.mu.Lock()
|
||||||
|
this.json, _ = sjson.Set(this.json, *this.path, value)
|
||||||
|
this.reader = gjson.Parse(this.json)
|
||||||
|
this.save()
|
||||||
|
this.mu.Unlock()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Config) String() string {
|
||||||
|
return this.reader.Get(*this.path).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Config) Int() int {
|
||||||
|
val := this.reader.Get(*this.path).Value()
|
||||||
|
switch val.(type) {
|
||||||
|
case float64: return int(val.(float64))
|
||||||
|
case int64: return int(val.(int64))
|
||||||
|
case int: return val.(int)
|
||||||
}
|
}
|
||||||
if c.General.SecretKey == "" {
|
return 0
|
||||||
c.General.SecretKey = RandomString(16)
|
}
|
||||||
j, err := json.MarshalIndent(c, "", " ")
|
|
||||||
if err == nil {
|
func (this Config) Bool() bool {
|
||||||
f, err := os.OpenFile(path, os.O_WRONLY, os.ModePerm)
|
val := this.reader.Get(*this.path).Value()
|
||||||
if err == nil {
|
switch val.(type) {
|
||||||
f.Write(j)
|
case bool: return val.(bool)
|
||||||
f.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if c.OAuthProvider.Dropbox.ClientID == "" {
|
return false
|
||||||
c.OAuthProvider.Dropbox.ClientID = os.Getenv("DROPBOX_CLIENT_ID")
|
}
|
||||||
|
|
||||||
|
func (this Config) Interface() interface{} {
|
||||||
|
return this.reader.Get(*this.path).Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Config) save() {
|
||||||
|
if gjson.Valid(this.json) == false {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if c.OAuthProvider.GoogleDrive.ClientID == "" {
|
if f, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE, os.ModePerm); err == nil {
|
||||||
c.OAuthProvider.GoogleDrive.ClientID = os.Getenv("GDRIVE_CLIENT_ID")
|
buf := bytes.NewBuffer(PrettyPrint([]byte(this.json)))
|
||||||
}
|
io.Copy(f, buf)
|
||||||
if c.OAuthProvider.GoogleDrive.ClientSecret == "" {
|
f.Close()
|
||||||
c.OAuthProvider.GoogleDrive.ClientSecret = os.Getenv("GDRIVE_CLIENT_SECRET")
|
|
||||||
}
|
|
||||||
if c.General.Host == "" {
|
|
||||||
c.General.Host = os.Getenv("APPLICATION_URL")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Export() (string, error) {
|
func (this Config) Scan(p interface{}) error {
|
||||||
|
content := this.reader.Get(*this.path).String()
|
||||||
|
|
||||||
|
return json.Unmarshal([]byte(content), &p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Config) Export() (string, error) {
|
||||||
publicConf := struct {
|
publicConf := struct {
|
||||||
Editor string `json:"editor"`
|
Editor string `json:"editor"`
|
||||||
ForkButton bool `json:"fork_button"`
|
ForkButton bool `json:"fork_button"`
|
||||||
DisplayHidden bool `json:"display_hidden"`
|
DisplayHidden bool `json:"display_hidden"`
|
||||||
AutoConnect bool `json:"auto_connect"`
|
AutoConnect bool `json:"auto_connect"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
RememberMe *bool `json:"remember_me"`
|
RememberMe bool `json:"remember_me"`
|
||||||
Connections interface{} `json:"connections"`
|
Connections interface{} `json:"connections"`
|
||||||
MimeTypes map[string]string `json:"mime"`
|
MimeTypes map[string]string `json:"mime"`
|
||||||
}{
|
}{
|
||||||
Editor: c.General.Editor,
|
Editor: this.Get("general.editor").String(),
|
||||||
ForkButton: c.General.ForkButton,
|
ForkButton: this.Get("general.fork_button").Bool(),
|
||||||
DisplayHidden: c.General.DisplayHidden,
|
DisplayHidden: this.Get("general.display_hidden").Bool(),
|
||||||
AutoConnect: c.General.AutoConnect,
|
AutoConnect: this.Get("general.auto_connect").Bool(),
|
||||||
Connections: c.Connections,
|
Name: this.Get("general.name").String(),
|
||||||
MimeTypes: c.MimeTypes,
|
RememberMe: this.Get("general.remember_me").Bool(),
|
||||||
Name: c.General.Name,
|
Connections: this.Get("connections").Interface(),
|
||||||
RememberMe: c.General.RememberMe,
|
MimeTypes: AllMimeTypes(),
|
||||||
}
|
}
|
||||||
j, err := json.Marshal(publicConf)
|
j, err := json.Marshal(publicConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -200,13 +190,3 @@ func (c *Config) Export() (string, error) {
|
|||||||
}
|
}
|
||||||
return string(j), nil
|
return string(j), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) loadMimeType(path string) error {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
defer file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
decoder := json.NewDecoder(file)
|
|
||||||
return decoder.Decode(&c.MimeTypes)
|
|
||||||
}
|
|
||||||
|
|||||||
77
server/common/config_test.go
Normal file
77
server/common/config_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigGet(t *testing.T) {
|
||||||
|
assert.Equal(t, nil, NewConfig().Get("foo").Interface())
|
||||||
|
assert.Equal(t, nil, NewConfig().Get("foo.bar").Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigDefault(t *testing.T) {
|
||||||
|
c := NewConfig()
|
||||||
|
assert.Equal(t, "test", c.Get("foo.bar").Default("test").String())
|
||||||
|
assert.Equal(t, "test", c.Get("foo.bar").String())
|
||||||
|
assert.Equal(t, "test", c.Get("foo.bar").Default("nope").String())
|
||||||
|
assert.Equal(t, "nope", c.Get("foo.bar.test").Default("nope").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigTypeCase(t *testing.T) {
|
||||||
|
assert.Equal(t, nil, NewConfig().Get("foo.bar.nil").Default(nil).Interface())
|
||||||
|
assert.Equal(t, true, NewConfig().Get("foo.bar").Default(true).Bool())
|
||||||
|
assert.Equal(t, 10, NewConfig().Get("foo.bar").Default(10).Int())
|
||||||
|
assert.Equal(t, "test", NewConfig().Get("foo.bar").Default("test").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigSet(t *testing.T) {
|
||||||
|
c := NewConfig()
|
||||||
|
assert.Equal(t, "test", c.Get("foo.bar").Set("test").String())
|
||||||
|
assert.Equal(t, "valu", c.Get("foo.bar").Set("valu").String())
|
||||||
|
assert.Equal(t, "valu", c.Get("foo.bar.test.bar.foo").Set("valu").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigScan(t *testing.T) {
|
||||||
|
c := NewConfig()
|
||||||
|
c.Get("foo.bar").Default("test")
|
||||||
|
c.Get("foo.bar2").Default(32)
|
||||||
|
c.Get("foo.bar3").Default(true)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
Bar2 int `json:"bar2"`
|
||||||
|
Bar3 bool `json:"bar3"`
|
||||||
|
}
|
||||||
|
c.Get("foo").Scan(&data)
|
||||||
|
assert.Equal(t, "test", data.Bar)
|
||||||
|
assert.Equal(t, 32, data.Bar2)
|
||||||
|
assert.Equal(t, true, data.Bar3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigSlice(t *testing.T) {
|
||||||
|
c := NewConfig()
|
||||||
|
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "test0", "label": "test0"})
|
||||||
|
c.Get("connections.-1").Set(map[string]interface{}{"type": "test1", "label": "Test1"})
|
||||||
|
|
||||||
|
var data []struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
}
|
||||||
|
c.Get("connections").Scan(&data)
|
||||||
|
assert.Equal(t, 2, len(data))
|
||||||
|
assert.Equal(t, "test0", data[0].Type)
|
||||||
|
assert.Equal(t, "test0", data[0].Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetConfigElement(b *testing.B) {
|
||||||
|
c := NewConfig()
|
||||||
|
c.Get("foo.bar.test.foo").Set("test")
|
||||||
|
c.Get("foo.bar.test.bar.foo").Set("valu")
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
c.Get("foo").String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
APP_VERSION = "v0.3"
|
||||||
|
CONFIG_PATH = "data/config/"
|
||||||
COOKIE_NAME_AUTH = "auth"
|
COOKIE_NAME_AUTH = "auth"
|
||||||
COOKIE_NAME_PROOF = "proof"
|
COOKIE_NAME_PROOF = "proof"
|
||||||
COOKIE_PATH = "/api/"
|
COOKIE_PATH = "/api/"
|
||||||
|
|||||||
@ -138,6 +138,7 @@ func verify(something []byte) ([]byte, error) {
|
|||||||
return something, nil
|
return something, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a unique ID that can be use to identify different session
|
||||||
func GenerateID(params map[string]string) string {
|
func GenerateID(params map[string]string) string {
|
||||||
p := "type =>" + params["type"]
|
p := "type =>" + params["type"]
|
||||||
p += "host =>" + params["host"]
|
p += "host =>" + params["host"]
|
||||||
|
|||||||
@ -35,8 +35,9 @@ func TestIDGeneration(t *testing.T) {
|
|||||||
|
|
||||||
func TestStringGeneration(t *testing.T) {
|
func TestStringGeneration(t *testing.T) {
|
||||||
str := QuickString(10)
|
str := QuickString(10)
|
||||||
str1 := QuickString(10)
|
str1 := QuickString(16)
|
||||||
str2 := QuickString(10)
|
str2 := QuickString(24)
|
||||||
assert.Equal(t, len(str), 10)
|
assert.Equal(t, len(str), 10)
|
||||||
t.Log(str, str1, str2)
|
assert.Equal(t, len(str1), 16)
|
||||||
|
assert.Equal(t, len(str2), 24)
|
||||||
}
|
}
|
||||||
|
|||||||
33
server/common/default.go
Normal file
33
server/common/default.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var HTTPClient = http.Client{
|
||||||
|
Timeout: 5 * time.Hour,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
KeepAlive: 10 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
IdleConnTimeout: 60 * time.Second,
|
||||||
|
ResponseHeaderTimeout: 60 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var HTTP = http.Client{
|
||||||
|
Timeout: 800 * time.Millisecond,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 500 * time.Millisecond,
|
||||||
|
KeepAlive: 500 * time.Millisecond,
|
||||||
|
}).Dial,
|
||||||
|
TLSHandshakeTimeout: 500 * time.Millisecond,
|
||||||
|
IdleConnTimeout: 500 * time.Millisecond,
|
||||||
|
ResponseHeaderTimeout: 500 * time.Millisecond,
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,5 +1,19 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetCurrentDir() string {
|
||||||
|
ex, _ := os.Executable()
|
||||||
|
return filepath.Dir(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAbsolutePath(p string) string {
|
||||||
|
return filepath.Join(GetCurrentDir(), p)
|
||||||
|
}
|
||||||
|
|
||||||
func IsDirectory(path string) bool {
|
func IsDirectory(path string) bool {
|
||||||
if string(path[len(path)-1]) != "/" {
|
if string(path[len(path)-1]) != "/" {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Helpers struct {
|
|
||||||
AbsolutePath func(p string) string
|
|
||||||
MimeType func(p string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHelpers(config *Config) *Helpers {
|
|
||||||
return &Helpers{
|
|
||||||
MimeType: mimeType(config),
|
|
||||||
AbsolutePath: absolutePath(config),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func absolutePath(c *Config) func(p string) string {
|
|
||||||
return func(p string) string {
|
|
||||||
return filepath.Join(c.Runtime.Dirname, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mimeType(c *Config) func(p string) string {
|
|
||||||
return func(p string) string {
|
|
||||||
ext := filepath.Ext(p)
|
|
||||||
if ext != "" {
|
|
||||||
ext = ext[1:]
|
|
||||||
}
|
|
||||||
ext = strings.ToLower(ext)
|
|
||||||
mType := c.MimeTypes[ext]
|
|
||||||
if mType == "" {
|
|
||||||
return "application/octet-stream"
|
|
||||||
}
|
|
||||||
return mType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,13 +2,7 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
"log"
|
slog "log"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LOG_INFO = "INFO"
|
|
||||||
LOG_WARNING = "WARNING"
|
|
||||||
LOG_ERROR = "ERROR"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogEntry struct {
|
type LogEntry struct {
|
||||||
@ -27,34 +21,76 @@ type LogEntry struct {
|
|||||||
Backend string `json:"backend"`
|
Backend string `json:"backend"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Log(ctx *App, str string, level string){
|
type log struct{
|
||||||
if ctx.Config.Log.Enable == false {
|
enable bool
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldDisplay := func(r string, l string) bool {
|
debug bool
|
||||||
levels := []string{"DEBUG", "INFO", "WARNING", "ERROR"}
|
info bool
|
||||||
|
warn bool
|
||||||
|
error bool
|
||||||
|
}
|
||||||
|
|
||||||
configLevel := -1
|
func (l *log) Info(str string) {
|
||||||
currentLevel := 0
|
if l.info && l.enable {
|
||||||
|
slog.Printf("INFO %s\n", str)
|
||||||
for i:=0; i <= len(levels); i++ {
|
|
||||||
if levels[i] == l {
|
|
||||||
currentLevel = i
|
|
||||||
}
|
|
||||||
if levels[i] == r {
|
|
||||||
configLevel = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentLevel <= configLevel {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}(ctx.Config.Log.Level, level)
|
|
||||||
|
|
||||||
if shouldDisplay {
|
|
||||||
log.Printf("%s %s\n", level, str)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *log) Warning(str string) {
|
||||||
|
if l.warn && l.enable {
|
||||||
|
slog.Printf("WARNING %s\n", str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *log) Error(str string) {
|
||||||
|
if l.error && l.enable {
|
||||||
|
slog.Printf("ERROR %s\n", str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *log) Debug(str string) {
|
||||||
|
if l.debug && l.enable {
|
||||||
|
slog.Printf("DEBUG %s\n", str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *log) SetVisibility(str string) {
|
||||||
|
switch str {
|
||||||
|
case "WARN":
|
||||||
|
l.debug = false
|
||||||
|
l.info = false
|
||||||
|
l.warn = true
|
||||||
|
l.error = true
|
||||||
|
case "ERROR":
|
||||||
|
l.debug = false
|
||||||
|
l.info = false
|
||||||
|
l.warn = false
|
||||||
|
l.error = true
|
||||||
|
case "DEBUG":
|
||||||
|
l.debug = true
|
||||||
|
l.info = true
|
||||||
|
l.warn = true
|
||||||
|
l.error = true
|
||||||
|
case "INFO":
|
||||||
|
l.debug = false
|
||||||
|
l.info = true
|
||||||
|
l.warn = true
|
||||||
|
l.error = true
|
||||||
|
default:
|
||||||
|
l.debug = false
|
||||||
|
l.info = true
|
||||||
|
l.warn = true
|
||||||
|
l.error = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func(l *log) Enable(val bool) {
|
||||||
|
l.enable = val
|
||||||
|
}
|
||||||
|
|
||||||
|
var Log = func () log {
|
||||||
|
l := log{}
|
||||||
|
l.SetVisibility("DEBUG")
|
||||||
|
l.Enable(true)
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|||||||
37
server/common/mime.go
Normal file
37
server/common/mime.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MimeTypes map[string]string
|
||||||
|
|
||||||
|
func init(){
|
||||||
|
path := filepath.Join(GetCurrentDir(), CONFIG_PATH + "mime.json")
|
||||||
|
if f, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm); err == nil {
|
||||||
|
j, _ := ioutil.ReadAll(f)
|
||||||
|
json.Unmarshal(j, &MimeTypes)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMimeType(p string) string {
|
||||||
|
ext := filepath.Ext(p)
|
||||||
|
if ext != "" {
|
||||||
|
ext = ext[1:]
|
||||||
|
}
|
||||||
|
ext = strings.ToLower(ext)
|
||||||
|
mType := MimeTypes[ext]
|
||||||
|
if mType == "" {
|
||||||
|
return "application/octet-stream"
|
||||||
|
}
|
||||||
|
return mType
|
||||||
|
}
|
||||||
|
|
||||||
|
func AllMimeTypes() map[string]string {
|
||||||
|
return MimeTypes
|
||||||
|
}
|
||||||
11
server/common/plugin.go
Normal file
11
server/common/plugin.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
type Plugin struct {
|
||||||
|
Type string
|
||||||
|
Call interface{}
|
||||||
|
Priority int
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PROCESS_FILE_CONTENT_BEFORE_SEND = "PROCESS_FILE_CONTENT_BEFORE_SEND"
|
||||||
|
)
|
||||||
@ -7,6 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type IBackend interface {
|
type IBackend interface {
|
||||||
|
Init(params map[string]string, app *App) (IBackend, error)
|
||||||
Ls(path string) ([]os.FileInfo, error)
|
Ls(path string) ([]os.FileInfo, error)
|
||||||
Cat(path string) (io.Reader, error)
|
Cat(path string) (io.Reader, error)
|
||||||
Mkdir(path string) error
|
Mkdir(path string) error
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
func NewBool(t bool) *bool {
|
func NewBool(t bool) *bool {
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
@ -21,6 +26,7 @@ func NewBoolFromInterface(val interface{}) bool {
|
|||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInt64pFromInterface(val interface{}) *int64 {
|
func NewInt64pFromInterface(val interface{}) *int64 {
|
||||||
switch val.(type) {
|
switch val.(type) {
|
||||||
case int64:
|
case int64:
|
||||||
@ -32,6 +38,7 @@ func NewInt64pFromInterface(val interface{}) *int64 {
|
|||||||
default: return nil
|
default: return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStringpFromInterface(val interface{}) *string {
|
func NewStringpFromInterface(val interface{}) *string {
|
||||||
switch val.(type) {
|
switch val.(type) {
|
||||||
case string:
|
case string:
|
||||||
@ -49,3 +56,13 @@ func NewStringFromInterface(val interface{}) string {
|
|||||||
default: return ""
|
default: return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PrettyPrint(json_dirty []byte) []byte {
|
||||||
|
var json_pretty bytes.Buffer
|
||||||
|
error := json.Indent(&json_pretty, json_dirty, "", " ")
|
||||||
|
if error != nil {
|
||||||
|
return json_dirty
|
||||||
|
}
|
||||||
|
json_pretty.Write([]byte("\n"))
|
||||||
|
return json_pretty.Bytes()
|
||||||
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package ctrl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/mickael-kerjean/nuage/server/common"
|
. "github.com/mickael-kerjean/nuage/server/common"
|
||||||
"github.com/mickael-kerjean/nuage/server/services"
|
|
||||||
"github.com/mickael-kerjean/nuage/server/model"
|
"github.com/mickael-kerjean/nuage/server/model"
|
||||||
|
"github.com/mickael-kerjean/nuage/server/plugin"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -110,10 +110,17 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = services.ProcessFileBeforeSend(file, &ctx, req, &res)
|
mType := GetMimeType(req.URL.Query().Get("path"))
|
||||||
if err != nil {
|
res.Header().Set("Content-Type", mType)
|
||||||
SendErrorResult(res, err)
|
|
||||||
return
|
for _, obj := range plugin.ProcessFileContentBeforeSend() {
|
||||||
|
if obj == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file, err = obj(file, &ctx, &res, req); err != nil {
|
||||||
|
SendErrorResult(res, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
io.Copy(res, file)
|
io.Copy(res, file)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,8 +69,7 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {
|
|||||||
SendErrorResult(res, NewError(err.Error(), 500))
|
SendErrorResult(res, NewError(err.Error(), 500))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
obfuscate, err := EncryptString(ctx.Config.General.SecretKey, string(s))
|
obfuscate, err := EncryptString(ctx.Config.Get("general.secret_key").String(), string(s))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SendErrorResult(res, NewError(err.Error(), 500))
|
SendErrorResult(res, NewError(err.Error(), 500))
|
||||||
return
|
return
|
||||||
|
|||||||
@ -49,7 +49,7 @@ func ShareUpsert(ctx App, res http.ResponseWriter, req *http.Request) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var data map[string]string
|
var data map[string]string
|
||||||
str, err := DecryptString(ctx.Config.General.SecretKey, c.Value)
|
str, err := DecryptString(ctx.Config.Get("general.secret_key").String(), c.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ func ShareUpsert(ctx App, res http.ResponseWriter, req *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
obfuscate, err := EncryptString(ctx.Config.General.SecretKey, string(s))
|
obfuscate, err := EncryptString(ctx.Config.Get("general.secret_key").String(), string(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ func ShareVerifyProof(ctx App, res http.ResponseWriter, req *http.Request) {
|
|||||||
Name: COOKIE_NAME_PROOF,
|
Name: COOKIE_NAME_PROOF,
|
||||||
Value: func(p []model.Proof) string {
|
Value: func(p []model.Proof) string {
|
||||||
j, _ := json.Marshal(p)
|
j, _ := json.Marshal(p)
|
||||||
str, _ := EncryptString(ctx.Config.General.SecretKey, string(j))
|
str, _ := EncryptString(ctx.Config.Get("general.secret_key").String(), string(j))
|
||||||
return str
|
return str
|
||||||
}(verifiedProof),
|
}(verifiedProof),
|
||||||
Path: COOKIE_PATH,
|
Path: COOKIE_PATH,
|
||||||
|
|||||||
@ -22,7 +22,7 @@ func StaticHandler(_path string, ctx App) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
absPath := ctx.Helpers.AbsolutePath(_path)
|
absPath := GetAbsolutePath(_path)
|
||||||
fsrv := http.FileServer(http.Dir(absPath))
|
fsrv := http.FileServer(http.Dir(absPath))
|
||||||
_, err := os.Open(path.Join(absPath, req.URL.Path+".gz"))
|
_, err := os.Open(path.Join(absPath, req.URL.Path+".gz"))
|
||||||
if err == nil && strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
if err == nil && strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
@ -45,11 +45,11 @@ func DefaultHandler(_path string, ctx App) http.Handler {
|
|||||||
SecureHeader(&header)
|
SecureHeader(&header)
|
||||||
|
|
||||||
p := _path
|
p := _path
|
||||||
if _, err := os.Open(path.Join(ctx.Config.Runtime.Dirname, p+".gz")); err == nil && strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
if _, err := os.Open(path.Join(GetCurrentDir(), p+".gz")); err == nil && strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
res.Header().Set("Content-Encoding", "gzip")
|
res.Header().Set("Content-Encoding", "gzip")
|
||||||
p += ".gz"
|
p += ".gz"
|
||||||
}
|
}
|
||||||
http.ServeFile(res, req, ctx.Helpers.AbsolutePath(p))
|
http.ServeFile(res, req, GetAbsolutePath(p))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,13 +2,13 @@ package ctrl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"log"
|
|
||||||
. "github.com/mickael-kerjean/nuage/server/common"
|
. "github.com/mickael-kerjean/nuage/server/common"
|
||||||
. "github.com/mickael-kerjean/nuage/server/middleware"
|
. "github.com/mickael-kerjean/nuage/server/middleware"
|
||||||
"github.com/mickael-kerjean/nuage/server/model"
|
"github.com/mickael-kerjean/nuage/server/model"
|
||||||
"github.com/mickael-kerjean/net/webdav"
|
"github.com/mickael-kerjean/net/webdav"
|
||||||
"github.com/mickael-kerjean/mux"
|
"github.com/mickael-kerjean/mux"
|
||||||
"time"
|
"time"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var start time.Time = time.Now()
|
var start time.Time = time.Now()
|
||||||
@ -40,7 +40,8 @@ func WebdavHandler(ctx App, res http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// webdav is WIP
|
// webdav is WIP
|
||||||
return http.NotFound(res, req)
|
http.NotFound(res, req)
|
||||||
|
return
|
||||||
|
|
||||||
h := &webdav.Handler{
|
h := &webdav.Handler{
|
||||||
Prefix: "/s/" + share_id,
|
Prefix: "/s/" + share_id,
|
||||||
@ -53,12 +54,7 @@ func WebdavHandler(ctx App, res http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
return "OK"
|
return "OK"
|
||||||
}(err)
|
}(err)
|
||||||
log.Printf("INFO %s WEBDAV %s %s %s", share_id, req.Method, req.URL.Path, e)
|
Log.Info(fmt.Sprintf("INFO %s WEBDAV %s %s %s", share_id, req.Method, req.URL.Path, e))
|
||||||
elapsed := time.Now().Sub(start)
|
|
||||||
if elapsed > 1000*time.Millisecond {
|
|
||||||
log.Println("\n\n\n")
|
|
||||||
}
|
|
||||||
start = time.Now()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
h.ServeHTTP(res, req)
|
h.ServeHTTP(res, req)
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/mickael-kerjean/mux"
|
"github.com/mickael-kerjean/mux"
|
||||||
. "github.com/mickael-kerjean/nuage/server/common"
|
. "github.com/mickael-kerjean/nuage/server/common"
|
||||||
. "github.com/mickael-kerjean/nuage/server/ctrl"
|
. "github.com/mickael-kerjean/nuage/server/ctrl"
|
||||||
. "github.com/mickael-kerjean/nuage/server/middleware"
|
. "github.com/mickael-kerjean/nuage/server/middleware"
|
||||||
"log"
|
_ "github.com/mickael-kerjean/nuage/server/plugin"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@ -13,7 +14,6 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
app := App{}
|
app := App{}
|
||||||
app.Config = NewConfig()
|
app.Config = NewConfig()
|
||||||
app.Helpers = NewHelpers(app.Config)
|
|
||||||
Init(&app)
|
Init(&app)
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
@ -53,15 +53,15 @@ func Init(a *App) *http.Server {
|
|||||||
r.PathPrefix("/").Handler(DefaultHandler(FILE_INDEX, *a)).Methods("GET")
|
r.PathPrefix("/").Handler(DefaultHandler(FILE_INDEX, *a)).Methods("GET")
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: ":" + strconv.Itoa(a.Config.General.Port),
|
Addr: ":" + strconv.Itoa(a.Config.Get("general.port").Int()),
|
||||||
Handler: r,
|
Handler: r,
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
log.Fatal("SERVER START ERROR ", err)
|
Log.Error(fmt.Sprintf("server start: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("SERVER START OK")
|
Log.Info("Server start")
|
||||||
}()
|
}()
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,12 +26,14 @@ func APIHandler(fn func(App, http.ResponseWriter, *http.Request), ctx App) http.
|
|||||||
fn(ctx, &resw, req)
|
fn(ctx, &resw, req)
|
||||||
req.Body.Close()
|
req.Body.Close()
|
||||||
|
|
||||||
if ctx.Config.Log.Telemetry {
|
go func() {
|
||||||
go telemetry(req, &resw, start, ctx.Backend.Info())
|
if ctx.Config.Get("log.telemetry").Bool() {
|
||||||
}
|
go telemetry(req, &resw, start, ctx.Backend.Info())
|
||||||
if ctx.Config.Log.Enable {
|
}
|
||||||
go logger(req, &resw, start)
|
if ctx.Config.Get("log.enable").Bool() {
|
||||||
}
|
go logger(req, &resw, start)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +101,7 @@ func ExtractSession(req *http.Request, ctx *App) (map[string]string, error) {
|
|||||||
str = cookie.Value
|
str = cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
str, _ = DecryptString(ctx.Config.General.SecretKey, str)
|
str, _ = DecryptString(ctx.Config.Get("general.secret_key").String(), str)
|
||||||
err := json.Unmarshal([]byte(str), &res)
|
err := json.Unmarshal([]byte(str), &res)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
package backend
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/mickael-kerjean/nuage/server/common"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomBackend struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCustomBackend(params map[string]string, app *App) (*CustomBackend, error) {
|
|
||||||
return &CustomBackend{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b CustomBackend) Info() string {
|
|
||||||
return "N/A"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b CustomBackend) Ls(path string) ([]os.FileInfo, error) {
|
|
||||||
return nil, NewError("", 401)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b CustomBackend) Cat(path string) (io.Reader, error) {
|
|
||||||
return strings.NewReader(""), NewError("", 401)
|
|
||||||
}
|
|
||||||
func (b CustomBackend) Mkdir(path string) error {
|
|
||||||
return NewError("", 401)
|
|
||||||
}
|
|
||||||
func (b CustomBackend) Rm(path string) error {
|
|
||||||
return NewError("", 401)
|
|
||||||
}
|
|
||||||
func (b CustomBackend) Mv(from string, to string) error {
|
|
||||||
return NewError("", 401)
|
|
||||||
}
|
|
||||||
func (b CustomBackend) Touch(path string) error {
|
|
||||||
return NewError("", 401)
|
|
||||||
}
|
|
||||||
func (b CustomBackend) Save(path string, file io.Reader) error {
|
|
||||||
return NewError("", 401)
|
|
||||||
}
|
|
||||||
@ -19,10 +19,18 @@ type Dropbox struct {
|
|||||||
Bearer string
|
Bearer string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDropbox(params map[string]string, app *App) (IBackend, error) {
|
func init() {
|
||||||
backend := Dropbox{}
|
Backend.Register("dropbox", Dropbox{})
|
||||||
backend.ClientId = app.Config.OAuthProvider.Dropbox.ClientID
|
}
|
||||||
backend.Hostname = app.Config.General.Host
|
|
||||||
|
func (d Dropbox) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
|
backend := &Dropbox{}
|
||||||
|
if env := os.Getenv("DROPBOX_CLIENT_ID"); env != "" {
|
||||||
|
backend.ClientId = env
|
||||||
|
} else {
|
||||||
|
backend.ClientId = app.Config.Get("oauth.dropbox.client_id").Default("").String()
|
||||||
|
}
|
||||||
|
backend.Hostname = app.Config.Get("general.host").String()
|
||||||
backend.Bearer = params["bearer"]
|
backend.Bearer = params["bearer"]
|
||||||
|
|
||||||
if backend.ClientId == "" {
|
if backend.ClientId == "" {
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import (
|
|||||||
var FtpCache AppCache
|
var FtpCache AppCache
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Backend.Register("ftp", Ftp{})
|
||||||
|
|
||||||
FtpCache = NewAppCache(2, 1)
|
FtpCache = NewAppCache(2, 1)
|
||||||
FtpCache.OnEvict(func(key string, value interface{}) {
|
FtpCache.OnEvict(func(key string, value interface{}) {
|
||||||
c := value.(*Ftp)
|
c := value.(*Ftp)
|
||||||
@ -25,7 +27,7 @@ type Ftp struct {
|
|||||||
client *goftp.Client
|
client *goftp.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFtp(params map[string]string, app *App) (IBackend, error) {
|
func (f Ftp) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
c := FtpCache.Get(params)
|
c := FtpCache.Get(params)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
d := c.(*Ftp)
|
d := c.(*Ftp)
|
||||||
|
|||||||
@ -22,22 +22,29 @@ type GDrive struct {
|
|||||||
Config *oauth2.Config
|
Config *oauth2.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGDrive(params map[string]string, app *App) (IBackend, error) {
|
func init() {
|
||||||
|
Backend.Register("gdrive", GDrive{})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (g GDrive) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
backend := GDrive{}
|
backend := GDrive{}
|
||||||
if app.Config.OAuthProvider.GoogleDrive.ClientID == "" {
|
|
||||||
return backend, NewError("Missing Client ID: Contact your admin", 502)
|
|
||||||
} else if app.Config.OAuthProvider.GoogleDrive.ClientSecret == "" {
|
|
||||||
return backend, NewError("Missing Client Secret: Contact your admin", 502)
|
|
||||||
} else if app.Config.General.Host == "" {
|
|
||||||
return backend, NewError("Missing Hostname: Contact your admin", 502)
|
|
||||||
}
|
|
||||||
config := &oauth2.Config{
|
config := &oauth2.Config{
|
||||||
Endpoint: google.Endpoint,
|
Endpoint: google.Endpoint,
|
||||||
ClientID: app.Config.OAuthProvider.GoogleDrive.ClientID,
|
ClientID: app.Config.Get("oauth.gdrive.client_id").Default(os.Getenv("GDRIVE_CLIENT_ID")).String(),
|
||||||
ClientSecret: app.Config.OAuthProvider.GoogleDrive.ClientSecret,
|
ClientSecret: app.Config.Get("oauth.gdrive.client_secret").Default(os.Getenv("GDRIVE_CLIENT_SECRET")).String(),
|
||||||
RedirectURL: app.Config.General.Host + "/login",
|
RedirectURL: app.Config.Get("general.host").String() + "/login",
|
||||||
Scopes: []string{"https://www.googleapis.com/auth/drive"},
|
Scopes: []string{"https://www.googleapis.com/auth/drive"},
|
||||||
}
|
}
|
||||||
|
if config.ClientID == "" {
|
||||||
|
return backend, NewError("Missing Client ID: Contact your admin", 502)
|
||||||
|
} else if config.ClientSecret == "" {
|
||||||
|
return backend, NewError("Missing Client Secret: Contact your admin", 502)
|
||||||
|
} else if config.RedirectURL == "/login" {
|
||||||
|
return backend, NewError("Missing Hostname: Contact your admin", 502)
|
||||||
|
}
|
||||||
|
|
||||||
token := &oauth2.Token{
|
token := &oauth2.Token{
|
||||||
AccessToken: params["token"],
|
AccessToken: params["token"],
|
||||||
RefreshToken: params["refresh"],
|
RefreshToken: params["refresh"],
|
||||||
|
|||||||
@ -26,6 +26,8 @@ type Git struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Backend.Register("git", Git{})
|
||||||
|
|
||||||
GitCache = NewAppCache()
|
GitCache = NewAppCache()
|
||||||
cachePath := filepath.Join(GetCurrentDir(), GitCachePath)
|
cachePath := filepath.Join(GetCurrentDir(), GitCachePath)
|
||||||
os.RemoveAll(cachePath)
|
os.RemoveAll(cachePath)
|
||||||
@ -50,7 +52,7 @@ type GitParams struct {
|
|||||||
basePath string
|
basePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGit(params map[string]string, app *App) (*Git, error) {
|
func (git Git) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
if obj := GitCache.Get(params); obj != nil {
|
if obj := GitCache.Get(params); obj != nil {
|
||||||
return obj.(*Git), nil
|
return obj.(*Git), nil
|
||||||
}
|
}
|
||||||
@ -95,7 +97,7 @@ func NewGit(params map[string]string, app *App) (*Git, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hash := GenerateID(params)
|
hash := GenerateID(params)
|
||||||
p.basePath = app.Helpers.AbsolutePath(GitCachePath + "repo_" + fmt.Sprint(hash) + "/")
|
p.basePath = GetAbsolutePath(GitCachePath + "repo_" + fmt.Sprint(hash) + "/")
|
||||||
|
|
||||||
repo, err := g.git.open(p, p.basePath)
|
repo, err := g.git.open(p, p.basePath)
|
||||||
g.git.repo = repo
|
g.git.repo = repo
|
||||||
|
|||||||
@ -22,10 +22,12 @@ type S3Backend struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Backend.Register("s3", S3Backend{})
|
||||||
S3Cache = NewAppCache(2, 1)
|
S3Cache = NewAppCache(2, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewS3(params map[string]string, app *App) (IBackend, error) {
|
|
||||||
|
func (s S3Backend) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
if params["region"] == "" {
|
if params["region"] == "" {
|
||||||
params["region"] = "us-east-2"
|
params["region"] = "us-east-2"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,16 +18,16 @@ type Sftp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
SftpCache = NewAppCache()
|
Backend.Register("sftp", Sftp{})
|
||||||
|
|
||||||
|
SftpCache = NewAppCache()
|
||||||
SftpCache.OnEvict(func(key string, value interface{}) {
|
SftpCache.OnEvict(func(key string, value interface{}) {
|
||||||
c := value.(*Sftp)
|
c := value.(*Sftp)
|
||||||
c.Close()
|
c.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSftp(params map[string]string, app *App) (*Sftp, error) {
|
func (s Sftp) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
var s Sftp = Sftp{}
|
|
||||||
p := struct {
|
p := struct {
|
||||||
hostname string
|
hostname string
|
||||||
port string
|
port string
|
||||||
|
|||||||
@ -24,7 +24,11 @@ type WebDavParams struct {
|
|||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWebDav(params map[string]string, app *App) (IBackend, error) {
|
func init() {
|
||||||
|
Backend.Register("webdav", WebDav{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w WebDav) Init(params map[string]string, app *App) (IBackend, error) {
|
||||||
params["url"] = regexp.MustCompile(`\/$`).ReplaceAllString(params["url"], "")
|
params["url"] = regexp.MustCompile(`\/$`).ReplaceAllString(params["url"], "")
|
||||||
backend := WebDav{
|
backend := WebDav{
|
||||||
params: &WebDavParams{
|
params: &WebDavParams{
|
||||||
|
|||||||
@ -3,48 +3,37 @@ package model
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
. "github.com/mickael-kerjean/nuage/server/common"
|
. "github.com/mickael-kerjean/nuage/server/common"
|
||||||
"github.com/mickael-kerjean/nuage/server/model/backend"
|
_ "github.com/mickael-kerjean/nuage/server/model/backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBackend(ctx *App, conn map[string]string) (IBackend, error) {
|
func NewBackend(ctx *App, conn map[string]string) (IBackend, error) {
|
||||||
isAllowed := false
|
isAllowed := func() bool {
|
||||||
for i := range ctx.Config.Connections {
|
ret := false
|
||||||
if ctx.Config.Connections[i].Type == conn["type"] {
|
var conns [] struct {
|
||||||
if ctx.Config.Connections[i].Hostname == nil {
|
Type string `json:"type"`
|
||||||
isAllowed = true
|
Hostname string `json:"hostname"`
|
||||||
break;
|
Path string `json:"path"`
|
||||||
}else if *ctx.Config.Connections[i].Hostname == conn["hostname"] {
|
}
|
||||||
isAllowed = true
|
ctx.Config.Get("connections").Scan(&conns)
|
||||||
break;
|
for i := range conns {
|
||||||
|
if conns[i].Type == conn["type"] {
|
||||||
|
if conns[i].Hostname != "" && conns[i].Hostname != conn["hostname"] {
|
||||||
|
continue
|
||||||
|
} else if conns[i].Path != "" && conns[i].Path != conn["path"] {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
ret = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return ret
|
||||||
|
}()
|
||||||
|
|
||||||
if isAllowed == false {
|
if isAllowed == false {
|
||||||
return backend.NewNothing(conn, ctx)
|
return Backend.Get(BACKEND_NIL).Init(conn, ctx)
|
||||||
}
|
}
|
||||||
|
return Backend.Get(conn["type"]).Init(conn, ctx)
|
||||||
switch conn["type"] {
|
|
||||||
case "webdav":
|
|
||||||
return backend.NewWebDav(conn, ctx)
|
|
||||||
case "ftp":
|
|
||||||
return backend.NewFtp(conn, ctx)
|
|
||||||
case "sftp":
|
|
||||||
return backend.NewSftp(conn, ctx)
|
|
||||||
case "git":
|
|
||||||
return backend.NewGit(conn, ctx)
|
|
||||||
case "s3":
|
|
||||||
return backend.NewS3(conn, ctx)
|
|
||||||
case "dropbox":
|
|
||||||
return backend.NewDropbox(conn, ctx)
|
|
||||||
case "gdrive":
|
|
||||||
return backend.NewGDrive(conn, ctx)
|
|
||||||
case "custombackend":
|
|
||||||
return backend.NewCustomBackend(conn, ctx)
|
|
||||||
default:
|
|
||||||
return backend.NewNothing(conn, ctx)
|
|
||||||
}
|
|
||||||
return nil, NewError("Invalid backend type", 501)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHome(b IBackend) (string, error) {
|
func GetHome(b IBackend) (string, error) {
|
||||||
@ -56,6 +45,7 @@ func GetHome(b IBackend) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func MapStringInterfaceToMapStringString(m map[string]interface{}) map[string]string {
|
func MapStringInterfaceToMapStringString(m map[string]interface{}) map[string]string {
|
||||||
res := make(map[string]string)
|
res := make(map[string]string)
|
||||||
for key, value := range m {
|
for key, value := range m {
|
||||||
|
|||||||
@ -261,12 +261,22 @@ func ShareProofVerifier(ctx *App, s Share, proof Proof) (Proof, error) {
|
|||||||
p.Message = NewString("We've sent you a message with a verification code")
|
p.Message = NewString("We've sent you a message with a verification code")
|
||||||
|
|
||||||
// Send email
|
// Send email
|
||||||
addr := fmt.Sprintf("%s:%d", ctx.Config.Email.Server, ctx.Config.Email.Port)
|
addr := fmt.Sprintf(
|
||||||
|
"%s:%d",
|
||||||
|
ctx.Config.Get("email.server").String(),
|
||||||
|
ctx.Config.Get("email.port").Int(),
|
||||||
|
)
|
||||||
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
|
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
|
||||||
subject := "Subject: Your verification code\n"
|
subject := "Subject: Your verification code\n"
|
||||||
msg := []byte(subject + mime + "\n" + b.String())
|
msg := []byte(subject + mime + "\n" + b.String())
|
||||||
auth := smtp.PlainAuth("", ctx.Config.Email.Username, ctx.Config.Email.Password, ctx.Config.Email.Server)
|
auth := smtp.PlainAuth(
|
||||||
if err := smtp.SendMail(addr, auth, ctx.Config.Email.From, []string{"mickael@kerjean.me"}, msg); err != nil {
|
"",
|
||||||
|
ctx.Config.Get("email.username").String(),
|
||||||
|
ctx.Config.Get("email.password").String(),
|
||||||
|
ctx.Config.Get("email.server").String(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := smtp.SendMail(addr, auth, ctx.Config.Get("email.from").String(), []string{proof.Value}, msg); err != nil {
|
||||||
log.Println("ERROR: ", err)
|
log.Println("ERROR: ", err)
|
||||||
log.Println("Verification code: " + code)
|
log.Println("Verification code: " + code)
|
||||||
return p, NewError("Couldn't send email", 500)
|
return p, NewError("Couldn't send email", 500)
|
||||||
@ -317,7 +327,7 @@ func ShareProofGetAlreadyVerified(req *http.Request, ctx *App) []Proof {
|
|||||||
if len(cookieValue) > 500 {
|
if len(cookieValue) > 500 {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
j, err := DecryptString(ctx.Config.General.SecretKey, cookieValue)
|
j, err := DecryptString(ctx.Config.Get("general.secret_key").String(), cookieValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|||||||
3
server/plugin/example/build.sh
Executable file
3
server/plugin/example/build.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
go build -buildmode=plugin -o ../../../dist/data/plugin/example.so index.go
|
||||||
24
server/plugin/example/index.go
Normal file
24
server/plugin/example/index.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/mickael-kerjean/nuage/server/common"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(config *Config) []Plugin {
|
||||||
|
config.Get("plugins.example.foo").Default("bar")
|
||||||
|
|
||||||
|
return []Plugin{
|
||||||
|
{
|
||||||
|
Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle
|
||||||
|
Call: hook, // actual function we trigger
|
||||||
|
Priority: 5, // determine execution order whilst multiple plugin type
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hook(file io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){
|
||||||
|
Log.Info("example plugin with config: '" + ctx.Config.Get("plugins.example.foo").String() + "'")
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
3
server/plugin/image/build.sh
Executable file
3
server/plugin/image/build.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
go build -buildmode=plugin -o ../../../dist/data/plugin/image.so index.go
|
||||||
109
server/plugin/image/index.go
Normal file
109
server/plugin/image/index.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/mickael-kerjean/nuage/server/common"
|
||||||
|
"github.com/mickael-kerjean/nuage/server/plugin/image/lib"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ImageCachePath = "data/cache/image/"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(conf *Config) []Plugin {
|
||||||
|
conf.Get("plugins.transcoder.image.enable").Default(true)
|
||||||
|
|
||||||
|
cachePath := filepath.Join(GetCurrentDir(), ImageCachePath)
|
||||||
|
os.RemoveAll(cachePath)
|
||||||
|
os.MkdirAll(cachePath, os.ModePerm)
|
||||||
|
|
||||||
|
return []Plugin{
|
||||||
|
{
|
||||||
|
Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle
|
||||||
|
Call: hook, // actual function we trigger
|
||||||
|
Priority: -1, // last plugin to execute
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hook(reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){
|
||||||
|
if ctx.Config.Get("plugins.transcoder.image.enable").Bool() == false {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
Log.Debug("Image plugin")
|
||||||
|
|
||||||
|
query := req.URL.Query()
|
||||||
|
mType := GetMimeType(query.Get("path"))
|
||||||
|
|
||||||
|
if strings.HasPrefix(mType, "image/") == false {
|
||||||
|
return reader, nil
|
||||||
|
} else if mType == "image/svg" {
|
||||||
|
return reader, nil
|
||||||
|
} else if mType == "image/x-icon" {
|
||||||
|
return reader, nil
|
||||||
|
} else if query.Get("thumbnail") != "true" && query.Get("size") == "" {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// Specify transformation
|
||||||
|
transform := &lib.Transform{
|
||||||
|
Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)),
|
||||||
|
Size: 300,
|
||||||
|
Crop: true,
|
||||||
|
Quality: 50,
|
||||||
|
Exif: false,
|
||||||
|
}
|
||||||
|
if query.Get("thumbnail") == "true" {
|
||||||
|
(*res).Header().Set("Cache-Control", "max-age=259200")
|
||||||
|
} else if query.Get("size") != "" {
|
||||||
|
(*res).Header().Set("Cache-Control", "max-age=600")
|
||||||
|
size, err := strconv.ParseInt(query.Get("size"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
transform.Size = int(size)
|
||||||
|
transform.Crop = false
|
||||||
|
transform.Quality = 90
|
||||||
|
transform.Exif = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// Insert file in the fs
|
||||||
|
// => lower RAM usage while processing
|
||||||
|
file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return reader, NewError("Can't use filesystem", 500)
|
||||||
|
}
|
||||||
|
io.Copy(file, reader)
|
||||||
|
file.Close()
|
||||||
|
if obj, ok := reader.(interface{ Close() error }); ok {
|
||||||
|
obj.Close()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
os.Remove(transform.Temporary)
|
||||||
|
}()
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// Transcode RAW image
|
||||||
|
if lib.IsRaw(mType) {
|
||||||
|
if lib.ExtractPreview(transform) == nil {
|
||||||
|
mType = "image/jpeg"
|
||||||
|
(*res).Header().Set("Content-Type", mType)
|
||||||
|
} else {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// Final stage: resizing
|
||||||
|
if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" && mType != "image/tiff" {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
return lib.CreateThumbnail(transform)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package images
|
package lib
|
||||||
|
|
||||||
// #cgo pkg-config: libraw
|
// #cgo pkg-config: libraw
|
||||||
// #include <raw.h>
|
// #include <raw.h>
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package images
|
package lib
|
||||||
|
|
||||||
// #cgo pkg-config: vips
|
// #cgo pkg-config: vips
|
||||||
// #include <resizer.h>
|
// #include <resizer.h>
|
||||||
68
server/plugin/index.go
Normal file
68
server/plugin/index.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
plg "plugin"
|
||||||
|
. "github.com/mickael-kerjean/nuage/server/common"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginPath = "data/plugin/"
|
||||||
|
|
||||||
|
var plugins = make(map[string][]Plugin)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ex, _ := os.Executable()
|
||||||
|
pPath := filepath.Join(filepath.Dir(ex), PluginPath)
|
||||||
|
|
||||||
|
file, err := os.Open(pPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
files, err := file.Readdir(0)
|
||||||
|
|
||||||
|
c := NewConfig()
|
||||||
|
for i:=0; i < len(files); i++ {
|
||||||
|
name := files[i].Name()
|
||||||
|
if strings.HasPrefix(name, ".") == true {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p, err := plg.Open(pPath + "/" + name)
|
||||||
|
if err != nil {
|
||||||
|
Log.Warning(fmt.Sprintf("Can't load plugin: %s => %v", name, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := p.Lookup("Register")
|
||||||
|
if err != nil {
|
||||||
|
Log.Warning(fmt.Sprintf("Can't register plugin: %s => %v", name, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if obj, ok := f.(func(config *Config) []Plugin); ok {
|
||||||
|
for _, plg := range obj(c) {
|
||||||
|
plugins[plg.Type] = append(plugins[plg.Type], plg)
|
||||||
|
sort.SliceStable(plugins[plg.Type], func(i, j int) bool {
|
||||||
|
return plugins[plg.Type][i].Priority > plugins[plg.Type][j].Priority
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func ProcessFileContentBeforeSend() []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error) {
|
||||||
|
fs := plugins[PROCESS_FILE_CONTENT_BEFORE_SEND]
|
||||||
|
ret := make([]func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error), len(fs))
|
||||||
|
for _, p := range fs {
|
||||||
|
if f, ok := p.Call.(func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error)); ok {
|
||||||
|
ret = append(ret, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/mickael-kerjean/nuage/server/common"
|
|
||||||
"github.com/mickael-kerjean/nuage/server/services/images"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ImageCachePath = "data/cache/image/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cachePath := filepath.Join(GetCurrentDir(), ImageCachePath)
|
|
||||||
os.RemoveAll(cachePath)
|
|
||||||
os.MkdirAll(cachePath, os.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProcessFileBeforeSend(reader io.Reader, ctx *App, req *http.Request, res *http.ResponseWriter) (io.Reader, error) {
|
|
||||||
query := req.URL.Query()
|
|
||||||
mType := ctx.Helpers.MimeType(query.Get("path"))
|
|
||||||
(*res).Header().Set("Content-Type", mType)
|
|
||||||
|
|
||||||
if strings.HasPrefix(mType, "image/") {
|
|
||||||
if mType == "image/svg" {
|
|
||||||
(*res).Header().Set("Content-Type", "image/svg+xml")
|
|
||||||
return reader, nil
|
|
||||||
} else if mType == "image/x-icon" {
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if query.Get("thumbnail") != "true" && query.Get("size") == "" {
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
// Specify transformation
|
|
||||||
transform := &images.Transform{
|
|
||||||
Temporary: ctx.Helpers.AbsolutePath(ImageCachePath + "image_" + QuickString(10)),
|
|
||||||
Size: 300,
|
|
||||||
Crop: true,
|
|
||||||
Quality: 50,
|
|
||||||
Exif: false,
|
|
||||||
}
|
|
||||||
if query.Get("thumbnail") == "true" {
|
|
||||||
(*res).Header().Set("Cache-Control", "max-age=259200")
|
|
||||||
} else if query.Get("size") != "" {
|
|
||||||
(*res).Header().Set("Cache-Control", "max-age=600")
|
|
||||||
size, err := strconv.ParseInt(query.Get("size"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
transform.Size = int(size)
|
|
||||||
transform.Crop = false
|
|
||||||
transform.Quality = 90
|
|
||||||
transform.Exif = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////
|
|
||||||
// Insert file in the fs
|
|
||||||
// => lower RAM usage while processing
|
|
||||||
file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return reader, NewError("Can't use filesystem", 500)
|
|
||||||
}
|
|
||||||
io.Copy(file, reader)
|
|
||||||
file.Close()
|
|
||||||
if obj, ok := reader.(interface{ Close() error }); ok {
|
|
||||||
obj.Close()
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
os.Remove(transform.Temporary)
|
|
||||||
}()
|
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
// Transcode RAW image
|
|
||||||
if images.IsRaw(mType) {
|
|
||||||
if images.ExtractPreview(transform) == nil {
|
|
||||||
mType = "image/jpeg"
|
|
||||||
(*res).Header().Set("Content-Type", mType)
|
|
||||||
} else {
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
// Final stage: resizing
|
|
||||||
if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" && mType != "image/tiff" {
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
return images.CreateThumbnail(transform)
|
|
||||||
}
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
// type Webdav struct {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func NewWebdav() Webdav {
|
|
||||||
// return Webdav{}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (w Webdav) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (w Webdav) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
|
|
||||||
// return File{}, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (w Webdav) RemoveAll(ctx context.Context, name string) error {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (w Webdav) Rename(ctx context.Context, oldName, newName string) error {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (w Webdav) Stat(ctx context.Context, name string) (os.FileInfo, error) {
|
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
Reference in New Issue
Block a user