aesmail / kaffy

Powerfully simple admin package for phoenix applications
https://kaffy.fly.dev/admin/
MIT License
1.33k stars 155 forks source link

Any ideas on how to support multi tenancy? #179

Open zamith opened 3 years ago

zamith commented 3 years ago

Currently kaffy assumes that there is only one repo, and even for that repo it does use the dynamic_repo which is now widespread in Ecto. This makes it hard to implement admin pages for multi tenant resources, e.g. user accounts within different organizations, in different databases or database schemas.

I understand supporting this is not straightforward, as we'd also need a way to change between tenants, which has UI implications. Given that it's not really that easy to change a template in kaffy it means having a generic answer to the problem. Have you given this any thought?

rhblind commented 3 years ago

Hi, I'm also in the works of trying to build a multi-tenant application dashboard and are looking into Kaffy for doing the heavy lifting. For multi-tenancy support on the database level I'm considering using Triplex, which is based around the concept of having a separate db schema for each tenant and then using Ecto's Query Prefix for querying the correct schema.

It would be super nice if the query prefix could somehow be supported in Kaffy as well, as it (at least for me) seems like a good way of separating ie. tenant data from each other as well as giving us the possibility to do cross-schema queries for "common data".

zamith commented 3 years ago

Unfortunately that does not work for me, since I need to have different databases, not only schemas. Have you tried using the custom queries callbacks? You can define a custom_show_query on the admin module and add the prefix there, for example.

Then you have the issue of how to change between tenants, which I changed in my PR to be a select added via a type of extension called navigation extras. This is the trickier part in terms of UI and you can obviously choose to do it differently.

rhblind commented 3 years ago

I see, thanks for the tip! I haven't gotten that far really so I wasn't aware of that possibility. That would certainly work for different schemas. As for using multiple databases, I totally agree. That would've been a very nice feature to support. I've looked through your PR and it looks really useful.

Hope to see it merged soon 👍

zenbaku commented 3 years ago

For completeness and based on the answer by @zamith I share my implementation of tenanted resourced based

defmodule AppWeb.Admin.Tenanted do
  defmacro __using__(_opts) do
    quote do
      alias App.Repo

      def custom_index_query(conn, _schema, query) do
        query
        |> Ecto.Queryable.to_query()
        |> Map.put(:prefix, conn.assigns.prefix)
      end

      def custom_show_query(conn, _schema, query) do
        query
        |> Ecto.Queryable.to_query()
        |> Map.put(:prefix, conn.assigns.prefix)
      end

      def insert(conn, changeset) do
        Repo.insert(changeset, prefix: conn.assigns.prefix)
      end

      def update(conn, changeset) do
        Repo.update(changeset, prefix: conn.assigns.prefix)
      end

      def delete(conn, changeset) do
        Repo.delete(changeset, prefix: conn.assigns.prefix)
      end
    end

Then, when defining a resource (in this example, for the schema Student)

defmodule AppWeb.Admin.StudentAdmin do
  use AppWeb.Admin.Tenanted
end

Hope it might help someone out there.

tfwright commented 2 years ago

Thanks for sharing @zenbaku

How are you handling the tenant switching? @zamith mentioned "navigation extras" but I don't see that mentioned in the documentation?