2022-09-20 12:55:00 +02:00
|
|
|
package db
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2023-03-13 02:04:09 +01:00
|
|
|
"crypto/sha256"
|
2022-09-20 12:55:00 +02:00
|
|
|
"encoding/base64"
|
2023-03-13 02:04:09 +01:00
|
|
|
"encoding/hex"
|
2023-03-30 14:44:32 +02:00
|
|
|
_ "image/gif"
|
|
|
|
_ "image/png"
|
2022-09-20 12:55:00 +02:00
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"emperror.dev/errors"
|
2023-05-11 00:59:40 +02:00
|
|
|
"github.com/davidbyttow/govips/v2/vips"
|
2022-09-20 12:55:00 +02:00
|
|
|
"github.com/minio/minio-go/v7"
|
|
|
|
"github.com/rs/xid"
|
|
|
|
)
|
|
|
|
|
|
|
|
const ErrInvalidDataURI = errors.Sentinel("invalid data URI")
|
|
|
|
const ErrInvalidContentType = errors.Sentinel("invalid avatar content type")
|
|
|
|
|
|
|
|
// ConvertAvatar parses an avatar from a data URI, converts it to WebP and JPEG, and returns the results.
|
|
|
|
func (db *DB) ConvertAvatar(data string) (
|
2023-03-30 14:44:32 +02:00
|
|
|
webpOut *bytes.Buffer,
|
|
|
|
jpgOut *bytes.Buffer,
|
2022-09-20 12:55:00 +02:00
|
|
|
err error,
|
|
|
|
) {
|
2023-05-11 00:59:40 +02:00
|
|
|
defer vips.ShutdownThread()
|
|
|
|
|
2022-09-20 12:55:00 +02:00
|
|
|
data = strings.TrimSpace(data)
|
|
|
|
if !strings.Contains(data, ",") || !strings.Contains(data, ":") || !strings.Contains(data, ";") {
|
|
|
|
return nil, nil, ErrInvalidDataURI
|
|
|
|
}
|
|
|
|
split := strings.Split(data, ",")
|
|
|
|
|
2023-03-30 14:44:32 +02:00
|
|
|
rawData, err := base64.StdEncoding.DecodeString(split[1])
|
2022-09-20 12:55:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errors.Wrap(err, "invalid base64 data")
|
|
|
|
}
|
|
|
|
|
2023-05-11 00:59:40 +02:00
|
|
|
image, err := vips.LoadImageFromBuffer(rawData, nil)
|
2022-09-20 12:55:00 +02:00
|
|
|
if err != nil {
|
2023-05-06 15:53:07 +02:00
|
|
|
return nil, nil, errors.Wrap(err, "decoding image")
|
2022-09-20 12:55:00 +02:00
|
|
|
}
|
|
|
|
|
2023-05-11 00:59:40 +02:00
|
|
|
err = image.ThumbnailWithSize(512, 512, vips.InterestingCentre, vips.SizeBoth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errors.Wrap(err, "resizing image")
|
|
|
|
}
|
2022-09-20 12:55:00 +02:00
|
|
|
|
2023-05-11 00:59:40 +02:00
|
|
|
webpExport := vips.NewWebpExportParams()
|
|
|
|
webpExport.Quality = 90
|
|
|
|
webpB, _, err := image.ExportWebp(webpExport)
|
2022-09-20 12:55:00 +02:00
|
|
|
if err != nil {
|
2023-05-11 00:59:40 +02:00
|
|
|
return nil, nil, errors.Wrap(err, "exporting webp image")
|
2022-09-20 12:55:00 +02:00
|
|
|
}
|
2023-05-11 00:59:40 +02:00
|
|
|
webpOut = bytes.NewBuffer(webpB)
|
2022-09-20 12:55:00 +02:00
|
|
|
|
2023-05-11 00:59:40 +02:00
|
|
|
jpegExport := vips.NewJpegExportParams()
|
|
|
|
jpegExport.Quality = 80
|
|
|
|
jpegB, _, err := image.ExportJpeg(jpegExport)
|
2022-09-20 12:55:00 +02:00
|
|
|
if err != nil {
|
2023-05-11 00:59:40 +02:00
|
|
|
return nil, nil, errors.Wrap(err, "exporting jpeg image")
|
2022-09-20 12:55:00 +02:00
|
|
|
}
|
2023-05-11 00:59:40 +02:00
|
|
|
jpgOut = bytes.NewBuffer(jpegB)
|
2022-09-20 12:55:00 +02:00
|
|
|
|
2023-03-30 14:44:32 +02:00
|
|
|
return webpOut, jpgOut, nil
|
2022-09-20 12:55:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) WriteUserAvatar(ctx context.Context,
|
2023-03-13 02:04:09 +01:00
|
|
|
userID xid.ID, webp *bytes.Buffer, jpeg *bytes.Buffer,
|
2022-09-20 12:55:00 +02:00
|
|
|
) (
|
2023-03-13 02:04:09 +01:00
|
|
|
hash string, err error,
|
2022-09-20 12:55:00 +02:00
|
|
|
) {
|
2023-03-13 02:04:09 +01:00
|
|
|
hasher := sha256.New()
|
|
|
|
_, err = hasher.Write(webp.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "hashing webp avatar")
|
|
|
|
}
|
|
|
|
hash = hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
|
|
|
|
_, err = db.minio.PutObject(ctx, db.minioBucket, "/users/"+userID.String()+"/"+hash+".webp", webp, -1, minio.PutObjectOptions{
|
2023-05-22 15:00:05 +02:00
|
|
|
ContentType: "image/webp",
|
|
|
|
SendContentMd5: true,
|
2022-09-20 12:55:00 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
2023-03-13 02:04:09 +01:00
|
|
|
return "", errors.Wrap(err, "uploading webp avatar")
|
2022-09-20 12:55:00 +02:00
|
|
|
}
|
|
|
|
|
2023-03-13 02:04:09 +01:00
|
|
|
_, err = db.minio.PutObject(ctx, db.minioBucket, "/users/"+userID.String()+"/"+hash+".jpg", jpeg, -1, minio.PutObjectOptions{
|
2023-05-22 15:00:05 +02:00
|
|
|
ContentType: "image/jpeg",
|
|
|
|
SendContentMd5: true,
|
2022-09-20 12:55:00 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
2023-03-13 02:04:09 +01:00
|
|
|
return "", errors.Wrap(err, "uploading jpeg avatar")
|
2022-09-20 12:55:00 +02:00
|
|
|
}
|
|
|
|
|
2023-03-13 02:04:09 +01:00
|
|
|
return hash, nil
|
2022-09-20 12:55:00 +02:00
|
|
|
}
|
2022-10-03 10:59:30 +02:00
|
|
|
|
|
|
|
func (db *DB) WriteMemberAvatar(ctx context.Context,
|
2023-03-13 02:04:09 +01:00
|
|
|
memberID xid.ID, webp *bytes.Buffer, jpeg *bytes.Buffer,
|
2022-10-03 10:59:30 +02:00
|
|
|
) (
|
2023-03-13 02:04:09 +01:00
|
|
|
hash string, err error,
|
2022-10-03 10:59:30 +02:00
|
|
|
) {
|
2023-03-13 02:04:09 +01:00
|
|
|
hasher := sha256.New()
|
|
|
|
_, err = hasher.Write(webp.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "hashing webp avatar")
|
|
|
|
}
|
|
|
|
hash = hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
|
2023-05-22 15:59:49 +02:00
|
|
|
_, err = db.minio.PutObject(ctx, db.minioBucket, "members/"+memberID.String()+"/"+hash+".webp", webp, -1, minio.PutObjectOptions{
|
2023-05-22 15:00:05 +02:00
|
|
|
ContentType: "image/webp",
|
|
|
|
SendContentMd5: true,
|
2022-10-03 10:59:30 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
2023-03-13 02:04:09 +01:00
|
|
|
return "", errors.Wrap(err, "uploading webp avatar")
|
2022-10-03 10:59:30 +02:00
|
|
|
}
|
|
|
|
|
2023-05-22 15:59:49 +02:00
|
|
|
_, err = db.minio.PutObject(ctx, db.minioBucket, "members/"+memberID.String()+"/"+hash+".jpg", jpeg, -1, minio.PutObjectOptions{
|
2023-05-22 15:00:05 +02:00
|
|
|
ContentType: "image/jpeg",
|
|
|
|
SendContentMd5: true,
|
2022-10-03 10:59:30 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
2023-03-13 02:04:09 +01:00
|
|
|
return "", errors.Wrap(err, "uploading jpeg avatar")
|
2022-10-03 10:59:30 +02:00
|
|
|
}
|
|
|
|
|
2023-03-13 02:04:09 +01:00
|
|
|
return hash, nil
|
2022-10-03 10:59:30 +02:00
|
|
|
}
|
2023-03-13 02:19:03 +01:00
|
|
|
|
|
|
|
func (db *DB) DeleteUserAvatar(ctx context.Context, userID xid.ID, hash string) error {
|
2023-05-22 15:59:49 +02:00
|
|
|
err := db.minio.RemoveObject(ctx, db.minioBucket, "users/"+userID.String()+"/"+hash+".webp", minio.RemoveObjectOptions{})
|
2023-03-13 02:19:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "deleting webp avatar")
|
|
|
|
}
|
|
|
|
|
2023-05-22 15:59:49 +02:00
|
|
|
err = db.minio.RemoveObject(ctx, db.minioBucket, "users/"+userID.String()+"/"+hash+".jpg", minio.RemoveObjectOptions{})
|
2023-03-13 02:19:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "deleting jpeg avatar")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) DeleteMemberAvatar(ctx context.Context, memberID xid.ID, hash string) error {
|
2023-05-22 15:59:49 +02:00
|
|
|
err := db.minio.RemoveObject(ctx, db.minioBucket, "members/"+memberID.String()+"/"+hash+".webp", minio.RemoveObjectOptions{})
|
2023-03-13 02:19:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "deleting webp avatar")
|
|
|
|
}
|
|
|
|
|
2023-05-22 15:59:49 +02:00
|
|
|
err = db.minio.RemoveObject(ctx, db.minioBucket, "members/"+memberID.String()+"/"+hash+".jpg", minio.RemoveObjectOptions{})
|
2023-03-13 02:19:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "deleting jpeg avatar")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-15 15:24:51 +01:00
|
|
|
|
|
|
|
func (db *DB) UserAvatar(ctx context.Context, userID xid.ID, hash string) (io.ReadCloser, error) {
|
2023-05-22 15:59:49 +02:00
|
|
|
obj, err := db.minio.GetObject(ctx, db.minioBucket, "users/"+userID.String()+"/"+hash+".webp", minio.GetObjectOptions{})
|
2023-03-15 15:24:51 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "getting object")
|
|
|
|
}
|
|
|
|
return obj, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) MemberAvatar(ctx context.Context, memberID xid.ID, hash string) (io.ReadCloser, error) {
|
2023-05-22 15:59:49 +02:00
|
|
|
obj, err := db.minio.GetObject(ctx, db.minioBucket, "members/"+memberID.String()+"/"+hash+".webp", minio.GetObjectOptions{})
|
2023-03-15 15:24:51 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "getting object")
|
|
|
|
}
|
|
|
|
return obj, nil
|
|
|
|
}
|