implement first prototype of the logo upload API

This commit is contained in:
David Christofas
2023-02-13 13:36:30 +01:00
parent e853b98918
commit 20e4e56d28
8 changed files with 76 additions and 82 deletions
+18 -24
View File
@@ -1,12 +1,11 @@
package assetsfs package assetsfs
import ( import (
"embed"
"fmt" "fmt"
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"path" "path/filepath"
"github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/log"
) )
@@ -21,22 +20,31 @@ type FileSystem struct {
// Open checks if assetPath is set and tries to load from there. Falls back to fs if that is not possible // Open checks if assetPath is set and tries to load from there. Falls back to fs if that is not possible
func (f *FileSystem) Open(original string) (http.File, error) { func (f *FileSystem) Open(original string) (http.File, error) {
if f.assetPath != "" { if f.assetPath != "" {
file, err := read(f.assetPath, original) file, err := os.Open(filepath.Join(f.assetPath, original))
if err == nil { if err == nil {
return file, nil return file, nil
} }
f.log.Warn().
Str("path", f.assetPath).
Str("filename", original).
Str("error", err.Error()).
Msg("error reading from assetPath")
} }
return f.fs.Open(original) return f.fs.Open(original)
} }
// Create creates a new file in the assetPath
func (f *FileSystem) Create(name string) (*os.File, error) {
fullPath := f.jailPath(name)
if err := os.MkdirAll(filepath.Dir(fullPath), 0770); err != nil {
return nil, err
}
return os.Create(fullPath)
}
// jailPath returns the fullPath `<assetPath>/<name>`. It makes sure that the path is
// always under `<assetPath>` to prevent directory traversal.
func (f *FileSystem) jailPath(name string) string {
return filepath.Join(f.assetPath, filepath.Join("/", name))
}
// New initializes a new FileSystem. Quits on error // New initializes a new FileSystem. Quits on error
func New(embedFS embed.FS, assetPath string, logger log.Logger) *FileSystem { func New(embedFS fs.FS, assetPath string, logger log.Logger) *FileSystem {
f, err := fs.Sub(embedFS, "assets") f, err := fs.Sub(embedFS, "assets")
if err != nil { if err != nil {
fmt.Println("Cannot load subtree fs:", err.Error()) fmt.Println("Cannot load subtree fs:", err.Error())
@@ -49,17 +57,3 @@ func New(embedFS embed.FS, assetPath string, logger log.Logger) *FileSystem {
log: logger, log: logger,
} }
} }
// tries to read file from disk or errors
func read(assetPath string, fileName string) (http.File, error) {
if stat, err := os.Stat(assetPath); err != nil || !stat.IsDir() {
return nil, fmt.Errorf("can't load asset path: %s", err)
}
p := path.Join(assetPath, fileName)
if _, err := os.Stat(p); err != nil {
return nil, err
}
return os.Open(p)
}
-50
View File
@@ -1,50 +0,0 @@
package assets
import (
"net/http"
"github.com/owncloud/ocis/v2/ocis-pkg/assetsfs"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/web"
"github.com/owncloud/ocis/v2/services/web/pkg/config"
)
// New returns a new http filesystem to serve assets.
func New(opts ...Option) http.FileSystem {
options := newOptions(opts...)
return assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger)
}
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
+2 -1
View File
@@ -2,12 +2,13 @@ package assets
import ( import (
"bytes" "bytes"
"golang.org/x/net/html"
"io" "io"
"mime" "mime"
"net/http" "net/http"
"path" "path"
"path/filepath" "path/filepath"
"golang.org/x/net/html"
) )
type fileServer struct { type fileServer struct {
@@ -1,8 +1,10 @@
package defaults package defaults
import ( import (
"path/filepath"
"strings" "strings"
"github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
"github.com/owncloud/ocis/v2/services/web/pkg/config" "github.com/owncloud/ocis/v2/services/web/pkg/config"
) )
@@ -31,7 +33,7 @@ func DefaultConfig() *config.Config {
Name: "web", Name: "web",
}, },
Asset: config.Asset{ Asset: config.Asset{
Path: "", Path: filepath.Join(defaults.BaseDataPath(), "web/assets"),
}, },
Web: config.Web{ Web: config.Web{
Path: "", Path: "",
@@ -28,3 +28,8 @@ func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (i instrument) Config(w http.ResponseWriter, r *http.Request) { func (i instrument) Config(w http.ResponseWriter, r *http.Request) {
i.next.Config(w, r) i.next.Config(w, r)
} }
// UploadLogo implements the Service interface.
func (i instrument) UploadLogo(w http.ResponseWriter, r *http.Request) {
i.next.UploadLogo(w, r)
}
+5
View File
@@ -28,3 +28,8 @@ func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (l logging) Config(w http.ResponseWriter, r *http.Request) { func (l logging) Config(w http.ResponseWriter, r *http.Request) {
l.next.Config(w, r) l.next.Config(w, r)
} }
// UploadLogo implements the Service interface.
func (l logging) UploadLogo(w http.ResponseWriter, r *http.Request) {
l.next.UploadLogo(w, r)
}
+38 -6
View File
@@ -2,16 +2,21 @@ package svc
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/owncloud/ocis/v2/ocis-pkg/assetsfs"
"github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/web"
"github.com/owncloud/ocis/v2/services/web/pkg/assets" "github.com/owncloud/ocis/v2/services/web/pkg/assets"
"github.com/owncloud/ocis/v2/services/web/pkg/config" "github.com/owncloud/ocis/v2/services/web/pkg/config"
) )
@@ -25,6 +30,7 @@ var (
type Service interface { type Service interface {
ServeHTTP(http.ResponseWriter, *http.Request) ServeHTTP(http.ResponseWriter, *http.Request)
Config(http.ResponseWriter, *http.Request) Config(http.ResponseWriter, *http.Request)
UploadLogo(http.ResponseWriter, *http.Request)
} }
// NewService returns a service implementation for Service. // NewService returns a service implementation for Service.
@@ -38,10 +44,12 @@ func NewService(opts ...Option) Service {
logger: options.Logger, logger: options.Logger,
config: options.Config, config: options.Config,
mux: m, mux: m,
fs: assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger),
} }
m.Route(options.Config.HTTP.Root, func(r chi.Router) { m.Route(options.Config.HTTP.Root, func(r chi.Router) {
r.Get("/config.json", svc.Config) r.Get("/config.json", svc.Config)
r.Post("/branding/logo", svc.UploadLogo)
r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL)) r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL))
}) })
@@ -58,6 +66,7 @@ type Web struct {
logger log.Logger logger log.Logger
config *config.Config config *config.Config
mux *chi.Mux mux *chi.Mux
fs *assetsfs.FileSystem
} }
// ServeHTTP implements the Service interface. // ServeHTTP implements the Service interface.
@@ -131,12 +140,7 @@ func (p Web) Static(ttl int) http.HandlerFunc {
static := http.StripPrefix( static := http.StripPrefix(
rootWithSlash, rootWithSlash,
assets.FileServer( assets.FileServer(p.fs),
assets.New(
assets.Logger(p.logger),
assets.Config(p.config),
),
),
) )
lastModified := time.Now().UTC().Format(http.TimeFormat) lastModified := time.Now().UTC().Format(http.TimeFormat)
@@ -161,3 +165,31 @@ func (p Web) Static(ttl int) http.HandlerFunc {
static.ServeHTTP(w, r) static.ServeHTTP(w, r)
} }
} }
// UploadLogo implements the endpoint to upload a custom logo for the oCIS instance.
func (p Web) UploadLogo(w http.ResponseWriter, r *http.Request) {
file, fileHeader, err := r.FormFile("logo")
if err != nil {
if errors.Is(err, http.ErrMissingFile) {
w.WriteHeader(http.StatusBadRequest)
}
w.WriteHeader(http.StatusInternalServerError)
return
}
defer file.Close()
dst, err := p.fs.Create(filepath.Join("branding", filepath.Join("/", fileHeader.Filename)))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer dst.Close()
_, err = io.Copy(dst, file)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
+5
View File
@@ -24,3 +24,8 @@ func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (t tracing) Config(w http.ResponseWriter, r *http.Request) { func (t tracing) Config(w http.ResponseWriter, r *http.Request) {
t.next.Config(w, r) t.next.Config(w, r)
} }
// UploadLogo implements the Service interface.
func (t tracing) UploadLogo(w http.ResponseWriter, r *http.Request) {
t.next.UploadLogo(w, r)
}