mirror of https://github.com/Superioz/aqua.git
Add extensions to support Discord embeds
This commit is contained in:
parent
1a2dbf266d
commit
a81a33c17a
|
@ -4,5 +4,7 @@ FILE_NAME_LENGTH=12
|
|||
FILE_MAX_SIZE=100
|
||||
FILE_META_DB_PATH=
|
||||
FILE_EXPIRATION_CYCLE=10
|
||||
METRICS_ENABLED=true
|
||||
FILE_SERVING_ENABLED=true
|
||||
FILE_EXTENSIONS_RESPONSE=true
|
||||
FILE_EXTENSIONS_EXCLUDED=image/png,image/jpeg
|
||||
METRICS_ENABLED=true
|
||||
|
|
|
@ -82,8 +82,10 @@ For examplary usage of the environment variables, have a look at the `.env.dist`
|
|||
| `FILE_MAX_SIZE` | Maximum size for uploaded files in Megabytes. |
|
||||
| `FILE_META_DB_PATH` | Path to the directory, where the sqlite database for file metadata should be stored. Recommended to not be the same folder as `FILE_STORAGE_PATH` to prevent overlapping. |
|
||||
| `FILE_EXPIRATION_CYCLE` | Determines the interval of the expiration cycle. `5` means that every 5 seconds the files will be checked for expiration. |
|
||||
| `FILE_SERVING_ENABLED` | Defaults to `true`, if `false`, the server won't serve the stored files. |
|
||||
| `FILE_EXTENSIONS_RESPONSE` | Defaults to `true`. if the file name returned will have its extension added to it. |
|
||||
| `FILE_EXTENSIONS_EXCLUDED` | Comma-seperated list of MIME types, that should be excluded from the extension response rule above. Defaults to `image/png,image/jpeg` |
|
||||
| `METRICS_ENABLED` | Is normally set to true, but otherwise disables the Prometheus metrics publishing. |
|
||||
| `FILE_SERVING_ENABLED` | Defaults to true, when `false`, then the server won't serve the stored files. |
|
||||
|
||||
## Tokens
|
||||
|
||||
|
@ -182,7 +184,7 @@ After that you can import it to your custom upload goals in the ShareX UI.
|
|||
"metadata": "{ \"expiration\": 3600 }"
|
||||
},
|
||||
"FileFormName": "file",
|
||||
"URL": "https://your-domain.com/$json:id$"
|
||||
"URL": "https://your-domain.com/$json:fileName$"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
func main() {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
klog.Warningf("Error loading .env file: %v", err)
|
||||
klog.Warningf("Could not load .env file: %v", err)
|
||||
}
|
||||
klog.Infoln("Hello World!")
|
||||
|
||||
|
|
|
@ -6,6 +6,13 @@ import (
|
|||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
ExpireNever = -1
|
||||
|
||||
EnvDefaultFileStoragePath = "/var/lib/aqua/files/"
|
||||
EnvDefaultMetaDbPath = "/var/lib/aqua/"
|
||||
)
|
||||
|
||||
type AuthConfig struct {
|
||||
ValidTokens []*TokenConfig `yaml:"validTokens"`
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superioz/aqua/internal/config"
|
||||
"github.com/superioz/aqua/internal/metrics"
|
||||
"github.com/superioz/aqua/internal/mime"
|
||||
"github.com/superioz/aqua/internal/request"
|
||||
"github.com/superioz/aqua/internal/storage"
|
||||
"github.com/superioz/aqua/pkg/env"
|
||||
"k8s.io/klog"
|
||||
|
@ -20,27 +20,13 @@ const (
|
|||
SizeMegaByte = 1 << (10 * 2)
|
||||
)
|
||||
|
||||
var (
|
||||
emptyRequestMetadata = &RequestMetadata{Expiration: storage.ExpireNever}
|
||||
)
|
||||
|
||||
// TODO when accessing: `/N2YwODUx.mp4` => `/N2YwODUx`
|
||||
|
||||
// RequestFormFile is the metadata we get from the file
|
||||
// which is requested to be uploaded.
|
||||
type RequestFormFile struct {
|
||||
File multipart.File
|
||||
ContentType string
|
||||
ContentLength int64
|
||||
}
|
||||
|
||||
type RequestMetadata struct {
|
||||
Expiration int64 `json:"expiration"`
|
||||
}
|
||||
|
||||
type UploadHandler struct {
|
||||
AuthConfig *config.AuthConfig
|
||||
FileStorage *storage.FileStorage
|
||||
|
||||
// excluded as per defined by the environment variable
|
||||
// FILE_EXTENSIONS_EXCEPT
|
||||
exclMimeTypes []string
|
||||
}
|
||||
|
||||
func NewUploadHandler() *UploadHandler {
|
||||
|
@ -48,6 +34,7 @@ func NewUploadHandler() *UploadHandler {
|
|||
handler.ReloadAuthConfig()
|
||||
|
||||
handler.FileStorage = storage.NewFileStorage()
|
||||
handler.exclMimeTypes = env.ListOrDefault("FILE_EXTENSIONS_EXCLUDED", []string{"image/png", "image/jpeg"})
|
||||
return handler
|
||||
}
|
||||
|
||||
|
@ -124,8 +111,8 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
|||
}
|
||||
defer of.Close()
|
||||
|
||||
metadata := getMetadata(form)
|
||||
rff := &RequestFormFile{
|
||||
metadata := request.GetMetadata(form)
|
||||
rff := &request.RequestFormFile{
|
||||
File: of,
|
||||
ContentType: ct,
|
||||
ContentLength: c.Request.ContentLength,
|
||||
|
@ -145,22 +132,26 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
|||
klog.Infof("Stored file %s (expiresIn: %s)", sf.Id, expiresIn)
|
||||
metrics.IncFilesUploaded()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"id": sf.Id})
|
||||
// add extension to file name if it is enabled and
|
||||
// the extension is not excluded
|
||||
storedName := sf.Id
|
||||
if env.BoolOrDefault("FILE_EXTENSIONS_RESPONSE", true) && !h.isExtensionExcluded(sf.MimeType) {
|
||||
storedName = fmt.Sprintf("%s.%s", storedName, mime.GetExtension(sf.MimeType))
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"fileName": storedName})
|
||||
}
|
||||
|
||||
func getMetadata(form *multipart.Form) *RequestMetadata {
|
||||
metaRawList := form.Value["metadata"]
|
||||
if len(metaRawList) == 0 {
|
||||
return emptyRequestMetadata
|
||||
// isExtensionExcluded returns if the given mime type
|
||||
// should be excluded by the extension response rule, which states if
|
||||
// an extension should be appended to the file name when responding.
|
||||
func (h *UploadHandler) isExtensionExcluded(mimeType string) bool {
|
||||
for _, exclMimeType := range h.exclMimeTypes {
|
||||
if exclMimeType == mimeType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
metaRaw := metaRawList[0]
|
||||
|
||||
var metadata *RequestMetadata
|
||||
err := json.Unmarshal([]byte(metaRaw), &metadata)
|
||||
if err != nil {
|
||||
return emptyRequestMetadata
|
||||
}
|
||||
return metadata
|
||||
return false
|
||||
}
|
||||
|
||||
// workaround for file Content-Type headers
|
||||
|
@ -191,10 +182,17 @@ func getToken(c *gin.Context) string {
|
|||
// HandleStaticFiles takes the files inside the configured file storage
|
||||
// path and serves them to the client.
|
||||
func HandleStaticFiles() gin.HandlerFunc {
|
||||
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", storage.EnvDefaultFileStoragePath)
|
||||
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", config.EnvDefaultFileStoragePath)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
fileName := c.Param("file")
|
||||
|
||||
// the file name could contain the extension
|
||||
// we split it and ship it.
|
||||
if strings.Contains(fileName, ".") {
|
||||
fileName = strings.Split(fileName, ".")[0]
|
||||
}
|
||||
|
||||
fullPath := fileStoragePath + fileName
|
||||
|
||||
f, err := os.Open(fullPath)
|
||||
|
|
|
@ -28,7 +28,7 @@ var (
|
|||
|
||||
// IsValid checks if given type is inside Types map
|
||||
func IsValid(t string) bool {
|
||||
for _, mt := range Types {
|
||||
for mt := range Types {
|
||||
if mt == t {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/superioz/aqua/internal/config"
|
||||
"mime/multipart"
|
||||
)
|
||||
|
||||
var (
|
||||
emptyRequestMetadata = &RequestMetadata{Expiration: config.ExpireNever}
|
||||
)
|
||||
|
||||
// RequestFormFile is the metadata we get from the file
|
||||
// which is requested to be uploaded.
|
||||
type RequestFormFile struct {
|
||||
File multipart.File
|
||||
ContentType string
|
||||
ContentLength int64
|
||||
}
|
||||
|
||||
type RequestMetadata struct {
|
||||
Expiration int64 `json:"expiration"`
|
||||
}
|
||||
|
||||
func GetMetadata(form *multipart.Form) *RequestMetadata {
|
||||
metaRawList := form.Value["metadata"]
|
||||
if len(metaRawList) == 0 {
|
||||
return emptyRequestMetadata
|
||||
}
|
||||
metaRaw := metaRawList[0]
|
||||
|
||||
var metadata *RequestMetadata
|
||||
err := json.Unmarshal([]byte(metaRaw), &metadata)
|
||||
if err != nil {
|
||||
return emptyRequestMetadata
|
||||
}
|
||||
return metadata
|
||||
}
|
|
@ -5,8 +5,9 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/superioz/aqua/internal/handler"
|
||||
"github.com/superioz/aqua/internal/config"
|
||||
"github.com/superioz/aqua/internal/metrics"
|
||||
"github.com/superioz/aqua/internal/request"
|
||||
"github.com/superioz/aqua/pkg/env"
|
||||
"k8s.io/klog"
|
||||
"strings"
|
||||
|
@ -15,13 +16,6 @@ import (
|
|||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
const (
|
||||
ExpireNever = -1
|
||||
|
||||
EnvDefaultFileStoragePath = "/var/lib/aqua/files/"
|
||||
EnvDefaultMetaDbPath = "/var/lib/aqua/"
|
||||
)
|
||||
|
||||
type StoredFile struct {
|
||||
Id string
|
||||
UploadedAt int64
|
||||
|
@ -43,14 +37,14 @@ type FileStorage struct {
|
|||
}
|
||||
|
||||
func NewFileStorage() *FileStorage {
|
||||
metaDbFilePath := env.StringOrDefault("FILE_META_DB_PATH", EnvDefaultMetaDbPath)
|
||||
metaDbFilePath := env.StringOrDefault("FILE_META_DB_PATH", config.EnvDefaultMetaDbPath)
|
||||
fileMetaDb := NewSqliteFileMetaDatabase(metaDbFilePath)
|
||||
err := fileMetaDb.Connect()
|
||||
if err != nil {
|
||||
klog.Errorf("Could not connect to file meta db: %v", err)
|
||||
}
|
||||
|
||||
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", EnvDefaultFileStoragePath)
|
||||
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", config.EnvDefaultFileStoragePath)
|
||||
fileSystem := NewLocalFileStorage(fileStoragePath)
|
||||
|
||||
return &FileStorage{
|
||||
|
@ -102,7 +96,7 @@ func (fs *FileStorage) Cleanup() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (fs *FileStorage) StoreFile(rff *handler.RequestFormFile, expiration int64) (*StoredFile, error) {
|
||||
func (fs *FileStorage) StoreFile(rff *request.RequestFormFile, expiration int64) (*StoredFile, error) {
|
||||
name, err := getRandomFileName(env.IntOrDefault("FILE_NAME_LENGTH", 8))
|
||||
if err != nil {
|
||||
return nil, errors.New("could not generate random name")
|
||||
|
@ -116,8 +110,8 @@ func (fs *FileStorage) StoreFile(rff *handler.RequestFormFile, expiration int64)
|
|||
|
||||
currentTime := time.Now().Unix()
|
||||
expAt := currentTime + expiration
|
||||
if expiration == ExpireNever {
|
||||
expAt = ExpireNever
|
||||
if expiration == config.ExpireNever {
|
||||
expAt = config.ExpireNever
|
||||
}
|
||||
|
||||
sf := &StoredFile{
|
||||
|
|
|
@ -3,6 +3,7 @@ package env
|
|||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func String(name string) (string, bool) {
|
||||
|
@ -57,3 +58,16 @@ func BoolOrDefault(name string, def bool) bool {
|
|||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func ListOrDefault(name string, def []string) []string {
|
||||
s, ok := String(name)
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
|
||||
spl := strings.Split(s, ",")
|
||||
if len(spl) == 0 {
|
||||
return def
|
||||
}
|
||||
return spl
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue