BeaconCMS / beacon

Open-source content management system (CMS) built with Phoenix LiveView. Faster render times to boost SEO performance, even for the most content-heavy pages.
https://beaconcms.org
MIT License
1.04k stars 101 forks source link

Asset Manager #31

Open bcardarella opened 2 years ago

bcardarella commented 2 years ago

We need an interface for managing all assets that are uploaded to a site managed by Beacon.

I'd prefer that the asset manager does not permit directories. It should be a flat file system that allows for the following

This should be a LiveView UI and available/unique per-site in Beacon

Assets should be stored in the database as binary data types

leandrocp commented 1 year ago

@bcardarella and @TheFirstAvenger where the asset manager routes fit in the project? The current management namespace is /page_management so that would put the asset manager in /page_management/assets but that naming feels a bit off. Maybe renaming to something more generic like management, admin? Wdyt?

bcardarella commented 1 year ago

@leandrocp I think naming to management or admin would potentially create namespace collisions. However, we can coach this by suggesting that in that event beacon should be namespaced under its own route

So I'm OK with using /admin for this but it should be up to whoever is adding beacon to nest it under /beacon or /cms or whatever they want

leandrocp commented 1 year ago

Assets should be stored in the database as binary data types

@bcardarella DB gives the portability we need but it's not that efficient and I was thinking how to serve those files without imposing infrastructure work on the final users, so an idea is a module that will hold binary content and serve it on a route:

On layouts and pages one can request an asset using a Beacon component:

<BeaconWeb.MediaLibrary.image name="logo.jpg"/>
# or audio, pdf, and so on...

A controller will fetch and serve that asset:

# controller
binary = Beacon.MediaLibrary.fetch_asset(name)

conn
|> Plug.Conn.put_resp_header("content-type", "image/jpg; charset=utf-8") # depends on the file
|> Plug.Conn.send_resp(200, binary)

And the module responsible for returning the binary, either from in-memory or from the DB:

defmodule Beacon.MediaLibrary do
  # fetch from in-memory if available or fallback db
  def fetch_asset(name) do
  end

  # runtime compile functions holding binary content
  for {name, binary} <- Beacon.MediaLibrary.used_assets() do
    def "#{name}", do: binary
  end
end

I'm leaving some details out to keep it simple, but that's the core idea. Eventually we'll have to deal with:

  1. Memory usage but we can limit how much we keep in memory with a user config parameter, or keep just the most frequent assets in-memory. Everything else is fetched from DB.
  2. Security

That can be implemented in a following PR but I'd like some feedback to guide how asset serving will be designed.

/cc @TheFirstAvenger

bcardarella commented 1 year ago

@leandrocp the idea is to have all assets served via a CDN but long-term storage is in the database. When an asset Create, Update, or Destroy action takes place it should do on on the database then prime the CDN cache and register the asset on the CDN. This way first requests are not hung up. We should actually have a lifecycle for updating assets. The cached asset is not replaced until the updated asset is in the database.

What we don't want is a cache strategy that will cache on demand or on cache misses. This is a common strategy but in high request situations it can actually act as a DOS attack if enough requests come in before the asset is cached.