2024-12-28 15:10:57 +00:00
|
|
|
package services
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"math/rand/v2"
|
|
|
|
nurl "net/url"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
localCache "git.solsynth.dev/hypernet/paperclip/pkg/internal/cache"
|
|
|
|
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
|
|
|
|
"git.solsynth.dev/hypernet/paperclip/pkg/internal/models"
|
|
|
|
"github.com/eko/gocache/lib/v4/cache"
|
|
|
|
"github.com/eko/gocache/lib/v4/marshaler"
|
|
|
|
"github.com/eko/gocache/lib/v4/store"
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
2024-12-28 15:16:07 +00:00
|
|
|
"github.com/minio/minio-go/v7"
|
|
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
2024-12-28 15:10:57 +00:00
|
|
|
"github.com/samber/lo"
|
|
|
|
)
|
|
|
|
|
|
|
|
type openAttachmentResult struct {
|
|
|
|
Attachment models.Attachment `json:"attachment"`
|
|
|
|
Boosts []models.AttachmentBoost `json:"boost"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetAttachmentOpenCacheKey(rid string) any {
|
|
|
|
return fmt.Sprintf("attachment-open#%s", rid)
|
|
|
|
}
|
|
|
|
|
|
|
|
func OpenAttachmentByRID(rid string, region ...string) (url string, mimetype string, err error) {
|
|
|
|
cacheManager := cache.New[any](localCache.S)
|
|
|
|
marshal := marshaler.New(cacheManager)
|
|
|
|
contx := context.Background()
|
|
|
|
|
|
|
|
var result *openAttachmentResult
|
|
|
|
if val, err := marshal.Get(
|
|
|
|
contx,
|
|
|
|
GetAttachmentOpenCacheKey(rid),
|
|
|
|
new(openAttachmentResult),
|
|
|
|
); err == nil {
|
|
|
|
result = val.(*openAttachmentResult)
|
|
|
|
}
|
|
|
|
|
|
|
|
if result == nil {
|
|
|
|
var attachment models.Attachment
|
|
|
|
if err = database.C.Where(models.Attachment{
|
|
|
|
Rid: rid,
|
|
|
|
}).
|
|
|
|
Preload("Pool").
|
|
|
|
Preload("Thumbnail").
|
|
|
|
Preload("Compressed").
|
|
|
|
Preload("Boosts").
|
|
|
|
First(&attachment).Error; err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var boosts []models.AttachmentBoost
|
|
|
|
boosts, err = ListBoostByAttachment(attachment.ID)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
result = &openAttachmentResult{
|
|
|
|
Attachment: attachment,
|
|
|
|
Boosts: boosts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(result.Attachment.MimeType) > 0 {
|
|
|
|
mimetype = result.Attachment.MimeType
|
|
|
|
}
|
|
|
|
|
|
|
|
var dest models.BaseDestination
|
|
|
|
var rawDest []byte
|
|
|
|
|
|
|
|
if len(region) > 0 {
|
2024-12-28 16:03:00 +00:00
|
|
|
if des, ok := DestinationsByRegion[region[0]]; ok {
|
2024-12-28 15:10:57 +00:00
|
|
|
for _, boost := range result.Boosts {
|
|
|
|
if boost.Destination == des.Index {
|
|
|
|
rawDest = des.Raw
|
|
|
|
json.Unmarshal(rawDest, &dest)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if rawDest == nil {
|
|
|
|
if len(result.Boosts) > 0 {
|
|
|
|
randomIdx := rand.IntN(len(result.Boosts))
|
2024-12-28 18:08:41 +00:00
|
|
|
boost := result.Boosts[randomIdx]
|
|
|
|
if des, ok := DestinationsByIndex[boost.Destination]; ok {
|
2024-12-28 15:10:57 +00:00
|
|
|
rawDest = des.Raw
|
|
|
|
json.Unmarshal(rawDest, &dest)
|
|
|
|
}
|
|
|
|
} else {
|
2024-12-28 16:03:00 +00:00
|
|
|
if des, ok := DestinationsByIndex[result.Attachment.Destination]; ok {
|
2024-12-28 15:10:57 +00:00
|
|
|
rawDest = des.Raw
|
|
|
|
json.Unmarshal(rawDest, &dest)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if rawDest == nil {
|
|
|
|
err = fmt.Errorf("no destination found")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch dest.Type {
|
|
|
|
case models.DestinationTypeLocal:
|
|
|
|
var destConfigured models.LocalDestination
|
|
|
|
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
|
|
|
|
url = "file://" + filepath.Join(destConfigured.Path, result.Attachment.Uuid)
|
|
|
|
return
|
|
|
|
case models.DestinationTypeS3:
|
|
|
|
var destConfigured models.S3Destination
|
|
|
|
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
|
2024-12-28 15:16:07 +00:00
|
|
|
if destConfigured.EnabledSigned {
|
|
|
|
var client *minio.Client
|
|
|
|
client, err = minio.New(destConfigured.Endpoint, &minio.Options{
|
|
|
|
Creds: credentials.NewStaticV4(destConfigured.SecretID, destConfigured.SecretKey, ""),
|
|
|
|
Secure: destConfigured.EnableSSL,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var uri *nurl.URL
|
|
|
|
uri, err = client.PresignedGetObject(context.Background(), destConfigured.Bucket, result.Attachment.Uuid, 60*time.Minute, nil)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
url = uri.String()
|
|
|
|
return
|
|
|
|
}
|
2024-12-28 15:10:57 +00:00
|
|
|
if len(destConfigured.AccessBaseURL) > 0 {
|
|
|
|
url = fmt.Sprintf(
|
|
|
|
"%s/%s",
|
|
|
|
destConfigured.AccessBaseURL,
|
|
|
|
nurl.QueryEscape(filepath.Join(destConfigured.Path, result.Attachment.Uuid)),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
protocol := lo.Ternary(destConfigured.EnableSSL, "https", "http")
|
|
|
|
url = fmt.Sprintf(
|
|
|
|
"%s://%s.%s/%s",
|
|
|
|
protocol,
|
|
|
|
destConfigured.Bucket,
|
|
|
|
destConfigured.Endpoint,
|
|
|
|
nurl.QueryEscape(filepath.Join(destConfigured.Path, result.Attachment.Uuid)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func CacheOpenAttachment(item *openAttachmentResult) {
|
|
|
|
if item == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cacheManager := cache.New[any](localCache.S)
|
|
|
|
marshal := marshaler.New(cacheManager)
|
|
|
|
contx := context.Background()
|
|
|
|
|
|
|
|
_ = marshal.Set(
|
|
|
|
contx,
|
|
|
|
GetAttachmentCacheKey(item.Attachment.Rid),
|
|
|
|
*item,
|
|
|
|
store.WithExpiration(60*time.Minute),
|
|
|
|
store.WithTags([]string{"attachment-open", fmt.Sprintf("user#%s", item.Attachment.Rid)}),
|
|
|
|
)
|
|
|
|
}
|