package media import ( "context" "errors" "fmt" "io" "github.com/google/uuid" ) var ( ErrFileTooLarge = errors.New("file exceeds the maximum allowed size") ErrUnsupportedType = errors.New("unsupported file type") ) // allowedTypes maps accepted MIME types to a safe file extension. Anything // not in this list is rejected -- uploads are never trusted to declare // their own extension. var allowedTypes = map[string]string{ "image/jpeg": ".jpg", "image/png": ".png", "image/webp": ".webp", "image/gif": ".gif", "video/mp4": ".mp4", "video/webm": ".webm", } type Service struct { repo Repository storage Storage maxSizeByte int64 } func NewService(repo Repository, storage Storage, maxSizeBytes int64) *Service { return &Service{repo: repo, storage: storage, maxSizeByte: maxSizeBytes} } func (s *Service) List(ctx context.Context) ([]*Media, error) { return s.repo.List(ctx) } func (s *Service) Get(ctx context.Context, id uuid.UUID) (*Media, error) { return s.repo.FindByID(ctx, id) } func (s *Service) Upload(ctx context.Context, filename string, reader io.Reader, size int64, contentType, altText string) (*Media, error) { if size > s.maxSizeByte { return nil, ErrFileTooLarge } ext, ok := allowedTypes[contentType] if !ok { return nil, ErrUnsupportedType } key := uuid.NewString() + ext url, err := s.storage.Save(ctx, key, reader, size, contentType) if err != nil { return nil, fmt.Errorf("save file: %w", err) } m := &Media{ ID: uuid.New(), Filename: filename, StorageKey: key, URL: url, MimeType: contentType, SizeBytes: size, AltText: altText, } if err := s.repo.Create(ctx, m); err != nil { _ = s.storage.Delete(ctx, key) return nil, fmt.Errorf("save metadata: %w", err) } return m, nil } func (s *Service) UpdateAltText(ctx context.Context, id uuid.UUID, altText string) (*Media, error) { m, err := s.repo.FindByID(ctx, id) if err != nil { return nil, err } m.AltText = altText if err := s.repo.Update(ctx, m); err != nil { return nil, err } return m, nil } func (s *Service) Delete(ctx context.Context, id uuid.UUID) error { m, err := s.repo.FindByID(ctx, id) if err != nil { return err } if err := s.storage.Delete(ctx, m.StorageKey); err != nil { return fmt.Errorf("delete stored file: %w", err) } return s.repo.Delete(ctx, id) }