feat: psql support

This commit is contained in:
face.wsl 2022-11-20 00:38:27 +08:00
parent 02db83c72e
commit 8cd5ecd077
10 changed files with 908 additions and 15550 deletions

View File

@ -17,6 +17,7 @@ import (
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt"
"github.com/filebrowser/filebrowser/v2/storage/psql"
)
func checkErr(err error) {
@ -82,27 +83,53 @@ func dbExists(path string) (bool, error) {
return false, err
}
func openBoltDB(path string, cfg pythonConfig) pythonData {
data := pythonData{hadDB: true}
exists, err := dbExists(path)
if err != nil {
panic(err)
} else if exists && cfg.noDB {
log.Fatal(path + " already exists")
} else if !exists && !cfg.noDB && !cfg.allowNoDB {
log.Fatal(path + " does not exist. Please run 'filebrowser config init' first.")
}
data.hadDB = exists
db, err := storm.Open(path)
checkErr(err)
defer db.Close()
data.store, err = bolt.NewStorage(db)
checkErr(err)
return data
}
func isPsqlDB(path string) bool {
return strings.HasPrefix(path, "postgres:")
}
func openPsqlDB(path string, cfg pythonConfig) pythonData {
data := pythonData{hadDB: true}
db, err := psql.ConnectDB(path)
if err != nil {
data.store, err = psql.NewStorage(db)
} else {
log.Fatal("Fail to open psql database " + path)
}
return data
}
func openDB(path string, cfg pythonConfig) pythonData {
if isPsqlDB(path) {
return openPsqlDB(path, cfg)
}
return openBoltDB(path, cfg)
}
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
return func(cmd *cobra.Command, args []string) {
data := pythonData{hadDB: true}
path := getParam(cmd.Flags(), "database")
exists, err := dbExists(path)
if err != nil {
panic(err)
} else if exists && cfg.noDB {
log.Fatal(path + " already exists")
} else if !exists && !cfg.noDB && !cfg.allowNoDB {
log.Fatal(path + " does not exist. Please run 'filebrowser config init' first.")
}
data.hadDB = exists
db, err := storm.Open(path)
checkErr(err)
defer db.Close()
data.store, err = bolt.NewStorage(db)
checkErr(err)
data := openDB(path, cfg)
fn(cmd, args, data)
}
}

15632
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

12
go.mod
View File

@ -32,6 +32,8 @@ require (
require (
github.com/andybalholm/brotli v1.0.1 // indirect
github.com/boltdb/bolt v1.3.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d // indirect
@ -42,20 +44,30 @@ require (
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hasit/bolter v0.0.0-20221019173212-593eefaa56b7 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/compress v1.11.4 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/kval-access-language/kval-boltdb v0.0.0-20170330045345-f3797777c95e // indirect
github.com/kval-access-language/kval-parse v0.0.0-20170504112528-b96aa5a26330 // indirect
github.com/kval-access-language/kval-scanner v0.0.0-20170504112421-4f097cacd289 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.2 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/ulikunitz/xz v0.5.9 // indirect
github.com/urfave/cli v1.22.1 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect

26
go.sum
View File

@ -47,6 +47,8 @@ github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSUL
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@ -55,6 +57,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -167,6 +171,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hasit/bolter v0.0.0-20221019173212-593eefaa56b7 h1:Plet+TSM/QDQjX84z7sD4ZiAxXO6NDQ3HF8gVZ4J7H8=
github.com/hasit/bolter v0.0.0-20221019173212-593eefaa56b7/go.mod h1:QeiIIsu6N8sVPeSHpo1i9cID5fjR5/9nEgumickazDo=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -187,6 +193,14 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kval-access-language/kval-boltdb v0.0.0-20170330045345-f3797777c95e h1:6eQQkLvMgIY9ecZKVtEsKdUeApg6qkNl8IwJp6cZBNg=
github.com/kval-access-language/kval-boltdb v0.0.0-20170330045345-f3797777c95e/go.mod h1:CJfrSSteer+JJgIkdw/LU7Ss1CiZ6G/+oIfo4nJ1P/s=
github.com/kval-access-language/kval-parse v0.0.0-20170504112528-b96aa5a26330 h1:u+bu63lKw/77wcyhzRB9YwipGEa4l3et/hu4xkcX2cQ=
github.com/kval-access-language/kval-parse v0.0.0-20170504112528-b96aa5a26330/go.mod h1:sIViryZLFGwtty1cz01GVfqCzHR2j5O7ZCcuYiGJAJA=
github.com/kval-access-language/kval-scanner v0.0.0-20170504112421-4f097cacd289 h1:xp6dE/eMUsoTqp/A4p846HSDlETgYPcW+gmtSW7G0tc=
github.com/kval-access-language/kval-scanner v0.0.0-20170504112421-4f097cacd289/go.mod h1:YAawD4QhDnNbLB6455T6SheCYnZh90kII4jhq5HWzAQ=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@ -194,6 +208,8 @@ github.com/maruel/natural v1.0.0 h1:C1GqgYygkdnwD1H1psoEVsPazXyUqRooEvX/XyWFFDg=
github.com/maruel/natural v1.0.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -202,12 +218,17 @@ github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGg
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o=
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.0 h1:P7Bq0SaI8nsexyay5UAyDo+ICWy5MQPgEZ5+l8JQTKo=
github.com/pelletier/go-toml/v2 v2.0.0/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -216,9 +237,12 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
@ -247,6 +271,8 @@ github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoi
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=

36
storage/psql/auth.go Normal file
View File

@ -0,0 +1,36 @@
package psql
import (
"database/sql"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/settings"
)
type authBackend struct {
db *sql.DB
}
func (s authBackend) Get(t settings.AuthMethod) (auth.Auther, error) {
var auther auth.Auther
switch t {
case auth.MethodJSONAuth:
auther = &auth.JSONAuth{}
case auth.MethodProxyAuth:
auther = &auth.ProxyAuth{}
case auth.MethodHookAuth:
auther = &auth.HookAuth{}
case auth.MethodNoAuth:
auther = &auth.NoAuth{}
default:
return nil, errors.ErrInvalidAuthMethod
}
return auther, getConfig(s.db, "auther", auther)
}
func (s authBackend) Save(a auth.Auther) error {
return setConfig(s.db, "author", a)
}

44
storage/psql/psql.go Normal file
View File

@ -0,0 +1,44 @@
package psql
import (
"database/sql"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/share"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
)
func ConnectDB(path string) (*sql.DB, error) {
db, err := sql.Open("postgres", path)
if err == nil {
return db, nil
}
return nil, err
}
// NewStorage creates a storage.Storage based on Bolt DB.
func NewStorage(db *sql.DB) (*storage.Storage, error) {
userStore := users.NewStorage(usersBackend{db: db})
shareStore := share.NewStorage(shareBackend{db: db})
settingsStore := settings.NewStorage(settingsBackend{db: db})
authStore := auth.NewStorage(authBackend{db: db}, userStore)
err := save(db, "version", 2) //nolint:gomnd
if err != nil {
return nil, err
}
return &storage.Storage{
Auth: authStore,
Users: userStore,
Share: shareStore,
Settings: settingsStore,
}, nil
}
func save(db *sql.DB, name string, from interface{}) error {
// return db.Set("config", name, from)
return nil
}

254
storage/psql/settings.go Normal file
View File

@ -0,0 +1,254 @@
package psql
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"github.com/filebrowser/filebrowser/v2/settings"
)
type settingsBackend struct {
db *sql.DB
}
func userDefaultsFromString(s string) settings.UserDefaults {
if s == "" {
return settings.UserDefaults{}
}
userDefaults := settings.UserDefaults{}
err := json.Unmarshal([]byte(s), &userDefaults)
if err != nil {
fmt.Printf("ERROR: fail to parse settings.UserDefaults")
}
return userDefaults
}
func userDefaultsToString(d settings.UserDefaults) string {
data, err := json.Marshal(d)
if err != nil {
return ""
}
return string(data)
}
func brandingFromString(s string) settings.Branding {
if s == "" {
return settings.Branding{}
}
branding := settings.Branding{}
err := json.Unmarshal([]byte(s), &branding)
if err != nil {
fmt.Printf("ERROR: fail to parse settings.Branding")
}
return branding
}
func brandingToString(s settings.Branding) string {
data, err := json.Marshal(s)
if err != nil {
fmt.Printf("ERROR: fail to jsonify settings.Branding")
return ""
}
return string(data)
}
func commandsToString(c map[string][]string) string {
data, err := json.Marshal(c)
if err != nil {
fmt.Printf("ERROR: fail to jsonify commands")
return ""
}
return string(data)
}
func commandsFromString(s string) map[string][]string {
if s == "" {
return map[string][]string{}
}
c := map[string][]string{}
err := json.Unmarshal([]byte(s), &c)
if err != nil {
fmt.Printf("ERROR: fail to parse commands")
}
return c
}
func stringsFromString(s string) []string {
if s == "" {
return []string{}
}
c := []string{}
err := json.Unmarshal([]byte(s), &c)
if err != nil {
fmt.Printf("ERROR: fail to parse []string")
}
return c
}
func stringsToString(c []string) string {
data, err := json.Marshal(c)
if err != nil {
fmt.Printf("ERROR: fail to jsonify strings")
return ""
}
return string(data)
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
func boolFromInt(i int) bool {
if i == 0 {
return false
}
return true
}
func boolFromString(s string) bool {
if s == "0" || s == "" || s == "f" || s == "F" {
return false
}
return true
}
func boolToString(b bool) string {
if b {
return "1"
}
return "0"
}
func (s settingsBackend) Get() (*settings.Settings, error) {
sql := "select key, value from settings"
rows, err := s.db.Query(sql)
if err != nil {
return nil, nil
}
key := ""
value := ""
settings1 := settings.Settings{}
for rows.Next() {
err = rows.Scan(key, value)
if err != nil {
fmt.Printf("ERROR: fail to query settings.Settings")
}
if key == "Key" {
settings1.Key = []byte(value)
} else if key == "Signup" {
settings1.Signup = boolFromString(value)
} else if key == "CreateUserDir" {
settings1.CreateUserDir = boolFromString(value)
} else if key == "Defaults" {
settings1.Defaults = userDefaultsFromString(value)
} else if key == "AuthMethod" {
settings1.AuthMethod = settings.AuthMethod(value)
} else if key == "Branding" {
settings1.Branding = brandingFromString(value)
} else if key == "Commands" {
settings1.Commands = commandsFromString(value)
} else if key == "Shell" {
settings1.Shell = stringsFromString(value)
} else if key == "Rules" {
settings1.Rules = rulesFromString(value)
} else {
fmt.Printf("ERROR: unknown settings key " + key)
}
}
return &settings1, nil
}
func (s settingsBackend) Save(ss *settings.Settings) error {
columns := []string{"Key", "Signup", "CreateUserDir", "UserHomeBasePath", "Defaults", "AuthMethod", "Branding", "Commands", "Shell", "Rules"}
values := []string{
"'" + string(ss.Key) + "'",
boolToString(ss.Signup),
boolToString(ss.CreateUserDir),
"'" + string(ss.UserHomeBasePath) + "'",
userDefaultsToString(ss.Defaults),
string(ss.AuthMethod),
brandingToString(ss.Branding),
commandsToString(ss.Commands),
stringsToString(ss.Shell),
RulesToString(ss.Rules)}
sql := fmt.Sprintf("INSERT INTO settings (%s) VALUES(%s)", strings.Join(columns, ","), strings.Join(values, ","))
_, err := s.db.Exec(sql)
if err != nil {
return err
}
return nil
}
func (s settingsBackend) GetServer() (*settings.Server, error) {
sql := "select key, value from settings"
rows, err := s.db.Query(sql)
if err != nil {
return nil, nil
}
key := ""
value := ""
server := settings.Server{}
for rows.Next() {
err = rows.Scan(key, value)
if err != nil {
fmt.Printf("ERROR: fail to query settings.Settings")
}
if key == "Root" {
server.Root = value
} else if key == "BaseURL" {
server.BaseURL = value
} else if key == "Socket" {
server.Socket = value
} else if key == "TLSKey" {
server.TLSKey = value
} else if key == "TLSCert" {
server.TLSCert = value
} else if key == "Port" {
server.Port = value
} else if key == "Address" {
server.Address = value
} else if key == "Log" {
server.Log = value
} else if key == "EnableThumbnails" {
server.EnableThumbnails = boolFromString(value)
} else if key == "ResizePreview" {
server.ResizePreview = boolFromString(value)
} else if key == "EnableExec" {
server.EnableExec = boolFromString(value)
} else if key == "TypeDetectionByHeader" {
server.TypeDetectionByHeader = boolFromString(value)
} else if key == "AuthHook" {
server.AuthHook = value
}
}
return &server, nil
}
func (s settingsBackend) SaveServer(ss *settings.Server) error {
columns := []string{"Root", "BaseURL", "Socket", "TLSKey", "TLSCert", "Port", "Address", "Log", "EnableThumbnails", "ResizePreview", "EnableExec", "TypeDetectionByHeader", "AuthHook"}
values := []string{
"'" + ss.Root + "'",
"'" + ss.BaseURL + "'",
"'" + ss.Socket + "'",
"'" + ss.TLSKey + "'",
"'" + ss.TLSCert + "'",
"'" + ss.Port + "'",
"'" + ss.Address + "'",
"'" + ss.Log + "'",
boolToString(ss.EnableThumbnails),
boolToString(ss.ResizePreview),
boolToString(ss.EnableExec),
boolToString(ss.TypeDetectionByHeader),
"'" + ss.AuthHook + "'"}
sql := fmt.Sprintf("INSERT INTO settings (%s) VALUES(%s)", strings.Join(columns, ","), strings.Join(values, ","))
_, err := s.db.Exec(sql)
if err != nil {
return err
}
return nil
}

96
storage/psql/share.go Normal file
View File

@ -0,0 +1,96 @@
package psql
import (
"database/sql"
"errors"
"fmt"
"github.com/filebrowser/filebrowser/v2/share"
)
type shareBackend struct {
db *sql.DB
}
type linkRecord interface {
Scan(dest ...interface{}) error
}
func parseLink(row linkRecord) (*share.Link, error) {
path := ""
hash := ""
userid := uint(0)
expire := int64(0)
passwordhash := ""
token := ""
err := row.Scan(path, hash, userid, expire, passwordhash, token)
if err != nil {
s := "ERROR: Fail to parse record for share.Link"
err := errors.New(s)
fmt.Printf(s)
return nil, err
}
link := share.Link{}
link.Path = path
link.Hash = hash
link.UserID = userid
link.Expire = expire
link.PasswordHash = passwordhash
link.Token = token
return &link, nil
}
func queryLinks(db *sql.DB, condition string) ([]*share.Link, error) {
sql := "select hash, path, userid, expire, passwordhash, token from share_links"
if len(condition) > 0 {
sql = sql + " where " + condition
}
rows, err := db.Query(sql)
if err != nil {
return nil, err
}
var links []*share.Link = []*share.Link{}
for rows.Next() {
link, err := parseLink(rows)
if err != nil {
fmt.Printf("ERROR: Fail to parse record for share.Link")
continue
}
links = append(links, link)
}
return links, nil
}
func (s shareBackend) All() ([]*share.Link, error) {
return queryLinks(s.db, "")
}
func (s shareBackend) FindByUserID(id uint) ([]*share.Link, error) {
condition := fmt.Sprintf("userid=%d", id)
return queryLinks(s.db, condition)
}
func (s shareBackend) GetByHash(hash string) (*share.Link, error) {
sql := fmt.Sprintf("select hash, path, userid, expire, passwordhash, token from share_links where hash='%s'", hash)
return parseLink(s.db.QueryRow(sql))
}
func (s shareBackend) GetPermanent(path string, id uint) (*share.Link, error) {
sql := fmt.Sprintf("select hash, path, userid, expire, passwordhash, token from share_links where path='%s' and userid=%d", path, id)
return parseLink(s.db.QueryRow(sql))
}
func (s shareBackend) Gets(path string, id uint) ([]*share.Link, error) {
condition := fmt.Sprintf("userid=%d and path='%s'", id, path)
return queryLinks(s.db, condition)
}
func (s shareBackend) Save(l *share.Link) error {
sql := fmt.Sprintf("insert into share_links (hash, path, userid, expire, passwordhash, token) values('%s', '%s', %d, %d, '%s', '%s')", l.Hash, l.Path, l.UserID, l.Expire, l.PasswordHash, l.Token)
_, err := s.db.Exec(sql)
return err
}
func (s shareBackend) Delete(hash string) error {
sql := fmt.Sprintf("DELETE FROM share_links WHERE hash='%s'", hash)
_, err := s.db.Exec(sql)
return err
}

242
storage/psql/users.go Normal file
View File

@ -0,0 +1,242 @@
package psql
import (
"database/sql"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/users"
)
type usersBackend struct {
db *sql.DB
}
func PermFromString(s string) users.Permissions {
if s == "" {
return users.Permissions{}
}
var perm users.Permissions
json.Unmarshal([]byte(s), &perm)
return perm
}
func PermToString(perm users.Permissions) string {
data, err := json.Marshal(perm)
if err != nil {
return ""
}
return string(data)
}
func CommandsFromString(s string) []string {
if s == "" {
return []string{}
}
var commands []string
err := json.Unmarshal([]byte(s), &commands)
if err != nil {
return nil
}
return commands
}
func CommandsToString(commands []string) string {
data, err := json.Marshal(commands)
if err != nil {
return ""
}
return string(data)
}
func SortingFromString(s string) files.Sorting {
if s == "" {
return files.Sorting{}
}
var sorting files.Sorting
json.Unmarshal([]byte(s), &sorting)
return sorting
}
func SortingToString(sorting files.Sorting) string {
data, err := json.Marshal(sorting)
if err != nil {
return ""
}
return string(data)
}
func rulesFromString(s string) []rules.Rule {
if s == "" {
return []rules.Rule{}
}
var rules []rules.Rule
json.Unmarshal([]byte(s), &rules)
return rules
}
func RulesToString(rules []rules.Rule) string {
data, err := json.Marshal(rules)
if err != nil {
return ""
}
return string(data)
}
func (s usersBackend) Get(id interface{}) (*users.User, error) {
userId := id.(uint)
username := ""
password := ""
scope := ""
lockpassword := false
var viewmode users.ViewMode = "list"
perm := ""
commands := ""
sorting := ""
rules := ""
sql := "select username, password, scope, lockpassword, viewmode, perm,commands,sorting,rules from users where id=" + strconv.Itoa(int(userId))
s.db.QueryRow(sql).Scan(username, password, scope, lockpassword, viewmode, perm, commands, sorting, rules)
user := users.User{}
user.ID = userId
user.Username = username
user.Password = password
user.Scope = scope
user.LockPassword = lockpassword
user.ViewMode = viewmode
user.Perm = PermFromString(perm)
user.Commands = CommandsFromString(commands)
user.Sorting = SortingFromString(sorting)
user.Rules = rulesFromString(rules)
return &user, nil
}
func (s usersBackend) Gets() ([]*users.User, error) {
sql := "select id, username, password, scope, lockpassword, viewmode, perm,commands,sorting,rules from users"
rows, err := s.db.Query(sql)
if err != nil {
return nil, err
}
var users2 []*users.User = []*users.User{}
for rows.Next() {
id := 0
username := ""
password := ""
scope := ""
lockpassword := false
var viewmode users.ViewMode = "list"
perm := ""
commands := ""
sorting := ""
rules := ""
err := rows.Scan(id, username, password, scope, lockpassword, viewmode, perm, commands, sorting, rules)
if err != nil {
fmt.Printf("Fail to parse record for user.User")
continue
}
user := users.User{}
user.ID = uint(id)
user.Username = username
user.Password = password
user.Scope = scope
user.LockPassword = lockpassword
user.ViewMode = viewmode
user.Perm = PermFromString(perm)
user.Commands = CommandsFromString(commands)
user.Sorting = SortingFromString(sorting)
user.Rules = rulesFromString(rules)
users2 = append(users2, &user)
}
return users2, nil
}
func (s usersBackend) GetBy(id interface{}) (*users.User, error) {
return s.Get(id)
}
func (s usersBackend) updateUser(id uint, user *users.User) error {
lockpassword := 0
if user.LockPassword {
lockpassword = 1
}
sql := fmt.Sprintf(
"update users set username='%s',password='%s',scope='%s',lockpassword=%d,viewmode='%s',perm='%s',commands='%s',sorting='%s',rules='%s' where id=%d",
user.Username,
user.Password,
user.Scope,
lockpassword,
user.ViewMode,
PermToString(user.Perm),
CommandsToString(user.Commands),
SortingToString(user.Sorting),
RulesToString(user.Rules),
user.ID,
)
_, err := s.db.Exec(sql)
return err
}
func (s usersBackend) insertUser(user *users.User) error {
sql := fmt.Sprintf(
"insert into users (username, password, scope, lockpassword, viewmode, perm, commands, sorting) values ('%s','%s','%s',%d,'%s','%s','%s','%s','%s')",
user.Username,
user.Password,
user.Scope,
user.LockPassword,
user.ViewMode,
PermToString(user.Perm),
CommandsToString(user.Commands),
SortingToString(user.Sorting),
RulesToString(user.Rules),
)
_, err := s.db.Exec(sql)
return err
}
func (s usersBackend) Save(user *users.User) error {
userOriginal, err := s.GetBy(user.ID)
if err != nil {
return err
}
if userOriginal != nil {
return s.updateUser(user.ID, user)
}
return s.insertUser(user)
}
func (s usersBackend) DeleteByID(id uint) error {
sql := "delete from users where id=" + strconv.Itoa(int(id))
_, err := s.db.Exec(sql)
return err
}
func (s usersBackend) DeleteByUsername(username string) error {
sql := "delete from users where username='" + username + "'"
_, err := s.db.Exec(sql)
return err
}
func (s usersBackend) Update(u *users.User, fields ...string) error {
var setItems = []string{}
for _, field := range fields {
userField := reflect.ValueOf(u).Elem().FieldByName(field)
if !userField.IsValid() {
continue
}
val := userField.Interface()
if reflect.TypeOf(val).Kind().String() == "string" {
setItems = append(setItems, fmt.Sprintf("%s='%s'", field, val))
} else {
// TODO
setItems = append(setItems, fmt.Sprintf("%s=%d", field, val))
}
}
sql := fmt.Sprintf("update users set %s if id=%d", strings.Join(setItems, ","), u.ID)
_, err := s.db.Exec(sql)
return err
}

53
storage/psql/utils.go Normal file
View File

@ -0,0 +1,53 @@
package psql
import (
"database/sql"
"log"
_ "github.com/lib/pq"
)
const (
// DB_DSN is a
DB_DSN = "postgres://postgres:12345678@127.0.0.1:5432/postgres?sslmode=disable"
)
func loadUsers() {
db, err := sql.Open("postgres", DB_DSN)
if err != nil {
log.Fatal("Fail to open a DB connection")
}
rows, err := db.Query("select id, name from users")
if err == nil {
log.Fatal("Fail to query db")
}
defer db.Close()
for rows.Next() {
id := ""
name := ""
err := rows.Scan(&id, &name)
if err != nil {
log.Fatal("Fail to scan db row")
}
}
}
func getConfig(db *sql.DB, key string, to interface{}) error {
var value string = ""
err := db.QueryRow("select value from config where name='" + key + "'").Scan(&value)
if err == nil {
return err
}
to = value
return nil
}
func setConfig(db *sql.DB, key string, to interface{}) error {
var value string = to.(string)
_, err := db.Exec("insert into config (name, value) values('" + key + "','" + value + "')")
if err == nil {
return nil
}
return err
}