package file import ( "context" "errors" "mime/multipart" "strings" ) var ( ErrDependencyUnavailable = errors.New("dependency unavailable") ErrInvalidFile = errors.New("invalid file") ) const maxUploadSize = 10 * 1024 * 1024 var allowedContentTypes = map[string]bool{ "image/jpeg": true, "image/png": true, "image/webp": true, "application/pdf": true, } type Service struct { storage *Storage } func NewService(storage *Storage) *Service { return &Service{storage: storage} } func (s *Service) Upload(req uploadRequest) (*UploadDTO, error) { if s.storage == nil { return nil, ErrDependencyUnavailable } if req.Header == nil || req.Reader == nil || req.Header.Size <= 0 || req.Header.Size > maxUploadSize { return nil, ErrInvalidFile } contentType := req.ContentType if contentType == "" { contentType = req.Header.Header.Get("Content-Type") } if !allowedContentTypes[contentType] { return nil, ErrInvalidFile } scene := normalizeScene(req.Scene) key, err := s.storage.Put(req.Context, scene, req.Header, req.Reader, contentType) if err != nil { return nil, err } return &UploadDTO{ ObjectKey: key, URL: "/api/files/object?key=" + key, Filename: req.Header.Filename, ContentType: contentType, Size: req.Header.Size, }, nil } func normalizeScene(scene string) string { scene = strings.TrimSpace(strings.ToLower(scene)) switch scene { case "listing", "handoff", "dispute", "realname", "avatar": return scene default: return "misc" } } type uploadRequest struct { Context context.Context Scene string Header *multipart.FileHeader Reader multipart.File ContentType string }