97 lines
3.1 KiB
Go
97 lines
3.1 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
|
|
"spark-server/internal/module/upload/entity"
|
|
"spark-server/internal/pkg/errs"
|
|
"spark-server/internal/pkg/i18n"
|
|
"spark-server/internal/pkg/reqctx"
|
|
"spark-server/internal/pkg/validate"
|
|
)
|
|
|
|
// CreateInput requests a single-object Presigned PUT. The client does not choose the key.
|
|
type CreateInput struct {
|
|
Purpose string `json:"purpose" validate:"required" label:"field.purpose" errmsg:"required:validate.upload.purpose_required"`
|
|
Mime string `json:"mime" validate:"required,max=128" label:"field.mime" errmsg:"required:validate.upload.mime_required;max:validate.upload.mime_invalid"`
|
|
Size int64 `json:"size" validate:"required,gt=0" label:"field.size" errmsg:"required:validate.upload.size_required;gt:validate.upload.size_invalid"`
|
|
GroupID int64 `json:"group_id" validate:"omitempty,gt=0" label:"field.group_id"`
|
|
}
|
|
|
|
func (s *Service) CreateCredential(ctx context.Context, userID int64, in *CreateInput) (*entity.Credential, error) {
|
|
if !s.ready() {
|
|
return nil, errs.ErrAvailable
|
|
}
|
|
if userID <= 0 {
|
|
return nil, errs.ErrUnauthorized
|
|
}
|
|
if in == nil {
|
|
return nil, errs.ErrInvalidArgument
|
|
}
|
|
if err := validate.StructLang(reqctx.Lang(ctx), in); err != nil {
|
|
return nil, err
|
|
}
|
|
if !s.allow(userID) {
|
|
return nil, errs.ErrRateLimited
|
|
}
|
|
return s.issuePut(ctx, userID, in)
|
|
}
|
|
|
|
func (s *Service) issuePut(ctx context.Context, userID int64, in *CreateInput) (*entity.Credential, error) {
|
|
_, mime, err := matchSpec(ctx, in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name := reqctx.NewObjectName()
|
|
ownerID := userID
|
|
if in.Purpose == entity.PurposeGroupAvatar {
|
|
if err := s.readyGroupAvatar(ctx, userID, in.GroupID); err != nil {
|
|
return nil, err
|
|
}
|
|
ownerID = in.GroupID
|
|
}
|
|
key := entity.BuildObjectKey(in.Purpose, ownerID, name, dayOf(s.now()), entity.ExtForMIME(mime))
|
|
url, err := s.store.PresignPut(ctx, key, mime, in.Size, s.ttl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &entity.Credential{
|
|
ObjectKey: key,
|
|
UploadURL: url,
|
|
Method: entity.MethodPUT,
|
|
Headers: map[string]string{
|
|
"Content-Type": mime,
|
|
"Content-Length": strconv.FormatInt(in.Size, 10),
|
|
"If-None-Match": "*",
|
|
},
|
|
ExpiresIn: s.expiresIn(),
|
|
}, nil
|
|
}
|
|
|
|
func matchSpec(ctx context.Context, in *CreateInput) (entity.Spec, string, error) {
|
|
lang := reqctx.Lang(ctx)
|
|
spec, ok := entity.LookupSpec(in.Purpose)
|
|
if !ok {
|
|
return entity.Spec{}, "", errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUploadPurposeInvalid, nil))
|
|
}
|
|
mime := entity.NormalizeMIME(in.Mime)
|
|
if _, ok := spec.MIMEs[mime]; !ok {
|
|
return entity.Spec{}, "", errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUploadMIMEInvalid, nil))
|
|
}
|
|
if in.Size > spec.MaxSize {
|
|
return entity.Spec{}, "", errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUploadSizeExceeded, nil))
|
|
}
|
|
return spec, mime, nil
|
|
}
|
|
|
|
func (s *Service) readyGroupAvatar(ctx context.Context, userID, groupID int64) error {
|
|
if groupID <= 0 {
|
|
return errs.ErrInvalidArgument.WithMsg(i18n.T(reqctx.Lang(ctx), i18n.KeyUploadGroupRequired, nil))
|
|
}
|
|
if s.groups == nil {
|
|
return nil
|
|
}
|
|
return s.groups.CanSetAvatar(ctx, userID, groupID)
|
|
}
|