ash icon indicating copy to clipboard operation
ash copied to clipboard

Proposal: Create Ash module to deal with `file` resources / uploads.

Open jakeschurch opened this issue 1 year ago • 4 comments

Is your feature request related to a problem? Please describe. Ash is great. We're missing interacting with files! I'm open to contributing at the very minimum an MVP to solve this 😄

Describe the solution you'd like Use waffle_ecto under the hood for new module `AshFile.Resource.

Create a composite type {:file, MyApp.Uploader.Foo}

With a composite type of {:file, :uploader}:

  • we could provide mix task for file uploader definition creation based that would map to waffle_ecto's Uploader resource definition with ash integration.
  • allows us to centralize file upload logic that could be reused in other resources, e.g. MyApp.Avatar.Type across multiple resources
  • rely much more on waffle_ecto work to get MVP ready, implementation could be as simple as a macro def and some mix tasks
  • all :uploader types auto-added to custom ash types
  • potential to embed as an AshFile.Resource, all arguments defined in relevant sections in related module, centralizing logic

Describe alternatives you've considered

With a inline singleton to an Ash.Resource:

Macro expansion could define sub-module Uploaders with embedded waffle_ecto resource, with use Ash.Resource, extensions: [Ash.File] for 1-1 mapping and feature parity with waffle_ecto.

Definitely way more implementation work up-front to get MVP ready (for me), but seems more ash design-compliant than composite type approach.

This would allow us to define a global ash_files extension api with default options, override-able at attribute level.

E.g:


defmodule MyApp.Accounts.User do
  use Ash.Resource,
  extensions: [Ash.File]
  
  ...
  
  attributes do
    ...
    attribute :avatar, :file, allow_nil?: false, constraints: [
      storage: (:s3 | :local),
      validate_file?: (:bool | :fn),  # for form uploads
      s3_opts: [
        access_key_id: :string,
        secret_access_key: :string,
        bucket: :string,
        asset_host: :string,
        storage_dir: (:string | :fn)
        storage_dir_prefix (:string | :fn)
        async?: :bool,
        acl: :enum
      ],
      common_opts: [
        accepts: ({:list :string} | :fn) # file type
        filename: (:fn),
        versions: ({:list :string}, :string, :fn)
        ...  
      ],
      local_opts: [
        ...
      ]
    ]
  end
end

Express the feature either with a change to resource syntax, or with a change to the resource interface

Due to extent of change, I'd like to get to an agreement on API design before filling this section out if ok.

Additional context

I'm currently leaning towards the composite approach due to less time needed for implementation, but the decision in the end is ultimately left up to @zachdaniel 😄

jakeschurch avatar Apr 23 '24 21:04 jakeschurch

I love this, but I think it should be added as a configurable extension with multiple strategies ala ash authentication.

jimsynz avatar Apr 23 '24 23:04 jimsynz

@jimsynz could you say more? Waffle ecto allows for s3 and local - if you are referring to multiple storage providers.

If you are referring to blob datatypes, I wonder if that would better fall under repos like ash_postgres.

This at the very least could be an interim tool.

What other strategies would you like to see?

jakeschurch avatar Apr 24 '24 00:04 jakeschurch

Its interesting...if it can be encapsulated with a type then it should probably be done fully within the bounds of a type as opposed to an extension, but we'll really have to see how it takes shape.

zachdaniel avatar Apr 24 '24 02:04 zachdaniel

I think let's start with the type-first approach you've got here, and see how far it can go. For now, let's call it ash_waffle, as opposed to ash_file or ash_file_upload or w/e. If we determine that this is the defecto strategy for a certain type of file uploads, we can make ash_file or ash_file_upload depend on this package, for example.

zachdaniel avatar Apr 24 '24 02:04 zachdaniel