feat: add tenant entry registration APIs
This commit is contained in:
@@ -153,6 +153,29 @@ func (fh *fshandler) Upload(fdef *types.FileDef, file io.Reader) (string, int64,
|
||||
return fh.ServeURL + fname, size, nil
|
||||
}
|
||||
|
||||
func (fh *fshandler) UploadPublic(fdef *media.PublicFileDef, file io.Reader) (string, int64, error) {
|
||||
location, err := publicFileLocation(fh.FileUploadDirectory, fdef.Purpose, fdef.ID, fdef.CreatedAt)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Dir(location), 0777); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
outfile, err := os.Create(location)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
size, err := io.Copy(outfile, file)
|
||||
outfile.Close()
|
||||
if err != nil {
|
||||
os.Remove(location)
|
||||
return "", 0, err
|
||||
}
|
||||
fdef.Location = location
|
||||
fdef.ETag = etagFromPath(location)
|
||||
return location, size, nil
|
||||
}
|
||||
|
||||
// Download processes request for file download.
|
||||
// The returned ReadSeekCloser must be closed after use.
|
||||
func (fh *fshandler) Download(tenantID types.TenantID, url string) (*types.FileDef, media.ReadSeekCloser, error) {
|
||||
@@ -226,6 +249,28 @@ func tenantFileLocation(root string, tenantID types.TenantID, fileID string, cre
|
||||
createdAt.Format("2006"), createdAt.Format("01"), fileID), nil
|
||||
}
|
||||
|
||||
func publicFileLocation(root, purpose, fileID string, createdAt time.Time) (string, error) {
|
||||
purpose = cleanPublicPurpose(purpose)
|
||||
fileID = strings.Trim(fileID, `/\`)
|
||||
if root == "" || purpose == "" || fileID == "" {
|
||||
return "", types.ErrMalformed
|
||||
}
|
||||
if createdAt.IsZero() {
|
||||
createdAt = time.Now().UTC()
|
||||
}
|
||||
return filepath.Join(root, "public", purpose, createdAt.Format("2006"), createdAt.Format("01"), fileID), nil
|
||||
}
|
||||
|
||||
func cleanPublicPurpose(purpose string) string {
|
||||
purpose = strings.Trim(strings.ToLower(strings.TrimSpace(purpose)), `/\`)
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '_' || r == '-' {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}, purpose)
|
||||
}
|
||||
|
||||
func init() {
|
||||
store.RegisterMediaHandler(handlerName, &fshandler{})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"slices"
|
||||
|
||||
@@ -45,6 +46,19 @@ type Handler interface {
|
||||
GetIdFromUrl(url string) types.Uid
|
||||
}
|
||||
|
||||
type PublicFileDef struct {
|
||||
ID string
|
||||
Purpose string
|
||||
MimeType string
|
||||
CreatedAt time.Time
|
||||
Location string
|
||||
ETag string
|
||||
}
|
||||
|
||||
type PublicUploader interface {
|
||||
UploadPublic(fdef *PublicFileDef, file io.Reader) (string, int64, error)
|
||||
}
|
||||
|
||||
type AllowedOrigin struct {
|
||||
Origin string
|
||||
URL url.URL
|
||||
|
||||
@@ -262,6 +262,29 @@ func (oh *ossHandler) Upload(fdef *types.FileDef, file io.Reader) (string, int64
|
||||
return oh.conf.ServeURL + fname, rc.count, nil
|
||||
}
|
||||
|
||||
func (oh *ossHandler) UploadPublic(fdef *media.PublicFileDef, file io.Reader) (string, int64, error) {
|
||||
key, err := publicObjectKey(fdef.Purpose, fdef.ID, fdef.CreatedAt)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
rc := readerCounter{reader: file}
|
||||
options := []aliyunoss.Option{aliyunoss.CacheControl(oh.conf.CacheControl)}
|
||||
if fdef.MimeType != "" {
|
||||
options = append(options, aliyunoss.ContentType(fdef.MimeType))
|
||||
}
|
||||
if err = oh.bucket.PutObject(key, &rc, options...); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
fdef.Location = key
|
||||
fdef.ETag = etagFromLocation(key)
|
||||
if meta, err := oh.bucket.GetObjectDetailedMeta(key); err == nil {
|
||||
if etag := strings.Trim(meta.Get("ETag"), "\""); etag != "" {
|
||||
fdef.ETag = etag
|
||||
}
|
||||
}
|
||||
return key, rc.count, nil
|
||||
}
|
||||
|
||||
// Download is unsupported because OSS objects are served through signed redirects.
|
||||
func (oh *ossHandler) Download(tenantID types.TenantID, url string) (*types.FileDef, media.ReadSeekCloser, error) {
|
||||
return nil, nil, types.ErrUnsupported
|
||||
@@ -308,6 +331,27 @@ func tenantObjectKey(tenantID types.TenantID, fileID string, createdAt time.Time
|
||||
createdAt.Format("2006"), createdAt.Format("01"), fileID), nil
|
||||
}
|
||||
|
||||
func publicObjectKey(purpose, fileID string, createdAt time.Time) (string, error) {
|
||||
purpose = cleanPublicPurpose(purpose)
|
||||
if purpose == "" || fileID == "" {
|
||||
return "", types.ErrMalformed
|
||||
}
|
||||
if createdAt.IsZero() {
|
||||
createdAt = time.Now().UTC()
|
||||
}
|
||||
return path.Join("public", purpose, createdAt.Format("2006"), createdAt.Format("01"), strings.Trim(fileID, "/")), nil
|
||||
}
|
||||
|
||||
func cleanPublicPurpose(purpose string) string {
|
||||
purpose = strings.Trim(strings.ToLower(strings.TrimSpace(purpose)), "/")
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '_' || r == '-' {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}, purpose)
|
||||
}
|
||||
|
||||
func etagFromLocation(location string) string {
|
||||
hasher := fnv.New128()
|
||||
hasher.Write([]byte(location))
|
||||
|
||||
Reference in New Issue
Block a user