Local media storage

This commit is contained in:
Jan-Lukas Else 2021-01-10 15:59:43 +01:00
parent b2c618f300
commit 4c87a95e73
5 changed files with 79 additions and 22 deletions

View File

@ -247,10 +247,10 @@ func initConfig() error {
if appConfig.Micropub.MediaStorage.MediaURL == "" || if appConfig.Micropub.MediaStorage.MediaURL == "" ||
appConfig.Micropub.MediaStorage.BunnyStorageKey == "" || appConfig.Micropub.MediaStorage.BunnyStorageKey == "" ||
appConfig.Micropub.MediaStorage.BunnyStorageName == "" { appConfig.Micropub.MediaStorage.BunnyStorageName == "" {
appConfig.Micropub.MediaStorage = nil appConfig.Micropub.MediaStorage.BunnyStorageKey = ""
} else { appConfig.Micropub.MediaStorage.BunnyStorageName = ""
appConfig.Micropub.MediaStorage.MediaURL = strings.TrimSuffix(appConfig.Micropub.MediaStorage.MediaURL, "/")
} }
appConfig.Micropub.MediaStorage.MediaURL = strings.TrimSuffix(appConfig.Micropub.MediaStorage.MediaURL, "/")
} }
return nil return nil
} }

View File

@ -103,9 +103,7 @@ func buildHandler() (http.Handler, error) {
mpRouter.Use(checkIndieAuth, middleware.NoCache, minifier.Middleware) mpRouter.Use(checkIndieAuth, middleware.NoCache, minifier.Middleware)
mpRouter.Get("/", serveMicropubQuery) mpRouter.Get("/", serveMicropubQuery)
mpRouter.Post("/", serveMicropubPost) mpRouter.Post("/", serveMicropubPost)
if appConfig.Micropub.MediaStorage != nil { mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
}
}) })
// Editor // Editor
@ -182,6 +180,9 @@ func buildHandler() (http.Handler, error) {
r.With(cacheMiddleware).Get(path, serveStaticFile) r.With(cacheMiddleware).Get(path, serveStaticFile)
} }
// Media files
r.With(cacheMiddleware).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, serveMediaFile)
// Short paths // Short paths
r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath) r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath)

39
media.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"io"
"net/http"
"os"
"path/filepath"
"github.com/go-chi/chi"
)
const mediaFilePath = "data/media"
func saveMediaFile(filename string, mediaFile io.Reader) (string, error) {
err := os.MkdirAll(mediaFilePath, 0644)
if err != nil {
return "", err
}
newFile, err := os.Create(filepath.Join(mediaFilePath, filename))
if err != nil {
return "", err
}
_, err = io.Copy(newFile, mediaFile)
if err != nil {
return "", err
}
return "/m/" + filename, nil
}
func serveMediaFile(w http.ResponseWriter, r *http.Request) {
f := filepath.Join(mediaFilePath, chi.URLParam(r, "file"))
_, err := os.Stat(f)
if err != nil {
serve404(w, r)
return
}
w.Header().Add("Cache-Control", "public,max-age=31536000,immutable")
http.ServeFile(w, r, f)
}

View File

@ -26,11 +26,9 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
case "config": case "config":
w.Header().Add(contentType, contentTypeJSONUTF8) w.Header().Add(contentType, contentTypeJSONUTF8)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
mc := &micropubConfig{} _ = json.NewEncoder(w).Encode(&micropubConfig{
if appConfig.Micropub.MediaStorage != nil { MediaEndpoint: appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath,
mc.MediaEndpoint = appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath })
}
_ = json.NewEncoder(w).Encode(mc)
case "source": case "source":
var mf interface{} var mf interface{}
if urlString := r.URL.Query().Get("url"); urlString != "" { if urlString := r.URL.Query().Get("url"); urlString != "" {

View File

@ -63,34 +63,53 @@ func serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
} }
} }
fileName += strings.ToLower(fileExtension) fileName += strings.ToLower(fileExtension)
location, err := appConfig.Micropub.MediaStorage.uploadToBunny(fileName, file) // Save file
location, err := uploadFile(fileName, file)
if err != nil { if err != nil {
serveError(w, r, "failed to upload original file: "+err.Error(), http.StatusInternalServerError) serveError(w, r, "failed to save original file: "+err.Error(), http.StatusInternalServerError)
return return
} }
if appConfig.Micropub.MediaStorage.TinifyKey != "" { // Try to compress file
compressedLocation, err := appConfig.Micropub.MediaStorage.tinify(location) if ms := appConfig.Micropub.MediaStorage; ms != nil && ms.TinifyKey != "" {
compressedLocation, err := tinify(location, ms)
if err != nil { if err != nil {
serveError(w, r, "failed to compress file: "+err.Error(), http.StatusInternalServerError) serveError(w, r, "failed to compress file: "+err.Error(), http.StatusInternalServerError)
return return
} else if compressedLocation != "" { } else if compressedLocation != "" {
location = compressedLocation location = compressedLocation
} else {
serveError(w, r, "No compressed location", http.StatusInternalServerError)
} }
} }
http.Redirect(w, r, location, http.StatusCreated) http.Redirect(w, r, location, http.StatusCreated)
} }
func (mediaConf *configMicropubMedia) uploadToBunny(filename string, file multipart.File) (location string, err error) { func uploadFile(filename string, f io.Reader) (string, error) {
req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("https://storage.bunnycdn.com/%s/%s", url.PathEscape(mediaConf.BunnyStorageName), url.PathEscape(filename)), file) ms := appConfig.Micropub.MediaStorage
req.Header.Add("AccessKey", mediaConf.BunnyStorageKey) if ms != nil && ms.BunnyStorageKey != "" && ms.BunnyStorageName != "" {
return uploadToBunny(filename, f, ms)
}
loc, err := saveMediaFile(filename, f)
if err != nil {
return "", err
}
if ms != nil && ms.MediaURL != "" {
return ms.MediaURL + loc, nil
}
return loc, nil
}
func uploadToBunny(filename string, f io.Reader, config *configMicropubMedia) (location string, err error) {
req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("https://storage.bunnycdn.com/%s/%s", url.PathEscape(config.BunnyStorageName), url.PathEscape(filename)), f)
req.Header.Add("AccessKey", config.BunnyStorageKey)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
return "", errors.New("failed to upload file to BunnyCDN") return "", errors.New("failed to upload file to BunnyCDN")
} }
return mediaConf.MediaURL + "/" + filename, nil return config.MediaURL + "/" + filename, nil
} }
func (mediaConf *configMicropubMedia) tinify(url string) (location string, err error) { func tinify(url string, config *configMicropubMedia) (location string, err error) {
fileExtension := func() string { fileExtension := func() string {
spliced := strings.Split(url, ".") spliced := strings.Split(url, ".")
return spliced[len(spliced)-1] return spliced[len(spliced)-1]
@ -101,7 +120,7 @@ func (mediaConf *configMicropubMedia) tinify(url string) (location string, err e
if !(i < len(supportedTypes) && supportedTypes[i] == strings.ToLower(fileExtension)) { if !(i < len(supportedTypes) && supportedTypes[i] == strings.ToLower(fileExtension)) {
return "", nil return "", nil
} }
tfgo.SetKey(mediaConf.TinifyKey) tfgo.SetKey(config.TinifyKey)
s, err := tfgo.FromUrl(url) s, err := tfgo.FromUrl(url)
if err != nil { if err != nil {
return "", err return "", err
@ -134,7 +153,7 @@ func (mediaConf *configMicropubMedia) tinify(url string) (location string, err e
if err != nil { if err != nil {
return "", err return "", err
} }
location, err = mediaConf.uploadToBunny(fileName+"."+fileExtension, file) location, err = uploadFile(fileName+"."+fileExtension, file)
return return
} }