├── test ├── test_helper.exs └── ash_kaffy_test.exs ├── lib ├── ash_kaffy.ex └── ash_kaffy │ ├── context.ex │ ├── resource │ ├── column.ex │ ├── transformers │ │ └── create_admin_module.ex │ └── resource.ex │ ├── resource_reference.ex │ └── api.ex ├── .formatter.exs ├── README.md ├── .gitignore ├── mix.exs └── mix.lock /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /lib/ash_kaffy.ex: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy do 2 | @moduledoc """ 3 | Documentation for `AshKaffy`. 4 | """ 5 | end 6 | -------------------------------------------------------------------------------- /lib/ash_kaffy/context.ex: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy.Context do 2 | defstruct [:resources, :name, :pretty_name] 3 | end 4 | -------------------------------------------------------------------------------- /lib/ash_kaffy/resource/column.ex: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy.Resource.Column do 2 | defstruct [:name, :pretty_name] 3 | end 4 | -------------------------------------------------------------------------------- /lib/ash_kaffy/resource_reference.ex: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy.ResourceReference do 2 | defstruct [:resource, :name] 3 | end 4 | -------------------------------------------------------------------------------- /test/ash_kaffy_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AshKaffyTest do 2 | use ExUnit.Case 3 | doctest AshKaffy 4 | 5 | test "greets the world" do 6 | assert AshKaffy.hello() == :world 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOGENERATED USING `mix ash.formatter` 2 | # DONT MODIFY IT BY HAND 3 | locals_without_parens = [ 4 | column: 2, 5 | column: 3, 6 | context: 1, 7 | context: 2, 8 | pretty_name: 1, 9 | resource: 2, 10 | resource: 3 11 | ] 12 | 13 | [ 14 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], 15 | locals_without_parens: locals_without_parens, 16 | export: [ 17 | locals_without_parens: locals_without_parens 18 | ] 19 | ] 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AshKaffy 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `ash_kaffy` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:ash_kaffy, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at [https://hexdocs.pm/ash_kaffy](https://hexdocs.pm/ash_kaffy). 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | ash_kaffy-*.tar 24 | 25 | -------------------------------------------------------------------------------- /lib/ash_kaffy/resource/transformers/create_admin_module.ex: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy.Resource.Transformers.CreateAdminModule do 2 | use Ash.Dsl.Transformer 3 | 4 | @impl true 5 | def compile_time_only?, do: true 6 | 7 | @impl true 8 | def transform(resource, dsl) do 9 | resource 10 | |> Module.concat(KaffyAdmin) 11 | |> Module.create( 12 | quote do 13 | def index(_) do 14 | unquote(resource) 15 | |> AshKaffy.Resource.columns() 16 | |> Enum.map(fn column -> 17 | {column.name, %{name: column.pretty_name}} 18 | end) 19 | end 20 | end, 21 | Macro.Env.location(__ENV__) 22 | ) 23 | 24 | {:ok, dsl} 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ash_kaffy, 7 | version: "0.1.0", 8 | elixir: "~> 1.10", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps(), 11 | aliases: aliases() 12 | ] 13 | end 14 | 15 | # Run "mix help compile.app" to learn about applications. 16 | def application do 17 | [ 18 | extra_applications: [:logger] 19 | ] 20 | end 21 | 22 | defp aliases do 23 | [ 24 | "ash.formatter": "ash.formatter --extensions AshKaffy.Api,AshKaffy.Resource" 25 | ] 26 | end 27 | 28 | # Run "mix help deps" to learn about dependencies. 29 | defp deps do 30 | [ 31 | {:ash, "~> 0.10.0"}, 32 | {:kaffy, "~> 0.9.0"}, 33 | {:ex_doc, "~> 0.22", only: :dev, runtime: false} 34 | # {:dep_from_hexpm, "~> 0.3.0"}, 35 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 36 | ] 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/ash_kaffy/resource/resource.ex: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy.Resource do 2 | @transformers [ 3 | AshKaffy.Resource.Transformers.CreateAdminModule 4 | ] 5 | 6 | @column %Ash.Dsl.Entity{ 7 | name: :column, 8 | target: AshKaffy.Resource.Column, 9 | describe: "Adds a column in the kaffy index view", 10 | examples: [ 11 | "column :title, \"Title\"" 12 | ], 13 | schema: [ 14 | name: [ 15 | type: :atom, 16 | doc: "The attribute name to add", 17 | required: true 18 | ], 19 | pretty_name: [ 20 | type: :string, 21 | doc: "The actual text to use in the column header", 22 | required: true 23 | ] 24 | ], 25 | args: [:name, :pretty_name] 26 | } 27 | 28 | @columns %Ash.Dsl.Section{ 29 | name: :columns, 30 | describe: "Configure the columns for the index page in kaffy", 31 | entities: [ 32 | @column 33 | ] 34 | } 35 | 36 | @kaffy %Ash.Dsl.Section{ 37 | name: :kaffy, 38 | describe: "A section for declaring the configuration of your kaffy admin interface", 39 | sections: [ 40 | @columns 41 | ] 42 | } 43 | 44 | use Ash.Dsl.Extension, sections: [@kaffy], transformers: @transformers 45 | 46 | def columns(resource) do 47 | Ash.Dsl.Extension.get_entities(resource, [:kaffy, :columns]) 48 | end 49 | 50 | def admin_module(resource) do 51 | Module.concat(resource, KaffyAdmin) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/ash_kaffy/api.ex: -------------------------------------------------------------------------------- 1 | defmodule AshKaffy.Api do 2 | @moduledoc """ 3 | The Kaffy DSL extension 4 | 5 | * kaffy - `kaffy/1` 6 | """ 7 | @resource %Ash.Dsl.Entity{ 8 | name: :resource, 9 | target: AshKaffy.ResourceReference, 10 | describe: "A resource", 11 | examples: [ 12 | "resource MyApp.Organization" 13 | ], 14 | schema: [ 15 | name: [ 16 | type: :atom, 17 | doc: "The name of the resource", 18 | required: true 19 | ], 20 | resource: [ 21 | type: :atom, 22 | doc: "The resource to include in the context", 23 | required: true 24 | ] 25 | ], 26 | args: [:name, :resource] 27 | } 28 | 29 | @context %Ash.Dsl.Entity{ 30 | name: :context, 31 | target: AshKaffy.Context, 32 | describe: "A context, and its resources", 33 | examples: [ 34 | """ 35 | context :organizations do 36 | resource MyApp.Organization 37 | resource MyApp.Account 38 | end 39 | """ 40 | ], 41 | entities: [ 42 | resources: [@resource] 43 | ], 44 | schema: [ 45 | name: [ 46 | type: :atom, 47 | doc: "The name of the context", 48 | required: true 49 | ], 50 | pretty_name: [ 51 | type: :string, 52 | doc: "The name to be used in rendering", 53 | required: false 54 | ] 55 | ], 56 | args: [:name] 57 | } 58 | 59 | @contexts %Ash.Dsl.Section{ 60 | name: :contexts, 61 | describe: "A list of your contexts and the resources inside of them", 62 | entities: [ 63 | @context 64 | ] 65 | } 66 | 67 | @kaffy %Ash.Dsl.Section{ 68 | name: :kaffy, 69 | describe: 70 | "A section for declaring the contexts and resources to be shown in your kaffy admin interface", 71 | sections: [ 72 | @contexts 73 | ] 74 | } 75 | 76 | use Ash.Dsl.Extension, sections: [@kaffy] 77 | 78 | def contexts(api) do 79 | Ash.Dsl.Extension.get_entities(api, [:kaffy, :contexts]) 80 | end 81 | 82 | def resources(_conn, api) do 83 | api 84 | |> contexts() 85 | |> Enum.map(fn %{resources: resources, name: name, pretty_name: pretty_name} -> 86 | {name, 87 | [ 88 | name: pretty_name, 89 | resources: 90 | Enum.map(resources, fn resource_reference -> 91 | {resource_reference.name, 92 | [ 93 | schema: resource_reference.resource, 94 | admin: AshKaffy.Resource.admin_module(resource_reference.resource) 95 | ]} 96 | end) 97 | ]} 98 | end) 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "ash": {:hex, :ash, "0.10.0", "5394032040c7e7a0eefdc5a06c8a7be64169298e700a280fdde4ea16856a05b3", [:mix], [{:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.4", [hex: :picosat_elixir, repo: "hexpm", optional: false]}], "hexpm", "86e53fdadf10ca6a72fa1c6ceff143515b55aa003f73ad3d9424cba194ee8cb4"}, 3 | "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, 4 | "earmark": {:hex, :earmark, "1.4.9", "837e4c1c5302b3135e9955f2bbf52c6c52e950c383983942b68b03909356c0d9", [:mix], [{:earmark_parser, ">= 1.4.9", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "0d72df7d13a3dc8422882bed5263fdec5a773f56f7baeb02379361cb9e5b0d8e"}, 5 | "earmark_parser": {:hex, :earmark_parser, "1.4.9", "819bda2049e6ee1365424e4ced1ba65806eacf0d2867415f19f3f80047f8037b", [:mix], [], "hexpm", "8bf54fddabf2d7e137a0c22660e71b49d5a0a82d1fb05b5af62f2761cd6485c4"}, 6 | "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, 7 | "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, 8 | "ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"}, 9 | "ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"}, 10 | "kaffy": {:hex, :kaffy, "0.9.0", "bef34c9729f6a3af4d0dea8eede8bcb9e11371a83ac9a8b393991bce81839517", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.11", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "d18ff57b8e68feb433aed11e71510cd357abc7034e75358af5deff7d0d4c6ed3"}, 11 | "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, 12 | "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, 13 | "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, 14 | "nimble_options": {:hex, :nimble_options, "0.3.0", "1872911bf50a048f04da26e02704e6aeafc362c2daa7636b6dbfda9492ccfcfa", [:mix], [], "hexpm", "180790a8644fea402452bc15bb54b9bf2c8e5c1fdeb6b39d8072e59c324edf7f"}, 15 | "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, 16 | "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"}, 17 | "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, 18 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, 19 | "picosat_elixir": {:hex, :picosat_elixir, "0.1.4", "d259219ae27148c07c4aa3fdee61b1a14f4bc7f83b0ebdf2752558d06b302c62", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "eb41cb16053a45c8556de32f065084af98ea0b13a523fb46dfb4f9cff4152474"}, 20 | "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"}, 21 | "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, 22 | "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, 23 | } 24 | --------------------------------------------------------------------------------