├── web
├── views
│ ├── admin_view.ex
│ ├── page_view.ex
│ ├── user_view.ex
│ ├── layout_view.ex
│ ├── product_view.ex
│ ├── line_item_view.ex
│ ├── order_view.ex
│ ├── error_view.ex
│ └── error_helpers.ex
├── static
│ ├── assets
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ └── phoenix.png
│ │ └── robots.txt
│ └── js
│ │ ├── app.js
│ │ └── socket.js
├── templates
│ ├── user
│ │ ├── new.html.haml
│ │ ├── edit.html.haml
│ │ ├── show.html.haml
│ │ ├── form.html.haml
│ │ └── index.html.haml
│ ├── order
│ │ ├── new.html.haml
│ │ ├── edit.html.haml
│ │ ├── show.html.haml
│ │ ├── form.html.haml
│ │ └── index.html.haml
│ ├── product
│ │ ├── new.html.haml
│ │ ├── edit.html.haml
│ │ ├── show.html.haml
│ │ ├── index.html.haml
│ │ └── form.html.haml
│ ├── line_item
│ │ ├── new.html.haml
│ │ ├── edit.html.haml
│ │ ├── show.html.haml
│ │ ├── form.html.haml
│ │ └── index.html.haml
│ ├── admin
│ │ └── sidebar_links.html.haml
│ ├── page
│ │ └── index.html.eex
│ └── layout
│ │ └── app.html.eex
├── controllers
│ ├── page_controller.ex
│ ├── user_controller.ex
│ ├── order_controller.ex
│ ├── product_controller.ex
│ └── line_item_controller.ex
├── models
│ ├── line_item.ex
│ ├── product.ex
│ ├── user.ex
│ └── order.ex
├── gettext.ex
├── router.ex
├── channels
│ └── user_socket.ex
├── uploaders
│ └── image.ex
├── admin
│ ├── user.ex
│ ├── order.ex
│ ├── product.ex
│ └── dashboard.ex
└── web.ex
├── test
├── views
│ ├── layout_view_test.exs
│ ├── page_view_test.exs
│ └── error_view_test.exs
├── test_helper.exs
├── controllers
│ ├── page_controller_test.exs
│ ├── user_controller_test.exs
│ ├── line_item_controller_test.exs
│ ├── product_controller_test.exs
│ └── order_controller_test.exs
├── models
│ ├── line_item_test.exs
│ ├── order_test.exs
│ ├── user_test.exs
│ └── product_test.exs
└── support
│ ├── channel_case.ex
│ ├── conn_case.ex
│ └── model_case.ex
├── lib
├── ex_admin_demo
│ ├── repo.ex
│ └── endpoint.ex
└── ex_admin_demo.ex
├── priv
├── repo
│ ├── migrations
│ │ ├── 20160508212054_add_username_constraint_to_users.exs
│ │ ├── 20160316063422_create_user.exs
│ │ ├── 20160316063531_create_order.exs
│ │ ├── 20160316063249_create_product.exs
│ │ └── 20160316063641_create_line_item.exs
│ └── seeds.exs
└── gettext
│ ├── errors.pot
│ └── en
│ └── LC_MESSAGES
│ └── errors.po
├── .iex.exs
├── package.json
├── config
├── test.exs
├── dev.exs
├── config.exs
└── prod.exs
├── .gitignore
├── README.md
├── LICENSE
├── brunch-config.js
├── mix.exs
└── mix.lock
/web/views/admin_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.AdminView do
2 | use ExAdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.PageView do
2 | use ExAdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/user_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.UserView do
2 | use ExAdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.LayoutView do
2 | use ExAdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/product_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ProductView do
2 | use ExAdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/line_item_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.LineItemView do
2 | use ExAdminDemo.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/test/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.LayoutViewTest do
2 | use ExAdminDemo.ConnCase, async: true
3 | end
--------------------------------------------------------------------------------
/test/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.PageViewTest do
2 | use ExAdminDemo.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/web/static/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/ex_admin_demo/master/web/static/assets/favicon.ico
--------------------------------------------------------------------------------
/web/views/order_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.OrderView do
2 | use ExAdminDemo.Web, :view
3 | require Xain
4 |
5 | end
6 |
--------------------------------------------------------------------------------
/web/static/assets/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smpallen99/ex_admin_demo/master/web/static/assets/images/phoenix.png
--------------------------------------------------------------------------------
/lib/ex_admin_demo/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Repo do
2 | use Ecto.Repo, otp_app: :ex_admin_demo
3 | use Scrivener, page_size: 12
4 | end
5 |
--------------------------------------------------------------------------------
/web/templates/user/new.html.haml:
--------------------------------------------------------------------------------
1 | %h2 New user
2 |
3 | = render "form.html", changeset: @changeset, action: user_path(@conn, :create)
4 |
5 | = link "Back", to: user_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/web/templates/order/new.html.haml:
--------------------------------------------------------------------------------
1 | %h2 New order
2 |
3 | = render "form.html", changeset: @changeset, action: order_path(@conn, :create)
4 |
5 | = link "Back", to: order_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/web/templates/product/new.html.haml:
--------------------------------------------------------------------------------
1 | %h2 New product
2 |
3 | = render "form.html", changeset: @changeset, action: product_path(@conn, :create)
4 |
5 | = link "Back", to: product_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/web/templates/user/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Edit user
2 |
3 | = render "form.html", changeset: @changeset, action: user_path(@conn, :update, @user)
4 |
5 | = link "Back", to: user_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.PageController do
2 | use ExAdminDemo.Web, :controller
3 |
4 | def index(conn, _params) do
5 | render conn, "index.html"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/web/templates/order/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Edit order
2 |
3 | = render "form.html", changeset: @changeset, action: order_path(@conn, :update, @order)
4 |
5 | = link "Back", to: order_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/web/templates/line_item/new.html.haml:
--------------------------------------------------------------------------------
1 | %h2 New line item
2 |
3 | = render "form.html", changeset: @changeset, action: line_item_path(@conn, :create)
4 |
5 | = link "Back", to: line_item_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/web/templates/product/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Edit product
2 |
3 | = render "form.html", changeset: @changeset, action: product_path(@conn, :update, @product)
4 |
5 | = link "Back", to: product_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/web/templates/line_item/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Edit line item
2 |
3 | = render "form.html", changeset: @changeset, action: line_item_path(@conn, :update, @line_item)
4 |
5 | = link "Back", to: line_item_path(@conn, :index)
6 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start
2 |
3 | Mix.Task.run "ecto.create", ~w(-r ExAdminDemo.Repo --quiet)
4 | Mix.Task.run "ecto.migrate", ~w(-r ExAdminDemo.Repo --quiet)
5 | Ecto.Adapters.SQL.Sandbox.mode(ExAdminDemo.Repo, :manual)
6 |
7 |
--------------------------------------------------------------------------------
/web/static/assets/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160508212054_add_username_constraint_to_users.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Repo.Migrations.AddUsernameConstraintToUsers do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create unique_index(:users, [:username])
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.PageControllerTest do
2 | use ExAdminDemo.ConnCase
3 |
4 | test "GET /", %{conn: conn} do
5 | conn = get conn, "/"
6 | assert html_response(conn, 200) =~ "Welcome to Phoenix!"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/.iex.exs:
--------------------------------------------------------------------------------
1 | alias ExAdminDemo.Repo
2 | alias ExAdminDemo.User
3 | alias ExAdminDemo.Product
4 | alias ExAdminDemo.LineItem
5 | alias ExAdminDemo.Order
6 | alias ExAdminDemo.ExAdmin.Product, as: AdminProduct
7 | alias ExAdminDemo.ExAdmin.User, as: AdminUser
8 | alias ExAdminDemo.ExAdmin.Order, as: AdminOrder
9 | import Ecto.Query
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160316063422_create_user.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Repo.Migrations.CreateUser do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:users) do
6 | add :username, :string
7 | add :email, :string
8 | add :password_hash, :string
9 |
10 | timestamps
11 | end
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/web/templates/user/show.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Show user
2 |
3 | %ul
4 |
5 | %li
6 | %strong Username:
7 | = @user.username
8 |
9 | %li
10 | %strong Email:
11 | = @user.email
12 |
13 | %li
14 | %strong Password hash:
15 | = @user.password_hash
16 |
17 |
18 | = link "Edit", to: user_path(@conn, :edit, @user)
19 | = link "Back", to: user_path(@conn, :index)
20 |
--------------------------------------------------------------------------------
/web/templates/order/show.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Show order
2 |
3 | %ul
4 |
5 | %li
6 | %strong User:
7 | = @order.user_id
8 |
9 | %li
10 | %strong Checked out at:
11 | = @order.checked_out_at
12 |
13 | %li
14 | %strong Total price:
15 | = @order.total_price
16 |
17 |
18 | = link "Edit", to: order_path(@conn, :edit, @order)
19 | = link "Back", to: order_path(@conn, :index)
20 |
--------------------------------------------------------------------------------
/web/templates/line_item/show.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Show line item
2 |
3 | %ul
4 |
5 | %li
6 | %strong Order:
7 | = @line_item.order_id
8 |
9 | %li
10 | %strong Product:
11 | = @line_item.product_id
12 |
13 | %li
14 | %strong Price:
15 | = @line_item.price
16 |
17 |
18 | = link "Edit", to: line_item_path(@conn, :edit, @line_item)
19 | = link "Back", to: line_item_path(@conn, :index)
20 |
--------------------------------------------------------------------------------
/web/templates/admin/sidebar_links.html.haml:
--------------------------------------------------------------------------------
1 | %p
2 | This is the demo app for ExAdmin. Feel free to checkout the
3 | %a(href="https://github.com/smpallen99/ex_admin_demo/blob/master/web/admin/#{@model}.ex" target="_blank") source code for this page
4 | , the
5 | %a(href="http://exadmin.info/doc" target="_blank") Documentation
6 | and the
7 | %a(href="https://github.com/smpallen99/ex_admin" target="_blank") Github Repo
8 | .
9 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160316063531_create_order.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Repo.Migrations.CreateOrder do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:orders) do
6 | add :checked_out_at, :datetime
7 | add :total_price, :decimal
8 | add :user_id, references(:users, on_delete: :nothing)
9 |
10 | timestamps
11 | end
12 | create index(:orders, [:user_id])
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {
3 | },
4 | "dependencies": {
5 | "babel-brunch": "^6.0.0",
6 | "babel-preset-es2015": "^6.6.0",
7 | "brunch": "^2.1.1",
8 | "clean-css-brunch": ">= 1.0 < 1.8",
9 | "css-brunch": ">= 1.0 < 1.8",
10 | "javascript-brunch": ">= 1.0 < 1.8",
11 | "uglify-js-brunch": ">= 1.0 < 1.8",
12 | "phoenix": "file:deps/phoenix",
13 | "phoenix_html": "file:deps/phoenix_html"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/web/templates/line_item/form.html.haml:
--------------------------------------------------------------------------------
1 | - form_for @changeset, @action, fn f ->
2 | - if @changeset.action do
3 | .alert.alert-danger
4 | %p Oops, something went wrong! Please check the errors below.
5 |
6 | .form-group
7 | = label f, :price, class: "control-label"
8 | = number_input f, :price, step: "any", class: "form-control"
9 | = error_tag f, :price
10 |
11 | .form-group
12 | = submit "Submit", class: "btn btn-primary"
13 |
--------------------------------------------------------------------------------
/web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ErrorView do
2 | use ExAdminDemo.Web, :view
3 |
4 | def render("404.html", _assigns) do
5 | "Page not found"
6 | end
7 |
8 | def render("500.html", _assigns) do
9 | "Server internal error"
10 | end
11 |
12 | # In case no render clause matches or no
13 | # template is found, let's render it as 500
14 | def template_not_found(_template, assigns) do
15 | render "500.html", assigns
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160316063249_create_product.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Repo.Migrations.CreateProduct do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:products) do
6 | add :title, :string
7 | add :description, :text
8 | add :author, :string
9 | add :price, :decimal
10 | add :featured, :boolean, default: false
11 | add :available_on, :date
12 | add :image_file_name, :string
13 |
14 | timestamps
15 | end
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160316063641_create_line_item.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Repo.Migrations.CreateLineItem do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:line_items) do
6 | add :price, :decimal
7 | add :order_id, references(:orders, on_delete: :nothing)
8 | add :product_id, references(:products, on_delete: :nothing)
9 |
10 | timestamps
11 | end
12 | create index(:line_items, [:order_id])
13 | create index(:line_items, [:product_id])
14 |
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/models/line_item_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.LineItemTest do
2 | use ExAdminDemo.ModelCase
3 |
4 | alias ExAdminDemo.LineItem
5 |
6 | @valid_attrs %{price: "120.5"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = LineItem.changeset(%LineItem{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = LineItem.changeset(%LineItem{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/order_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.OrderTest do
2 | use ExAdminDemo.ModelCase
3 |
4 | alias ExAdminDemo.Order
5 |
6 | @valid_attrs %{user_id: 1, checked_out_at: "2010-04-17 14:00:00", total_price: "120.5"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Order.changeset(%Order{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Order.changeset(%Order{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/user_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.UserTest do
2 | use ExAdminDemo.ModelCase
3 |
4 | alias ExAdminDemo.User
5 |
6 | @valid_attrs %{email: "some content", password_hash: "some content", username: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = User.changeset(%User{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = User.changeset(%User{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :ex_admin_demo, ExAdminDemo.Endpoint,
6 | http: [port: 4001],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
12 | # Configure your database
13 | config :ex_admin_demo, ExAdminDemo.Repo,
14 | adapter: Ecto.Adapters.Postgres,
15 | username: "postgres",
16 | password: "postgres",
17 | database: "ex_admin_demo_test",
18 | hostname: "localhost",
19 | pool: Ecto.Adapters.SQL.Sandbox
20 |
--------------------------------------------------------------------------------
/web/models/line_item.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.LineItem do
2 | use ExAdminDemo.Web, :model
3 |
4 | schema "line_items" do
5 | field :price, :decimal
6 | belongs_to :order, ExAdminDemo.Order
7 | belongs_to :product, ExAdminDemo.Product
8 |
9 | timestamps
10 | end
11 |
12 | @doc """
13 | Creates a changeset based on the `model` and `params`.
14 |
15 | If no params are provided, an invalid changeset is returned
16 | with no validation performed.
17 | """
18 | def changeset(model, params \\ %{}) do
19 | model
20 | |> cast(params, ~w(price))
21 | |> validate_required([:price])
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/test/models/product_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ProductTest do
2 | use ExAdminDemo.ModelCase
3 |
4 | alias ExAdminDemo.Product
5 |
6 | @valid_attrs %{author: "some content", available_on: "2010-04-17", description: "some content", featured: true, price: "120.5", title: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Product.changeset(%Product{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Product.changeset(%Product{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/web/templates/order/form.html.haml:
--------------------------------------------------------------------------------
1 | - form_for @changeset, @action, fn f ->
2 | - if @changeset.action do
3 | .alert.alert-danger
4 | %p Oops, something went wrong! Please check the errors below.
5 |
6 | .form-group
7 | = label f, :checked_out_at, class: "control-label"
8 | = datetime_select f, :checked_out_at, class: "form-control"
9 | = error_tag f, :checked_out_at
10 |
11 | .form-group
12 | = label f, :total_price, class: "control-label"
13 | = number_input f, :total_price, step: "any", class: "form-control"
14 | = error_tag f, :total_price
15 |
16 | .form-group
17 | = submit "Submit", class: "btn btn-primary"
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # App artifacts
2 | /_build
3 | /db
4 | /deps
5 | /*.ez
6 | start
7 |
8 | # Generate on crash by the VM
9 | erl_crash.dump
10 |
11 | # Static artifacts
12 | /node_modules
13 |
14 | # Since we are building assets from web/static,
15 | # we ignore priv/static. You may want to comment
16 | # this depending on your deployment strategy.
17 | /priv/static/
18 |
19 | # The config/prod.secret.exs file by default contains sensitive
20 | # data and you should not commit it into version control.
21 | #
22 | # Alternatively, you may comment the line below and commit the
23 | # secrets file as long as you replace its contents by environment
24 | # variables.
25 | /config/prod.secret.exs
26 |
--------------------------------------------------------------------------------
/test/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ErrorViewTest do
2 | use ExAdminDemo.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(ExAdminDemo.ErrorView, "404.html", []) ==
9 | "Page not found"
10 | end
11 |
12 | test "render 500.html" do
13 | assert render_to_string(ExAdminDemo.ErrorView, "500.html", []) ==
14 | "Server internal error"
15 | end
16 |
17 | test "render any other" do
18 | assert render_to_string(ExAdminDemo.ErrorView, "505.html", []) ==
19 | "Server internal error"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/web/static/js/app.js:
--------------------------------------------------------------------------------
1 | // Brunch automatically concatenates all files in your
2 | // watched paths. Those paths can be configured at
3 | // config.paths.watched in "brunch-config.js".
4 | //
5 | // However, those files will only be executed if
6 | // explicitly imported. The only exception are files
7 | // in vendor, which are never wrapped in imports and
8 | // therefore are always executed.
9 |
10 | // Import dependencies
11 | //
12 | // If you no longer want to use a dependency, remember
13 | // to also remove its path from "config.paths.watched".
14 | import "phoenix_html"
15 |
16 | // Import local files
17 | //
18 | // Local files can be imported directly using relative
19 | // paths "./socket" or full ones "web/static/js/socket".
20 |
21 | // import socket from "./socket"
22 |
--------------------------------------------------------------------------------
/web/templates/product/show.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Show product
2 |
3 | %ul
4 |
5 | %li
6 | %strong Title:
7 | = @product.title
8 |
9 | %li
10 | %strong Description:
11 | = @product.description
12 |
13 | %li
14 | %strong Author:
15 | = @product.author
16 |
17 | %li
18 | %strong Price:
19 | = @product.price
20 |
21 | %li
22 | %strong Featured:
23 | = @product.featured
24 |
25 | %li
26 | %strong Available on:
27 | = @product.available_on
28 |
29 | %li
30 | %strong Image file name:
31 | - image = ExAdminDemo.Image.url({@product.image_file_name, @product}, :thumb)
32 | %img(src="#{image}")
33 |
34 |
35 | = link "Edit", to: product_path(@conn, :edit, @product)
36 | = link "Back", to: product_path(@conn, :index)
37 |
--------------------------------------------------------------------------------
/web/templates/user/form.html.haml:
--------------------------------------------------------------------------------
1 | - form_for @changeset, @action, fn f ->
2 | - if @changeset.action do
3 | .alert.alert-danger
4 | %p Oops, something went wrong! Please check the errors below.
5 |
6 | .form-group
7 | = label f, :username, class: "control-label"
8 | = text_input f, :username, class: "form-control"
9 | = error_tag f, :username
10 |
11 | .form-group
12 | = label f, :email, class: "control-label"
13 | = text_input f, :email, class: "form-control"
14 | = error_tag f, :email
15 |
16 | .form-group
17 | = label f, :password_hash, class: "control-label"
18 | = text_input f, :password_hash, class: "form-control"
19 | = error_tag f, :password_hash
20 |
21 | .form-group
22 | = submit "Submit", class: "btn btn-primary"
23 |
--------------------------------------------------------------------------------
/web/templates/user/index.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Listing users
2 |
3 | %table.table
4 | %thead
5 | %tr
6 | %th Username
7 | %th Email
8 | %th Password hash
9 |
10 | %th
11 | %tbody
12 | - for user <- @users do
13 | %tr
14 | %td= user.username
15 | %td= user.email
16 | %td= user.password_hash
17 |
18 | %td.text-right
19 | = link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs"
20 | = link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs"
21 | = link "Delete", to: user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs"
22 |
23 | = link "New user", to: user_path(@conn, :new)
24 |
--------------------------------------------------------------------------------
/web/templates/order/index.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Listing orders
2 |
3 | %table.table
4 | %thead
5 | %tr
6 | %th User
7 | %th Checked out at
8 | %th Total price
9 |
10 | %th
11 | %tbody
12 | - for order <- @orders do
13 | %tr
14 | %td= order.user_id
15 | %td= order.checked_out_at
16 | %td= order.total_price
17 |
18 | %td.text-right
19 | = link "Show", to: order_path(@conn, :show, order), class: "btn btn-default btn-xs"
20 | = link "Edit", to: order_path(@conn, :edit, order), class: "btn btn-default btn-xs"
21 | = link "Delete", to: order_path(@conn, :delete, order), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs"
22 |
23 | = link "New order", to: order_path(@conn, :new)
24 |
--------------------------------------------------------------------------------
/web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](http://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import ExAdminDemo.Gettext
9 |
10 | # Simple translation
11 | gettext "Here is the string to translate"
12 |
13 | # Plural translation
14 | ngettext "Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3
17 |
18 | # Domain-based translation
19 | dgettext "errors", "Here is the error message to translate"
20 |
21 | See the [Gettext Docs](http://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :ex_admin_demo
24 | end
25 |
--------------------------------------------------------------------------------
/web/templates/line_item/index.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Listing line items
2 |
3 | %table.table
4 | %thead
5 | %tr
6 | %th Order
7 | %th Product
8 | %th Price
9 |
10 | %th
11 | %tbody
12 | - for line_item <- @line_items do
13 | %tr
14 | %td= line_item.order_id
15 | %td= line_item.product_id
16 | %td= line_item.price
17 |
18 | %td.text-right
19 | = link "Show", to: line_item_path(@conn, :show, line_item), class: "btn btn-default btn-xs"
20 | = link "Edit", to: line_item_path(@conn, :edit, line_item), class: "btn btn-default btn-xs"
21 | = link "Delete", to: line_item_path(@conn, :delete, line_item), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs"
22 |
23 | = link "New line item", to: line_item_path(@conn, :new)
24 |
--------------------------------------------------------------------------------
/web/models/product.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Product do
2 | use ExAdminDemo.Web, :model
3 |
4 | schema "products" do
5 | field :title, :string
6 | field :description, :string
7 | field :author, :string
8 | field :price, :decimal
9 | field :featured, :boolean, default: false
10 | field :available_on, Ecto.Date
11 | field :image_file_name, ExAdminDemo.Image.Type
12 |
13 | timestamps
14 | end
15 |
16 | @doc """
17 | Creates a changeset based on the `model` and `params`.
18 |
19 | If no params are provided, an invalid changeset is returned
20 | with no validation performed.
21 | """
22 | def changeset(model, params \\ %{}) do
23 | model
24 | |> cast(params, ~w(title description author price featured available_on image_file_name))
25 | |> validate_required(~w(title description author price featured)a)
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ExAdminDemo
2 |
3 | This is a demo application, demonstrating some of the capabilities of `ExAdmin`.
4 |
5 | Visit [Live Demo](http://demo.exadmin.info/admin) to try it out!
6 |
7 | Refer to [ExAdmin](https://github.com/smpallen99/ex_admin) for more details, or
8 | view the [Documentation](http://exadmin.info/doc).
9 |
10 | ## Install
11 |
12 | 1. Install dependencies with `mix deps.get && npm install`
13 | 2. Configure ExAdmin with `mix admin.install`
14 | 3. Create and migrate your database with `mix ecto.create && mix ecto.migrate`
15 | 4. Start Phoenix endpoint with `mix phoenix.server`
16 |
17 | Now you can visit [`localhost:4000/admin`](http://localhost:4000/admin) from your browser.
18 |
19 | ## License
20 |
21 | `ex_admin_demo` is Copyright (c) 2016 E-MetroTel
22 |
23 | The source code is released under the MIT License.
24 |
25 | Check [LICENSE](LICENSE) for more information.
26 |
--------------------------------------------------------------------------------
/web/models/user.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.User do
2 | use ExAdminDemo.Web, :model
3 | import Ecto.Query
4 | alias ExAdminDemo.{User}
5 |
6 | schema "users" do
7 | field :username, :string
8 | field :email, :string
9 | field :password_hash, :string
10 | has_many :orders, ExAdminDemo.Order
11 |
12 | timestamps
13 | end
14 |
15 | @required_fields ~w(username email)
16 | @optional_fields ~w(password_hash)
17 | @doc """
18 | Creates a changeset based on the `model` and `params`.
19 |
20 | If no params are provided, an invalid changeset is returned
21 | with no validation performed.
22 | """
23 | def changeset(model, params \\ %{}) do
24 | model
25 | |> cast(params, ~w(username email password_hash))
26 | |> validate_required(~w(username email)a)
27 | |> unique_constraint(:username)
28 | end
29 |
30 | def ordered(count) do
31 | order_by(User, desc: :id)
32 | |> limit(^count)
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Router do
2 | use ExAdminDemo.Web, :router
3 | use ExAdmin.Router
4 |
5 | pipeline :browser do
6 | plug :accepts, ["html"]
7 | plug :fetch_session
8 | plug :fetch_flash
9 | plug :protect_from_forgery
10 | plug :put_secure_browser_headers
11 | end
12 |
13 | pipeline :api do
14 | plug :accepts, ["json"]
15 | end
16 |
17 | scope "/admin", ExAdmin do
18 | pipe_through :browser
19 | admin_routes
20 | end
21 |
22 | scope "/", ExAdminDemo do
23 | pipe_through :browser # Use the default browser stack
24 |
25 | resources "/products", ProductController
26 | resources "/users", UserController
27 | resources "/orders", OrderController
28 | resources "/line_items", LineItemController
29 |
30 | get "/", PageController, :index
31 | end
32 |
33 | # Other scopes may use custom stacks.
34 | # scope "/api", ExAdminDemo do
35 | # pipe_through :api
36 | # end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/ex_admin_demo.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo do
2 | use Application
3 |
4 | # See http://elixir-lang.org/docs/stable/elixir/Application.html
5 | # for more information on OTP Applications
6 | def start(_type, _args) do
7 | import Supervisor.Spec, warn: false
8 |
9 | children = [
10 | # Start the endpoint when the application starts
11 | supervisor(ExAdminDemo.Endpoint, []),
12 | # Start the Ecto repository
13 | supervisor(ExAdminDemo.Repo, []),
14 | # Here you could define other workers and supervisors as children
15 | # worker(ExAdminDemo.Worker, [arg1, arg2, arg3]),
16 | ]
17 |
18 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
19 | # for other strategies and supported options
20 | opts = [strategy: :one_for_one, name: ExAdminDemo.Supervisor]
21 | Supervisor.start_link(children, opts)
22 | end
23 |
24 | # Tell Phoenix to update the endpoint configuration
25 | # whenever the application is updated.
26 | def config_change(changed, _new, removed) do
27 | ExAdminDemo.Endpoint.config_change(changed, removed)
28 | :ok
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
<%= gettext "Welcome to %{name}", name: "Phoenix!" %>
3 |
A productive web framework that does not compromise speed and maintainability.
4 |
5 |
6 |
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 E-MetroTel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/web/templates/product/index.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Listing products
2 |
3 | %table.table
4 | %thead
5 | %tr
6 | %th Title
7 | %th Description
8 | %th Author
9 | %th Price
10 | %th Featured
11 | %th Available on
12 | %th Image file name
13 |
14 | %th
15 | %tbody
16 | - for product <- @products do
17 | %tr
18 | %td= product.title
19 | %td= product.description
20 | %td= product.author
21 | %td= product.price
22 | %td= product.featured
23 | %td= product.available_on
24 | %td
25 | - image = ExAdminDemo.Image.url({product.image_file_name, product}, :thumb)
26 | %img(src="#{image}")
27 |
28 | %td.text-right
29 | = link "Show", to: product_path(@conn, :show, product), class: "btn btn-default btn-xs"
30 | = link "Edit", to: product_path(@conn, :edit, product), class: "btn btn-default btn-xs"
31 | = link "Delete", to: product_path(@conn, :delete, product), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs"
32 |
33 | = link "New product", to: product_path(@conn, :new)
34 |
--------------------------------------------------------------------------------
/web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | if error = form.errors[field] do
13 | content_tag :span, translate_error(error), class: "help-block"
14 | end
15 | end
16 |
17 | @doc """
18 | Translates an error message using gettext.
19 | """
20 | def translate_error({msg, opts}) do
21 | # Because error messages were defined within Ecto, we must
22 | # call the Gettext module passing our Gettext backend. We
23 | # also use the "errors" domain as translations are placed
24 | # in the errors.po file. On your own code and templates,
25 | # this could be written simply as:
26 | #
27 | # dngettext "errors", "1 file", "%{count} files", count
28 | #
29 | Gettext.dngettext(ExAdminDemo.Gettext, "errors", msg, msg, opts[:count] || 1, opts)
30 | end
31 |
32 | def translate_error(msg) do
33 | Gettext.dgettext(ExAdminDemo.Gettext, "errors", msg)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/ex_admin_demo/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :ex_admin_demo
3 |
4 | socket "/socket", ExAdminDemo.UserSocket
5 |
6 | # Serve at "/" the static files from "priv/static" directory.
7 | #
8 | # You should set gzip to true if you are running phoenix.digest
9 | # when deploying your static files in production.
10 | plug Plug.Static,
11 | at: "/", from: :ex_admin_demo, gzip: false,
12 | only: ~w(css fonts images js themes favicon.ico robots.txt)
13 |
14 | # Code reloading can be explicitly enabled under the
15 | # :code_reloader configuration of your endpoint.
16 | if code_reloading? do
17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
18 | plug Phoenix.LiveReloader
19 | plug Phoenix.CodeReloader
20 | end
21 |
22 | plug Plug.RequestId
23 | plug Plug.Logger
24 |
25 | plug Plug.Parsers,
26 | parsers: [:urlencoded, :multipart, :json],
27 | pass: ["*/*"],
28 | json_decoder: Poison
29 |
30 | plug Plug.MethodOverride
31 | plug Plug.Head
32 |
33 | plug Plug.Session,
34 | store: :cookie,
35 | key: "_ex_admin_demo_key",
36 | signing_salt: "VibiKh2n"
37 |
38 | plug ExAdminDemo.Router
39 | end
40 |
--------------------------------------------------------------------------------
/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | imports other functionality to make it easier
8 | to build and query models.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | alias ExAdminDemo.Repo
24 | import Ecto
25 | import Ecto.Changeset
26 | import Ecto.Query, only: [from: 1, from: 2]
27 |
28 |
29 | # The default endpoint for testing
30 | @endpoint ExAdminDemo.Endpoint
31 | end
32 | end
33 |
34 | setup tags do
35 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ExAdminDemo.Repo)
36 | unless tags[:async] do
37 | Ecto.Adapters.SQL.Sandbox.mode(ExAdminDemo.Repo, {:shared, self()})
38 | end
39 |
40 | :ok
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Hello ExAdminDemo!
11 | ">
12 |
13 |
14 |
15 |
16 |
24 |
25 |
<%= get_flash(@conn, :info) %>
26 |
<%= get_flash(@conn, :error) %>
27 |
28 |
29 | <%= render @view_module, @view_template, assigns %>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | imports other functionality to make it easier
8 | to build and query models.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with connections
21 | use Phoenix.ConnTest
22 |
23 | alias ExAdminDemo.Repo
24 | import Ecto
25 | import Ecto.Changeset
26 | import Ecto.Query, only: [from: 1, from: 2]
27 |
28 | import ExAdminDemo.Router.Helpers
29 |
30 | # The default endpoint for testing
31 | @endpoint ExAdminDemo.Endpoint
32 | end
33 | end
34 |
35 | setup tags do
36 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ExAdminDemo.Repo)
37 | unless tags[:async] do
38 | Ecto.Adapters.SQL.Sandbox.mode(ExAdminDemo.Repo, {:shared, self()})
39 | end
40 |
41 | {:ok, conn: Phoenix.ConnTest.build_conn()}
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "rooms:*", ExAdminDemo.RoomChannel
6 |
7 | ## Transports
8 | transport :websocket, Phoenix.Transports.WebSocket
9 | # transport :longpoll, Phoenix.Transports.LongPoll
10 |
11 | # Socket params are passed from the client and can
12 | # be used to verify and authenticate a user. After
13 | # verification, you can put default assigns into
14 | # the socket that will be set for all channels, ie
15 | #
16 | # {:ok, assign(socket, :user_id, verified_user_id)}
17 | #
18 | # To deny connection, return `:error`.
19 | #
20 | # See `Phoenix.Token` documentation for examples in
21 | # performing token verification on connect.
22 | def connect(_params, socket) do
23 | {:ok, socket}
24 | end
25 |
26 | # Socket id's are topics that allow you to identify all sockets for a given user:
27 | #
28 | # def id(socket), do: "users_socket:#{socket.assigns.user_id}"
29 | #
30 | # Would allow you to broadcast a "disconnect" event and terminate
31 | # all active sockets and channels for a given user:
32 | #
33 | # ExAdminDemo.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
34 | #
35 | # Returning `nil` makes this socket anonymous.
36 | def id(_socket), do: nil
37 | end
38 |
--------------------------------------------------------------------------------
/web/templates/product/form.html.haml:
--------------------------------------------------------------------------------
1 | - form_for @changeset, @action, [multipart: true], fn f ->
2 | - if @changeset.action do
3 | .alert.alert-danger
4 | %p Oops, something went wrong! Please check the errors below.
5 |
6 | .form-group
7 | = label f, :title, class: "control-label"
8 | = text_input f, :title, class: "form-control"
9 | = error_tag f, :title
10 |
11 | .form-group
12 | = label f, :description, class: "control-label"
13 | = text_input f, :description, class: "form-control"
14 | = error_tag f, :description
15 |
16 | .form-group
17 | = label f, :author, class: "control-label"
18 | = text_input f, :author, class: "form-control"
19 | = error_tag f, :author
20 |
21 | .form-group
22 | = label f, :price, class: "control-label"
23 | = number_input f, :price, step: "any", class: "form-control"
24 | = error_tag f, :price
25 |
26 | .form-group
27 | = label f, :featured, class: "control-label"
28 | = checkbox f, :featured, class: "form-control"
29 | = error_tag f, :featured
30 |
31 | .form-group
32 | = label f, :available_on, class: "control-label"
33 | = date_select f, :available_on, class: "form-control"
34 | = error_tag f, :available_on
35 |
36 | .form-group
37 | = label f, :image_file_name, class: "control-label"
38 | = file_input f, :image_file_name, class: "form-control"
39 | =# text_input f, :image_file_name, class: "form-control"
40 | = error_tag f, :image_file_name
41 |
42 | .form-group
43 | = submit "Submit", class: "btn btn-primary"
44 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :ex_admin_demo, ExAdminDemo.Endpoint,
10 | http: [port: 4002],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin"]]
15 |
16 | # Watch static and templates for browser reloading.
17 | config :ex_admin_demo, ExAdminDemo.Endpoint,
18 | live_reload: [
19 | patterns: [
20 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
21 | ~r{priv/gettext/.*(po)$},
22 | ~r{web/views/.*(ex)$},
23 | ~r{web/admin/.*(ex)$},
24 | ~r{../ex_admin/web/.*(ex)$},
25 | ~r{../ex_admin/lib/.*(ex)$},
26 | ~r{web/templates/.*(eex)$}
27 | ]
28 | ]
29 |
30 | # Do not include metadata nor timestamps in development logs
31 | config :logger, :console, format: "[$level] $message\n"
32 |
33 | # Set a higher stacktrace during development.
34 | # Do not configure such in production as keeping
35 | # and calculating stacktraces is usually expensive.
36 | config :phoenix, :stacktrace_depth, 20
37 |
38 | # Configure your database
39 | config :ex_admin_demo, ExAdminDemo.Repo,
40 | adapter: Ecto.Adapters.Postgres,
41 | username: "postgres",
42 | password: "postgres",
43 | database: "ex_admin_demo_dev",
44 | hostname: "localhost",
45 | pool_size: 10
46 |
--------------------------------------------------------------------------------
/web/uploaders/image.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Image do
2 | use Arc.Definition
3 | require Logger
4 |
5 | # Include ecto support (requires package arc_ecto installed):
6 | use Arc.Ecto.Definition
7 |
8 | @versions [:original, :thumb]
9 |
10 | # To add a thumbnail version:
11 | # @versions [:original, :thumb]
12 |
13 | # Whitelist file extensions:
14 | def validate({file, _}) do
15 | ~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name))
16 | end
17 |
18 | # Define a thumbnail transformation:
19 | def transform(:thumb, _) do
20 | {:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png}
21 | end
22 |
23 | # Override the persisted filenames:
24 | # def filename(version, _) do
25 | # version
26 | # end
27 |
28 | # Override the storage directory:
29 | def storage_dir(_version, {_file, _scope}) do
30 | # Logger.warn "storage_dir: #{inspect scope}"
31 | # "uploads/images/#{scope.id}"
32 | "uploads/images/steve"
33 | #"uploads/user/avatars/#{scope.id}"
34 | end
35 |
36 | # Provide a default URL if there hasn't been a file uploaded
37 | def default_url(_version, _scope) do
38 | # "priv/static/images/default_#{version}.png"
39 | "../images/blank_book.gif"
40 | end
41 |
42 | # Specify custom headers for s3 objects
43 | # Available options are [:cache_control, :content_disposition,
44 | # :content_encoding, :content_length, :content_type,
45 | # :expect, :expires, :storage_class, :website_redirect_location]
46 | #
47 | # def s3_object_headers(version, {file, scope}) do
48 | # [content_type: Plug.MIME.path(file.file_name)]
49 | # end
50 | end
51 |
--------------------------------------------------------------------------------
/web/admin/user.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ExAdmin.User do
2 | use ExAdmin.Register
3 | import ExAdmin.ViewHelpers
4 | alias ExAdminDemo.Order
5 | alias ExAdminDemo.Repo
6 |
7 | register_resource ExAdminDemo.User do
8 | menu label: "Customers", priority: 4
9 |
10 | filter [:id, :username, :email, :inserted_at]
11 |
12 | show customer do
13 | panel "Order History" do
14 | table_for customer.orders do
15 | column "Order", [sortable: :id], fn(order) -> a " ##{order.id}", href: admin_resource_path(order, :show) end
16 | column "State", fn(order) -> status_tag(ExAdminDemo.Order.state order) end
17 | column :checked_out_at, label: "Date", sortable: true
18 | column "Total", fn(order) -> text decimal_to_currency(order.total_price) end
19 | end
20 | end
21 | end
22 |
23 | query do
24 | %{all: [preload: :orders]}
25 | end
26 |
27 | sidebar "Customer Details", only: :show do
28 | attributes_table_for resource do
29 | row :username
30 | row :email
31 | row :inserted_at
32 | end
33 | end
34 |
35 | sidebar "Order History", only: :show do
36 | id = resource.id
37 | completed = where(Order, [o], o.user_id == ^id)
38 | |> Order.complete
39 | |> Repo.all
40 | attributes_table_for resource do
41 | row "Total Orders", fn(_) -> text Enum.count(completed) end
42 | row "Total Value", fn(_) ->
43 | Enum.reduce(completed, Decimal.new(0.0), &(Decimal.add(&1.total_price, &2)))
44 | |> decimal_to_currency
45 | |> text
46 | end
47 | end
48 | end
49 |
50 | sidebar "ExAdmin Demo", only: [:index, :show] do
51 | Phoenix.View.render ExAdminDemo.AdminView, "sidebar_links.html", model: "user"
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # Configures the endpoint
9 | config :ex_admin_demo, ExAdminDemo.Endpoint,
10 | url: [host: "localhost"],
11 | root: Path.dirname(__DIR__),
12 | secret_key_base: "+EYBCh/dYNlCrLsLUmgbLJF81vJfYX4UQmu6ykPORkiCmmalhDJf1XpBDJ3wpQuR",
13 | render_errors: [accepts: ~w(html json)],
14 | pubsub: [name: ExAdminDemo.PubSub,
15 | adapter: Phoenix.PubSub.PG2]
16 |
17 | config :ex_admin_demo, ecto_repos: [ExAdminDemo.Repo]
18 |
19 | # Configures Elixir's Logger
20 | config :logger, :console,
21 | format: "$time $metadata[$level] $message\n",
22 | metadata: [:request_id]
23 |
24 | config :ex_admin,
25 | repo: ExAdminDemo.Repo,
26 | module: ExAdminDemo,
27 | # theme_selector: [
28 | # {"AdminLte", ExAdmin.Theme.AdminLte2},
29 | # {"ActiveAdmin", ExAdmin.Theme.ActiveAdmin}
30 | # ],
31 | modules: [
32 | ExAdminDemo.ExAdmin.Dashboard,
33 | ExAdminDemo.ExAdmin.Product,
34 | ExAdminDemo.ExAdmin.User,
35 | ExAdminDemo.ExAdmin.Order
36 | ]
37 |
38 | # Configure phoenix generators
39 | config :phoenix, :generators,
40 | migration: true,
41 | binary_id: false
42 |
43 | config :phoenix, :template_engines,
44 | haml: PhoenixHaml.Engine
45 |
46 | config :xain, :quote, "'"
47 | config :xain, :after_callback, {Phoenix.HTML, :raw}
48 |
49 | config :arc, bucket: "exadmindemo"
50 | config :ex_aws,
51 | access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
52 | secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY")
53 |
54 | # Import environment specific config. This must remain at the bottom
55 | # of this file so it overrides the configuration defined above.
56 | import_config "#{Mix.env}.exs"
57 |
--------------------------------------------------------------------------------
/web/models/order.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Order do
2 | use ExAdminDemo.Web, :model
3 | alias ExAdminDemo.{Repo, Order, LineItem}
4 | import Ecto.Query
5 |
6 | @complete "complete"
7 | @in_progress "in_progress"
8 |
9 | schema "orders" do
10 | field :checked_out_at, Ecto.DateTime
11 | field :total_price, :decimal
12 | has_many :line_items, ExAdminDemo.LineItem
13 | belongs_to :user, ExAdminDemo.User
14 |
15 | timestamps
16 | end
17 |
18 | @doc """
19 | Creates a changeset based on the `model` and `params`.
20 |
21 | If no params are provided, an invalid changeset is returned
22 | with no validation performed.
23 | """
24 | def changeset(model, params \\ %{}) do
25 | model
26 | |> cast(params, ~w(user_id checked_out_at total_price))
27 | |> validate_required([:user_id])
28 | end
29 |
30 | def recalculate_price!(order) do
31 | new_price = order.line_items
32 | |> Enum.reduce(Decimal.new(0.0), fn(li, acc) ->
33 | Decimal.add li.price, acc
34 | end)
35 | Repo.update! changeset(order, %{total_price: new_price})
36 | end
37 |
38 | def checkout!(order) do
39 | utc = Ecto.DateTime.utc
40 | Repo.update! changeset(order, %{checked_out_at: utc})
41 | end
42 |
43 | def state(order) do
44 | if order.checked_out_at, do: @complete, else: @in_progress
45 | end
46 |
47 | def find_with_product(product) do
48 | product_id = product.id
49 | Order
50 | |> where([o], not is_nil(o.checked_out_at))
51 | |> join(:inner, [o], l in LineItem, l.order_id == o.id)
52 | |> where([o, l], l.product_id == ^product_id)
53 | |> order_by([o,l], desc: o.checked_out_at)
54 | end
55 |
56 | def complete(query) do
57 | where(query, [p], not is_nil(p.checked_out_at))
58 | end
59 |
60 | def ordered(count) do
61 | where(Order, true)
62 | |> complete
63 | |> limit(^count)
64 | |> preload([:user])
65 | end
66 |
67 | end
68 |
--------------------------------------------------------------------------------
/test/support/model_case.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ModelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | model tests.
5 |
6 | You may define functions here to be used as helpers in
7 | your model tests. See `errors_on/2`'s definition as reference.
8 |
9 | Finally, if the test case interacts with the database,
10 | it cannot be async. For this reason, every test runs
11 | inside a transaction which is reset at the beginning
12 | of the test unless the test case is marked as async.
13 | """
14 |
15 | use ExUnit.CaseTemplate
16 |
17 | using do
18 | quote do
19 | alias ExAdminDemo.Repo
20 |
21 | import Ecto
22 | import Ecto.Changeset
23 | import Ecto.Query, only: [from: 1, from: 2]
24 | import ExAdminDemo.ModelCase
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ExAdminDemo.Repo)
30 | unless tags[:async] do
31 | Ecto.Adapters.SQL.Sandbox.mode(ExAdminDemo.Repo, {:shared, self()})
32 | end
33 |
34 | :ok
35 | end
36 |
37 | @doc """
38 | Helper for returning list of errors in model when passed certain data.
39 |
40 | ## Examples
41 |
42 | Given a User model that lists `:name` as a required field and validates
43 | `:password` to be safe, it would return:
44 |
45 | iex> errors_on(%User{}, %{password: "password"})
46 | [password: "is unsafe", name: "is blank"]
47 |
48 | You could then write your assertion like:
49 |
50 | assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"})
51 |
52 | You can also create the changeset manually and retrieve the errors
53 | field directly:
54 |
55 | iex> changeset = User.changeset(%User{}, password: "password")
56 | iex> {:password, "is unsafe"} in changeset.errors
57 | true
58 | """
59 | def errors_on(model, data) do
60 | model.__struct__.changeset(model, data).errors
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/web/admin/order.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ExAdmin.Order do
2 | use ExAdmin.Register
3 | alias ExAdminDemo.Order
4 |
5 | register_resource ExAdminDemo.Order do
6 | menu priority: 3
7 |
8 | scope :all, default: true
9 | scope :in_progress, fn(q) ->
10 | where(q, [p], is_nil(p.checked_out_at))
11 | end
12 | scope :complete, fn(q) ->
13 | where(q, [p], not is_nil(p.checked_out_at))
14 | end
15 |
16 | actions :all, only: [:index, :show]
17 |
18 | index do
19 | column "Order", fn(o) -> a " ##{o.id}", href: "/admin/orders/#{o.id}" end
20 | column "State", fn(o) ->
21 | status_tag Order.state(o)
22 | end
23 | column :checked_out_at, label: "Date"
24 | column :user, label: "Customer", fields: [:username]
25 | column :total, fn(o) -> text decimal_to_currency(o.total_price) end
26 | end
27 |
28 | show order do
29 | panel "Invoice" do
30 | table_for(order.line_items) do
31 | column "Product", fn(item) -> auto_link item.product end
32 | column "Price", fn(item) -> text decimal_to_currency(item.price) end
33 | end
34 | end
35 | end
36 |
37 | query do
38 | %{all: [preload: [:user, line_items: [:product]]]}
39 | end
40 |
41 | sidebar "Order Total", only: :show do
42 | attributes_table_for resource do
43 | row "Total", fn(order) -> text decimal_to_currency(order.total_price) end
44 | end
45 | end
46 | sidebar "Customer Information", only: :show do
47 | attributes_table_for resource.user do
48 | row "User", fn(c) -> auto_link c end
49 | row :email
50 | end
51 | end
52 |
53 | sidebar "ExAdmin Demo", only: [:index, :show] do
54 | Phoenix.View.render ExAdminDemo.AdminView, "sidebar_links.html", [model: "order"]
55 | end
56 | end
57 |
58 | def display_name(order) do
59 | decimal_to_currency(order.total_price) <>
60 | " - Order ##{order.id} (#{order.user.username})"
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/web/web.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Web do
2 | @moduledoc """
3 | A module that keeps using definitions for controllers,
4 | views and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use ExAdminDemo.Web, :controller
9 | use ExAdminDemo.Web, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below.
17 | """
18 |
19 | def model do
20 | quote do
21 | use Ecto.Schema
22 |
23 | import Ecto
24 | import Ecto.Changeset
25 | import Ecto.Query, only: [from: 1, from: 2]
26 | end
27 | end
28 |
29 | def controller do
30 | quote do
31 | use Phoenix.Controller
32 |
33 | alias ExAdminDemo.Repo
34 | import Ecto
35 | import Ecto.Query, only: [from: 1, from: 2]
36 |
37 | import ExAdminDemo.Router.Helpers
38 | import ExAdminDemo.Gettext
39 | end
40 | end
41 |
42 | def view do
43 | quote do
44 | use Phoenix.View, root: "web/templates"
45 |
46 | # Import convenience functions from controllers
47 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
48 |
49 | # Use all HTML functionality (forms, tags, etc)
50 | use Phoenix.HTML
51 |
52 | import ExAdminDemo.Router.Helpers
53 | import ExAdminDemo.ErrorHelpers
54 | import ExAdminDemo.Gettext
55 | end
56 | end
57 |
58 | def router do
59 | quote do
60 | use Phoenix.Router
61 | end
62 | end
63 |
64 | def channel do
65 | quote do
66 | use Phoenix.Channel
67 |
68 | alias ExAdminDemo.Repo
69 | import Ecto
70 | import Ecto.Query, only: [from: 1, from: 2]
71 | import ExAdminDemo.Gettext
72 | end
73 | end
74 |
75 | @doc """
76 | When used, dispatch to the appropriate controller/view/etc.
77 | """
78 | defmacro __using__(which) when is_atom(which) do
79 | apply(__MODULE__, which, [])
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See http://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: "js/app.js"
6 |
7 | // To use a separate vendor.js bundle, specify two files path
8 | // https://github.com/brunch/brunch/blob/stable/docs/config.md#files
9 | // joinTo: {
10 | // "js/app.js": /^(web\/static\/js)/,
11 | // "js/vendor.js": /^(web\/static\/vendor)|(deps)/
12 | // }
13 | //
14 | // To change the order of concatenation of files, explicitly mention here
15 | // https://github.com/brunch/brunch/tree/master/docs#concatenation
16 | // order: {
17 | // before: [
18 | // "web/static/vendor/js/jquery-2.1.1.js",
19 | // "web/static/vendor/js/bootstrap.min.js"
20 | // ]
21 | // }
22 | },
23 | stylesheets: {
24 | joinTo: "css/app.css"
25 | },
26 | templates: {
27 | joinTo: "js/app.js"
28 | }
29 | },
30 |
31 | conventions: {
32 | // This option sets where we should place non-css and non-js assets in.
33 | // By default, we set this to "/web/static/assets". Files in this directory
34 | // will be copied to `paths.public`, which is "priv/static" by default.
35 | assets: /^(web\/static\/assets)/
36 | },
37 |
38 | // Phoenix paths configuration
39 | paths: {
40 | // Dependencies and current project directories to watch
41 | watched: [
42 | "web/static",
43 | "test/static"
44 | ],
45 |
46 | // Where to compile files to
47 | public: "priv/static"
48 | },
49 |
50 | // Configure your plugins
51 | plugins: {
52 | babel: {
53 | // Do not use ES6 compiler in vendor code
54 | ignore: [/web\/static\/vendor/]
55 | }
56 | },
57 |
58 | modules: {
59 | autoRequire: {
60 | "js/app.js": ["web/static/js/app"]
61 | }
62 | },
63 |
64 | npm: {
65 | enabled: true,
66 | // Whitelist the npm deps to be pulled in as front-end assets.
67 | // All other deps in package.json will be excluded from the bundle.
68 | whitelist: ["phoenix", "phoenix_html"]
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/web/controllers/user_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.UserController do
2 | use ExAdminDemo.Web, :controller
3 |
4 | alias ExAdminDemo.User
5 |
6 | plug :scrub_params, "user" when action in [:create, :update]
7 |
8 | def index(conn, _params) do
9 | users = Repo.all(User)
10 | render(conn, "index.html", users: users)
11 | end
12 |
13 | def new(conn, _params) do
14 | changeset = User.changeset(%User{})
15 | render(conn, "new.html", changeset: changeset)
16 | end
17 |
18 | def create(conn, %{"user" => user_params}) do
19 | changeset = User.changeset(%User{}, user_params)
20 |
21 | case Repo.insert(changeset) do
22 | {:ok, _user} ->
23 | conn
24 | |> put_flash(:info, "User created successfully.")
25 | |> redirect(to: user_path(conn, :index))
26 | {:error, changeset} ->
27 | render(conn, "new.html", changeset: changeset)
28 | end
29 | end
30 |
31 | def show(conn, %{"id" => id}) do
32 | user = Repo.get!(User, id)
33 | render(conn, "show.html", user: user)
34 | end
35 |
36 | def edit(conn, %{"id" => id}) do
37 | user = Repo.get!(User, id)
38 | changeset = User.changeset(user)
39 | render(conn, "edit.html", user: user, changeset: changeset)
40 | end
41 |
42 | def update(conn, %{"id" => id, "user" => user_params}) do
43 | user = Repo.get!(User, id)
44 | changeset = User.changeset(user, user_params)
45 |
46 | case Repo.update(changeset) do
47 | {:ok, user} ->
48 | conn
49 | |> put_flash(:info, "User updated successfully.")
50 | |> redirect(to: user_path(conn, :show, user))
51 | {:error, changeset} ->
52 | render(conn, "edit.html", user: user, changeset: changeset)
53 | end
54 | end
55 |
56 | def delete(conn, %{"id" => id}) do
57 | user = Repo.get!(User, id)
58 |
59 | # Here we use delete! (with a bang) because we expect
60 | # it to always work (and if it does not, it will raise).
61 | Repo.delete!(user)
62 |
63 | conn
64 | |> put_flash(:info, "User deleted successfully.")
65 | |> redirect(to: user_path(conn, :index))
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/web/controllers/order_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.OrderController do
2 | use ExAdminDemo.Web, :controller
3 |
4 | alias ExAdminDemo.Order
5 |
6 | plug :scrub_params, "order" when action in [:create, :update]
7 |
8 | def index(conn, _params) do
9 | orders = Repo.all(Order)
10 | render(conn, "index.html", orders: orders)
11 | end
12 |
13 | def new(conn, _params) do
14 | changeset = Order.changeset(%Order{})
15 | render(conn, "new.html", changeset: changeset)
16 | end
17 |
18 | def create(conn, %{"order" => order_params}) do
19 | changeset = Order.changeset(%Order{}, order_params)
20 |
21 | case Repo.insert(changeset) do
22 | {:ok, _order} ->
23 | conn
24 | |> put_flash(:info, "Order created successfully.")
25 | |> redirect(to: order_path(conn, :index))
26 | {:error, changeset} ->
27 | render(conn, "new.html", changeset: changeset)
28 | end
29 | end
30 |
31 | def show(conn, %{"id" => id}) do
32 | order = Repo.get!(Order, id)
33 | render(conn, "show.html", order: order)
34 | end
35 |
36 | def edit(conn, %{"id" => id}) do
37 | order = Repo.get!(Order, id)
38 | changeset = Order.changeset(order)
39 | render(conn, "edit.html", order: order, changeset: changeset)
40 | end
41 |
42 | def update(conn, %{"id" => id, "order" => order_params}) do
43 | order = Repo.get!(Order, id)
44 | changeset = Order.changeset(order, order_params)
45 |
46 | case Repo.update(changeset) do
47 | {:ok, order} ->
48 | conn
49 | |> put_flash(:info, "Order updated successfully.")
50 | |> redirect(to: order_path(conn, :show, order))
51 | {:error, changeset} ->
52 | render(conn, "edit.html", order: order, changeset: changeset)
53 | end
54 | end
55 |
56 | def delete(conn, %{"id" => id}) do
57 | order = Repo.get!(Order, id)
58 |
59 | # Here we use delete! (with a bang) because we expect
60 | # it to always work (and if it does not, it will raise).
61 | Repo.delete!(order)
62 |
63 | conn
64 | |> put_flash(:info, "Order deleted successfully.")
65 | |> redirect(to: order_path(conn, :index))
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/web/controllers/product_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ProductController do
2 | use ExAdminDemo.Web, :controller
3 |
4 | alias ExAdminDemo.Product
5 |
6 | plug :scrub_params, "product" when action in [:create, :update]
7 |
8 | def index(conn, _params) do
9 | products = Repo.all(Product)
10 | render(conn, "index.html", products: products)
11 | end
12 |
13 | def new(conn, _params) do
14 | changeset = Product.changeset(%Product{})
15 | render(conn, "new.html", changeset: changeset)
16 | end
17 |
18 | def create(conn, %{"product" => product_params}) do
19 | changeset = Product.changeset(%Product{}, product_params)
20 |
21 | case Repo.insert(changeset) do
22 | {:ok, _product} ->
23 | conn
24 | |> put_flash(:info, "Product created successfully.")
25 | |> redirect(to: product_path(conn, :index))
26 | {:error, changeset} ->
27 | render(conn, "new.html", changeset: changeset)
28 | end
29 | end
30 |
31 | def show(conn, %{"id" => id}) do
32 | product = Repo.get!(Product, id)
33 | render(conn, "show.html", product: product)
34 | end
35 |
36 | def edit(conn, %{"id" => id}) do
37 | product = Repo.get!(Product, id)
38 | changeset = Product.changeset(product)
39 | render(conn, "edit.html", product: product, changeset: changeset)
40 | end
41 |
42 | def update(conn, %{"id" => id, "product" => product_params}) do
43 | product = Repo.get!(Product, id)
44 | changeset = Product.changeset(product, product_params)
45 |
46 | case Repo.update(changeset) do
47 | {:ok, product} ->
48 | conn
49 | |> put_flash(:info, "Product updated successfully.")
50 | |> redirect(to: product_path(conn, :show, product))
51 | {:error, changeset} ->
52 | render(conn, "edit.html", product: product, changeset: changeset)
53 | end
54 | end
55 |
56 | def delete(conn, %{"id" => id}) do
57 | product = Repo.get!(Product, id)
58 |
59 | # Here we use delete! (with a bang) because we expect
60 | # it to always work (and if it does not, it will raise).
61 | Repo.delete!(product)
62 |
63 | conn
64 | |> put_flash(:info, "Product deleted successfully.")
65 | |> redirect(to: product_path(conn, :index))
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :ex_admin_demo,
6 | version: "0.0.2",
7 | elixir: "~> 1.0",
8 | elixirc_paths: elixirc_paths(Mix.env),
9 | compilers: [:phoenix, :gettext] ++ Mix.compilers,
10 | build_embedded: Mix.env == :prod,
11 | start_permanent: Mix.env == :prod,
12 | aliases: aliases,
13 | deps: deps]
14 | end
15 |
16 | # Configuration for the OTP application.
17 | #
18 | # Type `mix help compile.app` for more information.
19 | def application do
20 | [mod: {ExAdminDemo, []},
21 | applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
22 | :phoenix_haml, :ex_aws, :httpoison, :ex_admin, :ex_queb,
23 | :phoenix_ecto, :postgrex]]
24 | end
25 |
26 | # Specifies which paths to compile per environment.
27 | defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
28 | defp elixirc_paths(_), do: ["lib", "web"]
29 |
30 | # Specifies your project dependencies.
31 | #
32 | # Type `mix help deps` for examples and options.
33 | defp deps do
34 | [{:phoenix, "~> 1.1"},
35 | {:phoenix_ecto, "~> 3.0"},
36 | {:postgrex, ">= 0.0.0"},
37 | {:ecto, "~> 2.0", override: true},
38 | {:phoenix_html, "~> 2.3"},
39 | {:phoenix_live_reload, "~> 1.0", only: :dev},
40 | {:gettext, "~> 0.9"},
41 | {:phoenix_haml, "~> 0.2"},
42 | {:calliope, github: "nurugger07/calliope", override: true},
43 | {:ex_admin, github: "smpallen99/ex_admin"},
44 | {:arc, "~> 0.5.1"},
45 | {:arc_ecto, "~> 0.3.2"},
46 | {:ex_aws, "~> 0.4.10"}, # Required if using Amazon S3
47 | {:httpoison, "~> 0.7"}, # Required if using Amazon S3
48 | {:faker, "~> 0.6.0"},
49 | {:number, "~> 0.4.1"},
50 | {:cowboy, "~> 1.0"}]
51 | end
52 |
53 | # Aliases are shortcut or tasks specific to the current project.
54 | # For example, to create, migrate and run the seeds file at once:
55 | #
56 | # $ mix ecto.setup
57 | #
58 | # See the documentation for `Mix` for more info on aliases.
59 | defp aliases do
60 | ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
61 | "ecto.reset": ["ecto.drop", "ecto.setup"]]
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/web/controllers/line_item_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.LineItemController do
2 | use ExAdminDemo.Web, :controller
3 |
4 | alias ExAdminDemo.LineItem
5 |
6 | plug :scrub_params, "line_item" when action in [:create, :update]
7 |
8 | def index(conn, _params) do
9 | line_items = Repo.all(LineItem)
10 | render(conn, "index.html", line_items: line_items)
11 | end
12 |
13 | def new(conn, _params) do
14 | changeset = LineItem.changeset(%LineItem{})
15 | render(conn, "new.html", changeset: changeset)
16 | end
17 |
18 | def create(conn, %{"line_item" => line_item_params}) do
19 | changeset = LineItem.changeset(%LineItem{}, line_item_params)
20 |
21 | case Repo.insert(changeset) do
22 | {:ok, _line_item} ->
23 | conn
24 | |> put_flash(:info, "Line item created successfully.")
25 | |> redirect(to: line_item_path(conn, :index))
26 | {:error, changeset} ->
27 | render(conn, "new.html", changeset: changeset)
28 | end
29 | end
30 |
31 | def show(conn, %{"id" => id}) do
32 | line_item = Repo.get!(LineItem, id)
33 | render(conn, "show.html", line_item: line_item)
34 | end
35 |
36 | def edit(conn, %{"id" => id}) do
37 | line_item = Repo.get!(LineItem, id)
38 | changeset = LineItem.changeset(line_item)
39 | render(conn, "edit.html", line_item: line_item, changeset: changeset)
40 | end
41 |
42 | def update(conn, %{"id" => id, "line_item" => line_item_params}) do
43 | line_item = Repo.get!(LineItem, id)
44 | changeset = LineItem.changeset(line_item, line_item_params)
45 |
46 | case Repo.update(changeset) do
47 | {:ok, line_item} ->
48 | conn
49 | |> put_flash(:info, "Line item updated successfully.")
50 | |> redirect(to: line_item_path(conn, :show, line_item))
51 | {:error, changeset} ->
52 | render(conn, "edit.html", line_item: line_item, changeset: changeset)
53 | end
54 | end
55 |
56 | def delete(conn, %{"id" => id}) do
57 | line_item = Repo.get!(LineItem, id)
58 |
59 | # Here we use delete! (with a bang) because we expect
60 | # it to always work (and if it does not, it will raise).
61 | Repo.delete!(line_item)
62 |
63 | conn
64 | |> put_flash(:info, "Line item deleted successfully.")
65 | |> redirect(to: line_item_path(conn, :index))
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/web/static/js/socket.js:
--------------------------------------------------------------------------------
1 | // NOTE: The contents of this file will only be executed if
2 | // you uncomment its entry in "web/static/js/app.js".
3 |
4 | // To use Phoenix channels, the first step is to import Socket
5 | // and connect at the socket path in "lib/my_app/endpoint.ex":
6 | import {Socket} from "phoenix"
7 |
8 | let socket = new Socket("/socket", {params: {token: window.userToken}})
9 |
10 | // When you connect, you'll often need to authenticate the client.
11 | // For example, imagine you have an authentication plug, `MyAuth`,
12 | // which authenticates the session and assigns a `:current_user`.
13 | // If the current user exists you can assign the user's token in
14 | // the connection for use in the layout.
15 | //
16 | // In your "web/router.ex":
17 | //
18 | // pipeline :browser do
19 | // ...
20 | // plug MyAuth
21 | // plug :put_user_token
22 | // end
23 | //
24 | // defp put_user_token(conn, _) do
25 | // if current_user = conn.assigns[:current_user] do
26 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id)
27 | // assign(conn, :user_token, token)
28 | // else
29 | // conn
30 | // end
31 | // end
32 | //
33 | // Now you need to pass this token to JavaScript. You can do so
34 | // inside a script tag in "web/templates/layout/app.html.eex":
35 | //
36 | //
37 | //
38 | // You will need to verify the user token in the "connect/2" function
39 | // in "web/channels/user_socket.ex":
40 | //
41 | // def connect(%{"token" => token}, socket) do
42 | // # max_age: 1209600 is equivalent to two weeks in seconds
43 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
44 | // {:ok, user_id} ->
45 | // {:ok, assign(socket, :user, user_id)}
46 | // {:error, reason} ->
47 | // :error
48 | // end
49 | // end
50 | //
51 | // Finally, pass the token on connect as below. Or remove it
52 | // from connect if you don't care about authentication.
53 |
54 | socket.connect()
55 |
56 | // Now that you are connected, you can join channels with a topic:
57 | let channel = socket.channel("topic:subtopic", {})
58 | channel.join()
59 | .receive("ok", resp => { console.log("Joined successfully", resp) })
60 | .receive("error", resp => { console.log("Unable to join", resp) })
61 |
62 | export default socket
63 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we configure the host to read the PORT
4 | # from the system environment. Therefore, you will need
5 | # to set PORT=80 before running your server.
6 | #
7 | # You should also configure the url host to something
8 | # meaningful, we use this information when generating URLs.
9 | #
10 | # Finally, we also include the path to a manifest
11 | # containing the digested version of static files. This
12 | # manifest is generated by the mix phoenix.digest task
13 | # which you typically run after static files are built.
14 | config :ex_admin_demo, ExAdminDemo.Endpoint,
15 | http: [port: {:system, "PORT"}],
16 | url: [host: "example.com", port: 80],
17 | cache_static_manifest: "priv/static/manifest.json"
18 |
19 | # Do not print debug messages in production
20 | config :logger, level: :info
21 |
22 | # ## SSL Support
23 | #
24 | # To get SSL working, you will need to add the `https` key
25 | # to the previous section and set your `:url` port to 443:
26 | #
27 | # config :ex_admin_demo, ExAdminDemo.Endpoint,
28 | # ...
29 | # url: [host: "example.com", port: 443],
30 | # https: [port: 443,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
33 | #
34 | # Where those two env variables return an absolute path to
35 | # the key and cert in disk or a relative path inside priv,
36 | # for example "priv/ssl/server.key".
37 | #
38 | # We also recommend setting `force_ssl`, ensuring no data is
39 | # ever sent via http, always redirecting to https:
40 | #
41 | # config :ex_admin_demo, ExAdminDemo.Endpoint,
42 | # force_ssl: [hsts: true]
43 | #
44 | # Check `Plug.SSL` for all available options in `force_ssl`.
45 |
46 | # ## Using releases
47 | #
48 | # If you are doing OTP releases, you need to instruct Phoenix
49 | # to start the server for all endpoints:
50 | #
51 | # config :phoenix, :serve_endpoints, true
52 | #
53 | # Alternatively, you can configure exactly which server to
54 | # start per endpoint:
55 | #
56 | # config :ex_admin_demo, ExAdminDemo.Endpoint, server: true
57 | #
58 | # You will also need to set the application root to `.` in order
59 | # for the new static assets to be served after a hot upgrade:
60 | #
61 | # config :ex_admin_demo, ExAdminDemo.Endpoint, root: "."
62 |
63 | # Finally import the config/prod.secret.exs
64 | # which should be versioned separately.
65 | import_config "prod.secret.exs"
66 |
--------------------------------------------------------------------------------
/test/controllers/user_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.UserControllerTest do
2 | use ExAdminDemo.ConnCase
3 |
4 | alias ExAdminDemo.User
5 | @valid_attrs %{email: "some content", password_hash: "some content", username: "some content"}
6 | @invalid_attrs %{}
7 |
8 | test "lists all entries on index", %{conn: conn} do
9 | conn = get conn, user_path(conn, :index)
10 | assert html_response(conn, 200) =~ "Listing users"
11 | end
12 |
13 | test "renders form for new resources", %{conn: conn} do
14 | conn = get conn, user_path(conn, :new)
15 | assert html_response(conn, 200) =~ "New user"
16 | end
17 |
18 | test "creates resource and redirects when data is valid", %{conn: conn} do
19 | conn = post conn, user_path(conn, :create), user: @valid_attrs
20 | assert redirected_to(conn) == user_path(conn, :index)
21 | assert Repo.get_by(User, @valid_attrs)
22 | end
23 |
24 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
25 | conn = post conn, user_path(conn, :create), user: @invalid_attrs
26 | assert html_response(conn, 200) =~ "New user"
27 | end
28 |
29 | test "shows chosen resource", %{conn: conn} do
30 | user = Repo.insert! %User{}
31 | conn = get conn, user_path(conn, :show, user)
32 | assert html_response(conn, 200) =~ "Show user"
33 | end
34 |
35 | test "renders page not found when id is nonexistent", %{conn: conn} do
36 | assert_error_sent 404, fn ->
37 | get conn, user_path(conn, :show, -1)
38 | end
39 | end
40 |
41 | test "renders form for editing chosen resource", %{conn: conn} do
42 | user = Repo.insert! %User{}
43 | conn = get conn, user_path(conn, :edit, user)
44 | assert html_response(conn, 200) =~ "Edit user"
45 | end
46 |
47 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
48 | user = Repo.insert! %User{}
49 | conn = put conn, user_path(conn, :update, user), user: @valid_attrs
50 | assert redirected_to(conn) == user_path(conn, :show, user)
51 | assert Repo.get_by(User, @valid_attrs)
52 | end
53 |
54 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
55 | user = Repo.insert! %User{}
56 | conn = put conn, user_path(conn, :update, user), user: @invalid_attrs
57 | assert html_response(conn, 200) =~ "Edit user"
58 | end
59 |
60 | test "deletes chosen resource", %{conn: conn} do
61 | user = Repo.insert! %User{}
62 | conn = delete conn, user_path(conn, :delete, user)
63 | assert redirected_to(conn) == user_path(conn, :index)
64 | refute Repo.get(User, user.id)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/test/controllers/line_item_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.LineItemControllerTest do
2 | use ExAdminDemo.ConnCase
3 |
4 | alias ExAdminDemo.LineItem
5 | @valid_attrs %{price: "120.5"}
6 | @invalid_attrs %{}
7 |
8 | test "lists all entries on index", %{conn: conn} do
9 | conn = get conn, line_item_path(conn, :index)
10 | assert html_response(conn, 200) =~ "Listing line items"
11 | end
12 |
13 | test "renders form for new resources", %{conn: conn} do
14 | conn = get conn, line_item_path(conn, :new)
15 | assert html_response(conn, 200) =~ "New line item"
16 | end
17 |
18 | test "creates resource and redirects when data is valid", %{conn: conn} do
19 | conn = post conn, line_item_path(conn, :create), line_item: @valid_attrs
20 | assert redirected_to(conn) == line_item_path(conn, :index)
21 | assert Repo.get_by(LineItem, @valid_attrs)
22 | end
23 |
24 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
25 | conn = post conn, line_item_path(conn, :create), line_item: @invalid_attrs
26 | assert html_response(conn, 200) =~ "New line item"
27 | end
28 |
29 | test "shows chosen resource", %{conn: conn} do
30 | line_item = Repo.insert! %LineItem{}
31 | conn = get conn, line_item_path(conn, :show, line_item)
32 | assert html_response(conn, 200) =~ "Show line item"
33 | end
34 |
35 | test "renders page not found when id is nonexistent", %{conn: conn} do
36 | assert_error_sent 404, fn ->
37 | get conn, line_item_path(conn, :show, -1)
38 | end
39 | end
40 |
41 | test "renders form for editing chosen resource", %{conn: conn} do
42 | line_item = Repo.insert! %LineItem{}
43 | conn = get conn, line_item_path(conn, :edit, line_item)
44 | assert html_response(conn, 200) =~ "Edit line item"
45 | end
46 |
47 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
48 | line_item = Repo.insert! %LineItem{}
49 | conn = put conn, line_item_path(conn, :update, line_item), line_item: @valid_attrs
50 | assert redirected_to(conn) == line_item_path(conn, :show, line_item)
51 | assert Repo.get_by(LineItem, @valid_attrs)
52 | end
53 |
54 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
55 | line_item = Repo.insert! %LineItem{}
56 | conn = put conn, line_item_path(conn, :update, line_item), line_item: @invalid_attrs
57 | assert html_response(conn, 200) =~ "Edit line item"
58 | end
59 |
60 | test "deletes chosen resource", %{conn: conn} do
61 | line_item = Repo.insert! %LineItem{}
62 | conn = delete conn, line_item_path(conn, :delete, line_item)
63 | assert redirected_to(conn) == line_item_path(conn, :index)
64 | refute Repo.get(LineItem, line_item.id)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/test/controllers/product_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ProductControllerTest do
2 | use ExAdminDemo.ConnCase
3 |
4 | alias ExAdminDemo.Product
5 | @valid_attrs %{author: "some content", available_on: Ecto.DateTime.utc, description: "some content", featured: true, price: "120.5", title: "some content"}
6 | @invalid_attrs %{}
7 |
8 | test "lists all entries on index", %{conn: conn} do
9 | conn = get conn, product_path(conn, :index)
10 | assert html_response(conn, 200) =~ "Listing products"
11 | end
12 |
13 | test "renders form for new resources", %{conn: conn} do
14 | conn = get conn, product_path(conn, :new)
15 | assert html_response(conn, 200) =~ "New product"
16 | end
17 |
18 | test "creates resource and redirects when data is valid", %{conn: conn} do
19 | conn = post conn, product_path(conn, :create), product: @valid_attrs
20 | assert redirected_to(conn) == product_path(conn, :index)
21 | assert Repo.get_by(Product, @valid_attrs)
22 | end
23 |
24 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
25 | conn = post conn, product_path(conn, :create), product: @invalid_attrs
26 | assert html_response(conn, 200) =~ "New product"
27 | end
28 |
29 | test "shows chosen resource", %{conn: conn} do
30 | product = Repo.insert! %Product{}
31 | conn = get conn, product_path(conn, :show, product)
32 | assert html_response(conn, 200) =~ "Show product"
33 | end
34 |
35 | test "renders page not found when id is nonexistent", %{conn: conn} do
36 | assert_error_sent 404, fn ->
37 | get conn, product_path(conn, :show, -1)
38 | end
39 | end
40 |
41 | test "renders form for editing chosen resource", %{conn: conn} do
42 | product = Repo.insert! %Product{}
43 | conn = get conn, product_path(conn, :edit, product)
44 | assert html_response(conn, 200) =~ "Edit product"
45 | end
46 |
47 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
48 | product = Repo.insert! %Product{}
49 | conn = put conn, product_path(conn, :update, product), product: @valid_attrs
50 | assert redirected_to(conn) == product_path(conn, :show, product)
51 | assert Repo.get_by(Product, @valid_attrs)
52 | end
53 |
54 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
55 | product = Repo.insert! %Product{}
56 | conn = put conn, product_path(conn, :update, product), product: @invalid_attrs
57 | assert html_response(conn, 200) =~ "Edit product"
58 | end
59 |
60 | test "deletes chosen resource", %{conn: conn} do
61 | product = Repo.insert! %Product{}
62 | conn = delete conn, product_path(conn, :delete, product)
63 | assert redirected_to(conn) == product_path(conn, :index)
64 | refute Repo.get(Product, product.id)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This file is a PO Template file. `msgid`s here are often extracted from
2 | ## source code; add new translations manually only if they're dynamic
3 | ## translations that can't be statically extracted. Run `mix
4 | ## gettext.extract` to bring this file up to date. Leave `msgstr`s empty as
5 | ## changing them here as no effect; edit them in PO (`.po`) files instead.
6 | ## From Ecto.Changeset.cast/4
7 | msgid "can't be blank"
8 | msgstr ""
9 |
10 | ## From Ecto.Changeset.unique_constraint/3
11 | msgid "has already been taken"
12 | msgstr ""
13 |
14 | ## From Ecto.Changeset.put_change/3
15 | msgid "is invalid"
16 | msgstr ""
17 |
18 | ## From Ecto.Changeset.validate_format/3
19 | msgid "has invalid format"
20 | msgstr ""
21 |
22 | ## From Ecto.Changeset.validate_subset/3
23 | msgid "has an invalid entry"
24 | msgstr ""
25 |
26 | ## From Ecto.Changeset.validate_exclusion/3
27 | msgid "is reserved"
28 | msgstr ""
29 |
30 | ## From Ecto.Changeset.validate_confirmation/3
31 | msgid "does not match confirmation"
32 | msgstr ""
33 |
34 | ## From Ecto.Changeset.no_assoc_constraint/3
35 | msgid "is still associated to this entry"
36 | msgstr ""
37 |
38 | msgid "are still associated to this entry"
39 | msgstr ""
40 |
41 | ## From Ecto.Changeset.validate_length/3
42 | msgid "should be %{count} character(s)"
43 | msgid_plural "should be %{count} character(s)"
44 | msgstr[0] ""
45 | msgstr[1] ""
46 |
47 | msgid "should have %{count} item(s)"
48 | msgid_plural "should have %{count} item(s)"
49 | msgstr[0] ""
50 | msgstr[1] ""
51 |
52 | msgid "should be at least %{count} character(s)"
53 | msgid_plural "should be at least %{count} character(s)"
54 | msgstr[0] ""
55 | msgstr[1] ""
56 |
57 | msgid "should have at least %{count} item(s)"
58 | msgid_plural "should have at least %{count} item(s)"
59 | msgstr[0] ""
60 | msgstr[1] ""
61 |
62 | msgid "should be at most %{count} character(s)"
63 | msgid_plural "should be at most %{count} character(s)"
64 | msgstr[0] ""
65 | msgstr[1] ""
66 |
67 | msgid "should have at most %{count} item(s)"
68 | msgid_plural "should have at most %{count} item(s)"
69 | msgstr[0] ""
70 | msgstr[1] ""
71 |
72 | ## From Ecto.Changeset.validate_number/3
73 | msgid "must be less than %{count}"
74 | msgid_plural "must be less than %{count}"
75 | msgstr[0] ""
76 | msgstr[1] ""
77 |
78 | msgid "must be greater than %{count}"
79 | msgid_plural "must be greater than %{count}"
80 | msgstr[0] ""
81 | msgstr[1] ""
82 |
83 | msgid "must be less than or equal to %{count}"
84 | msgid_plural "must be less than or equal to %{count}"
85 | msgstr[0] ""
86 | msgstr[1] ""
87 |
88 | msgid "must be greater than or equal to %{count}"
89 | msgid_plural "must be greater than or equal to %{count}"
90 | msgstr[0] ""
91 | msgstr[1] ""
92 |
93 | msgid "must be equal to %{count}"
94 | msgid_plural "must be equal to %{count}"
95 | msgstr[0] ""
96 | msgstr[1] ""
97 |
--------------------------------------------------------------------------------
/web/admin/product.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ExAdmin.Product do
2 | use ExAdmin.Register
3 | require Logger
4 | import Ecto.Query
5 | alias ExAdminDemo.Order
6 | alias ExAdminDemo.LineItem
7 | alias ExAdminDemo.Product
8 | alias ExAdminDemo.Repo
9 |
10 | register_resource ExAdminDemo.Product do
11 | menu priority: 2
12 | scope :all, default: true
13 | scope :available, [], fn(q) ->
14 | now = Ecto.Date.utc
15 | where(q, [p], p.available_on <= ^now)
16 | end
17 | scope :drafts, fn(q) ->
18 | now = Ecto.Date.utc
19 | where(q, [p], p.available_on > ^now)
20 | end
21 | scope :featured_products, [], fn(q) ->
22 | where(q, [p], p.featured == true)
23 | end
24 |
25 | index as: :grid, default: true, columns: 4 do
26 | import Kernel, except: [div: 2]
27 | cell fn(p) ->
28 | div ".box" do
29 | div ".box-body" do
30 | a href: admin_resource_path(p, :show) do
31 | img(src: ExAdminDemo.Image.url({p.image_file_name, p}, :thumb), height: 100)
32 | end
33 | end
34 | div ".box-footer" do
35 | a truncate(p.title), href: admin_resource_path(p, :show)
36 | end
37 | end
38 | end
39 | end
40 |
41 | show _product do
42 | attributes_table do
43 | row :title
44 | row :description
45 | row :author
46 | row :price
47 | row :featured, toggle: true
48 | row :available_on
49 | row "Cover", &(img(src: ExAdminDemo.Image.url({&1.image_file_name, &1}), height: 250))
50 | end
51 | end
52 |
53 | form product do
54 | inputs do
55 | input product, :title
56 | input product, :description
57 | input product, :author
58 | input product, :price
59 | input product, :featured
60 | input product, :available_on
61 | input product, :image_file_name, type: :file
62 | end
63 | end
64 |
65 | sidebar "Product Stats", only: :show do
66 | attributes_table_for resource do
67 | row "Total Sold", fn(r) ->
68 | Order.find_with_product(r)
69 | |> Repo.all
70 | |> Enum.count
71 | |> Integer.to_string
72 | |> text
73 | end
74 | row "Dollar Value", fn(r) ->
75 | id = r.id
76 | LineItem
77 | |> where([l], l.product_id == ^id)
78 | |> Repo.all
79 | |> Enum.reduce(Decimal.new(0.0), fn(li, sum) ->
80 | Decimal.add sum, li.price
81 | end)
82 | |> decimal_to_currency
83 | |> text
84 | end
85 | end
86 | end
87 |
88 | sidebar "ExAdmin Demo", only: [:index, :show] do
89 | Phoenix.View.render ExAdminDemo.AdminView, "sidebar_links.html", [model: "product"]
90 | end
91 |
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files. Do not add, change, or
2 | ## remove `msgid`s manually here as they're tied to the ones in the
3 | ## corresponding POT file (with the same domain). Use `mix gettext.extract
4 | ## --merge` or `mix gettext.merge` to merge POT files into PO files.
5 | msgid ""
6 | msgstr ""
7 | "Language: en\n"
8 |
9 | ## From Ecto.Changeset.cast/4
10 | msgid "can't be blank"
11 | msgstr ""
12 |
13 | ## From Ecto.Changeset.unique_constraint/3
14 | msgid "has already been taken"
15 | msgstr ""
16 |
17 | ## From Ecto.Changeset.put_change/3
18 | msgid "is invalid"
19 | msgstr ""
20 |
21 | ## From Ecto.Changeset.validate_format/3
22 | msgid "has invalid format"
23 | msgstr ""
24 |
25 | ## From Ecto.Changeset.validate_subset/3
26 | msgid "has an invalid entry"
27 | msgstr ""
28 |
29 | ## From Ecto.Changeset.validate_exclusion/3
30 | msgid "is reserved"
31 | msgstr ""
32 |
33 | ## From Ecto.Changeset.validate_confirmation/3
34 | msgid "does not match confirmation"
35 | msgstr ""
36 |
37 | ## From Ecto.Changeset.no_assoc_constraint/3
38 | msgid "is still associated to this entry"
39 | msgstr ""
40 |
41 | msgid "are still associated to this entry"
42 | msgstr ""
43 |
44 | ## From Ecto.Changeset.validate_length/3
45 | msgid "should be %{count} character(s)"
46 | msgid_plural "should be %{count} character(s)"
47 | msgstr[0] ""
48 | msgstr[1] ""
49 |
50 | msgid "should have %{count} item(s)"
51 | msgid_plural "should have %{count} item(s)"
52 | msgstr[0] ""
53 | msgstr[1] ""
54 |
55 | msgid "should be at least %{count} character(s)"
56 | msgid_plural "should be at least %{count} character(s)"
57 | msgstr[0] ""
58 | msgstr[1] ""
59 |
60 | msgid "should have at least %{count} item(s)"
61 | msgid_plural "should have at least %{count} item(s)"
62 | msgstr[0] ""
63 | msgstr[1] ""
64 |
65 | msgid "should be at most %{count} character(s)"
66 | msgid_plural "should be at most %{count} character(s)"
67 | msgstr[0] ""
68 | msgstr[1] ""
69 |
70 | msgid "should have at most %{count} item(s)"
71 | msgid_plural "should have at most %{count} item(s)"
72 | msgstr[0] ""
73 | msgstr[1] ""
74 |
75 | ## From Ecto.Changeset.validate_number/3
76 | msgid "must be less than %{count}"
77 | msgid_plural "must be less than %{count}"
78 | msgstr[0] ""
79 | msgstr[1] ""
80 |
81 | msgid "must be greater than %{count}"
82 | msgid_plural "must be greater than %{count}"
83 | msgstr[0] ""
84 | msgstr[1] ""
85 |
86 | msgid "must be less than or equal to %{count}"
87 | msgid_plural "must be less than or equal to %{count}"
88 | msgstr[0] ""
89 | msgstr[1] ""
90 |
91 | msgid "must be greater than or equal to %{count}"
92 | msgid_plural "must be greater than or equal to %{count}"
93 | msgstr[0] ""
94 | msgstr[1] ""
95 |
96 | msgid "must be equal to %{count}"
97 | msgid_plural "must be equal to %{count}"
98 | msgstr[0] ""
99 | msgstr[1] ""
100 |
--------------------------------------------------------------------------------
/test/controllers/order_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.OrderControllerTest do
2 | use ExAdminDemo.ConnCase
3 |
4 | alias ExAdminDemo.{Order, Repo, User}
5 | @valid_attrs %{checked_out_at: Ecto.DateTime.utc, total_price: "120.5"}
6 | @invalid_attrs %{}
7 |
8 | setup %{conn: conn} do
9 | user = Repo.insert! %User{username: "test", email: "test@test"}
10 | valid_attrs = Map.put(@valid_attrs, :user_id, user.id)
11 | {:ok, conn: conn, user: user, valid_attrs: valid_attrs}
12 | end
13 | test "lists all entries on index", %{conn: conn} do
14 | conn = get conn, order_path(conn, :index)
15 | assert html_response(conn, 200) =~ "Listing orders"
16 | end
17 |
18 | test "renders form for new resources", %{conn: conn} do
19 | conn = get conn, order_path(conn, :new)
20 | assert html_response(conn, 200) =~ "New order"
21 | end
22 |
23 | test "creates resource and redirects when data is valid", %{conn: conn, valid_attrs: valid_attrs} do
24 | conn = post conn, order_path(conn, :create), order: valid_attrs
25 | assert redirected_to(conn) == order_path(conn, :index)
26 | assert Repo.get_by(Order, @valid_attrs)
27 | end
28 |
29 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
30 | conn = post conn, order_path(conn, :create), order: @invalid_attrs
31 | assert html_response(conn, 200) =~ "New order"
32 | end
33 |
34 | test "shows chosen resource", %{conn: conn} do
35 | order = Repo.insert! %Order{}
36 | conn = get conn, order_path(conn, :show, order)
37 | assert html_response(conn, 200) =~ "Show order"
38 | end
39 |
40 | test "renders page not found when id is nonexistent", %{conn: conn} do
41 | assert_error_sent 404, fn ->
42 | get conn, order_path(conn, :show, -1)
43 | end
44 | end
45 |
46 | test "renders form for editing chosen resource", %{conn: conn} do
47 | order = Repo.insert! %Order{}
48 | conn = get conn, order_path(conn, :edit, order)
49 | assert html_response(conn, 200) =~ "Edit order"
50 | end
51 |
52 | test "updates chosen resource and redirects when data is valid", %{conn: conn, valid_attrs: valid_attrs} do
53 | order = Repo.insert! %Order{}
54 | conn = put conn, order_path(conn, :update, order), order: valid_attrs
55 | assert redirected_to(conn) == order_path(conn, :show, order)
56 | assert Repo.get_by(Order, valid_attrs)
57 | end
58 |
59 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
60 | order = Repo.insert! %Order{}
61 | conn = put conn, order_path(conn, :update, order), order: @invalid_attrs
62 | assert html_response(conn, 200) =~ "Edit order"
63 | end
64 |
65 | test "deletes chosen resource", %{conn: conn} do
66 | order = Repo.insert! %Order{}
67 | conn = delete conn, order_path(conn, :delete, order)
68 | assert redirected_to(conn) == order_path(conn, :index)
69 | refute Repo.get(Order, order.id)
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/web/admin/dashboard.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAdminDemo.ExAdmin.Dashboard do
2 | use ExAdmin.Register
3 | use ExAdmin.Page
4 | import Ecto.Query
5 | alias ExAdminDemo.{Order, Product, User, Repo}
6 |
7 | register_page "Dashboard" do
8 | menu priority: 1, label: "Dashboard"
9 | content do
10 | columns do
11 | column do
12 | panel "Recent Orders" do
13 | Order.ordered(5)
14 | |> Repo.all
15 | |> table_for do
16 | column "State", fn(o) -> status_tag Order.state(o) end
17 | column "Customer", fn(o) ->
18 | a o.user.email, href: "/admin/users/#{o.user.id}"
19 | end
20 | column "Total", fn(o) -> text decimal_to_currency(o.total_price) end
21 | end
22 | end
23 | end
24 | column do
25 | panel "Recent Customers" do
26 | User.ordered(5)
27 | |> Repo.all
28 | |> table_for do
29 | column "User Name", fn(c) -> a c.username, href: "/admin/users/#{c.id}" end
30 | column "Email", fn(c) -> text c.email end
31 | end
32 | end
33 | end
34 | end
35 | columns do
36 | column do
37 | panel "Statistics" do
38 | markup_contents do
39 | products = Repo.all(Product) |> Enum.count
40 | orders = Repo.all(Order) |> Enum.count
41 | users = Repo.all(User) |> Enum.count
42 |
43 | table ".table" do
44 | thead do
45 | th "Item"
46 | th "Total Count"
47 | end
48 | tbody do
49 | tr do
50 | td "Products"
51 | td "#{products}"
52 | end
53 | tr do
54 | td "Orders"
55 | td "#{orders}"
56 | end
57 | tr do
58 | td "Users"
59 | td "#{users}"
60 | end
61 | end
62 | end
63 | end
64 | end
65 | end
66 | column do
67 | panel "News" do
68 | markup_contents do
69 | table ".table" do
70 | tr do
71 | td "ExAdmin 0.7.7 now supports multi-column panels!"
72 | end
73 | tr do
74 | td "Checkout ExAdmin Github master for Ecto 2.0 support"
75 | end
76 | end
77 | end
78 | end
79 | end
80 | column do
81 | panel "ExAdmin Demo" do
82 | markup_contents do
83 | Phoenix.View.render(ExAdminDemo.AdminView, "sidebar_links.html", [model: "dashboard"])
84 | |> Phoenix.HTML.safe_to_string
85 | |> raw
86 | end
87 | end
88 | end
89 | end
90 | # columns do
91 | # column do
92 | # # markup :nested do
93 | # panel "ExAdmin Demo" do
94 | # #Phoenix.View.render ExAdminDemo.AdminView, "sidebar_links.html", [model: "dashboard"]
95 | # end
96 | # # end
97 | # end
98 | # end
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"arc": {:hex, :arc, "0.5.3", "2bc1b4ce8c1064ae12015f5612f3e3853d354859905bb34088b3579bf34a519d", [:mix], [{:httpoison, "~> 0.7", [hex: :httpoison, optional: true]}, {:poison, "~> 1.2 or ~> 2.0", [hex: :poison, optional: true]}, {:ex_aws, "~> 0.4.10 or ~> 0.5.0", [hex: :ex_aws, optional: true]}]},
2 | "arc_ecto": {:hex, :arc_ecto, "0.3.2", "8893135fe4d5cf9b2a6aeb0e17bedd5c6f7ad5d89f760995805451c859bf8f89", [:mix], [{:ecto, ">= 0.10.0", [hex: :ecto, optional: false]}, {:arc, "~> 0.2", [hex: :arc, optional: false]}]},
3 | "calliope": {:git, "https://github.com/nurugger07/calliope.git", "43e1bc12d220a0bb66e564666d125a48b4c6520a", []},
4 | "certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []},
5 | "connection": {:hex, :connection, "1.0.3", "3145f7416be3df248a4935f24e3221dc467c1e3a158d62015b35bd54da365786", [:mix], []},
6 | "cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
7 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
8 | "csvlixir": {:hex, :csvlixir, "1.0.0", "e9fd30abfca2d312390060e86bb7ec52487c813824dcccad45bb13e85ecad6b1", [:mix], []},
9 | "db_connection": {:hex, :db_connection, "1.0.0-rc.3", "d9ceb670fe300271140af46d357b669983cd16bc0d01206d7d3222dde56cf038", [:mix], [{:sbroker, "~> 1.0.0-beta.3", [hex: :sbroker, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:connection, "~> 1.0.2", [hex: :connection, optional: false]}]},
10 | "decimal": {:hex, :decimal, "1.1.2", "79a769d4657b2d537b51ef3c02d29ab7141d2b486b516c109642d453ee08e00c", [:mix], []},
11 | "ecto": {:hex, :ecto, "2.0.2", "b02331c1f20bbe944dbd33c8ecd8f1ccffecc02e344c4471a891baf3a25f5406", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:db_connection, "~> 1.0-rc.2", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}]},
12 | "ex_admin": {:git, "https://github.com/smpallen99/ex_admin.git", "5593da469c00c5146166f34ba6ff3aa0cadf34e6", []},
13 | "ex_aws": {:hex, :ex_aws, "0.4.19", "84284a2ce936b1aed3d6db4dac4ccf28105f2b617201194b2804d737cd0ef818", [:mix], [{:jsx, "~> 2.5", [hex: :jsx, optional: true]}, {:httpotion, "~> 2.0", [hex: :httpotion, optional: true]}, {:poison, "~> 1.2 or ~> 2.0", [hex: :poison, optional: true]}, {:httpoison, "~> 0.8", [hex: :httpoison, optional: true]}, {:sweet_xml, "~> 0.5", [hex: :sweet_xml, optional: true]}]},
14 | "ex_form": {:git, "https://github.com/smpallen99/ex_form.git", "6d08d449b112f89e48279daeca74b2b42a16fdda", []},
15 | "ex_queb": {:hex, :ex_queb, "0.1.2", "a7bea897c490875a716042675cec4013b5075de083c26ec1b7f0e0fb5144f5a1", [:mix], [{:ecto, "~> 1.1", [hex: :ecto, optional: false]}]},
16 | "exactor": {:hex, :exactor, "2.2.0", "2a7418b82d974fe8276edd62c1facf4a9dc06339cdf11b5dcd10359e107fe5c3", [:mix], []},
17 | "factory_girl_elixir": {:hex, :factory_girl_elixir, "0.1.1", "2c2b41111a409cf74131cd0f651fe65d77eb60d4b45775192ecde9820dc59ac5", [:mix], []},
18 | "faker": {:hex, :faker, "0.6.0", "2d2ff0879d6b10fab5fb47eb2c1149b811e4af780a17022aa249deb3a7156d2b", [:mix], []},
19 | "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
20 | "gettext": {:hex, :gettext, "0.11.0", "80c1dd42d270482418fa158ec5ba073d2980e3718bacad86f3d4ad71d5667679", [:mix], []},
21 | "hackney": {:hex, :hackney, "1.6.1", "ddd22d42db2b50e6a155439c8811b8f6df61a4395de10509714ad2751c6da817", [:rebar3], [{:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:certifi, "0.4.0", [hex: :certifi, optional: false]}]},
22 | "httpoison": {:hex, :httpoison, "0.9.0", "68187a2daddfabbe7ca8f7d75ef227f89f0e1507f7eecb67e4536b3c516faddb", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]},
23 | "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []},
24 | "inflex": {:hex, :inflex, "1.7.0", "4466a34b7d8e871d8164619ba0f3b8410ec782e900f0ae1d3d27a5875a29532e", [:mix], []},
25 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
26 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
27 | "number": {:hex, :number, "0.4.2", "027fc6b03d17d3ccd4beea599c35503ed81901284749048ea2f4519ec877727a", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, optional: true]}]},
28 | "phoenix": {:hex, :phoenix, "1.2.0", "1bdeb99c254f4c534cdf98fd201dede682297ccc62fcac5d57a2627c3b6681fb", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
29 | "phoenix_ecto": {:hex, :phoenix_ecto, "3.0.0", "b947aaf03d076f5b1448f87828f22fb7710478ee38455c67cc3fe8e9a4dfd015", [:mix], [{:ecto, "~> 2.0.0-rc", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.6", [hex: :phoenix_html, optional: true]}]},
30 | "phoenix_haml": {:hex, :phoenix_haml, "0.2.2", "1fe8cf56cc3c8e7caa50da8eed909d0c1dd45b40f87f9248a0c177ad9221e69e", [:mix], [{:calliope, "~> 0.4.1", [hex: :calliope, optional: false]}, {:phoenix_html, "~> 2.3", [hex: :phoenix_html, optional: false]}, {:phoenix, "~> 1.1", [hex: :phoenix, optional: false]}]},
31 | "phoenix_html": {:hex, :phoenix_html, "2.6.2", "944a5e581b0d899e4f4c838a69503ebd05300fe35ba228a74439e6253e10e0c0", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
32 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.5", "829218c4152ba1e9848e2bf8e161fcde6b4ec679a516259442561d21fde68d0b", [:mix], [{:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}, {:fs, "~> 0.9.1", [hex: :fs, optional: false]}]},
33 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.0", "c31af4be22afeeebfaf246592778c8c840e5a1ddc7ca87610c41ccfb160c2c57", [:mix], []},
34 | "plug": {:hex, :plug, "1.1.6", "8927e4028433fcb859e000b9389ee9c37c80eb28378eeeea31b0273350bf668b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
35 | "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
36 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []},
37 | "postgrex": {:hex, :postgrex, "0.11.2", "139755c1359d3c5c6d6e8b1ea72556d39e2746f61c6ddfb442813c91f53487e8", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.0-rc", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]},
38 | "ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []},
39 | "scrivener": {:hex, :scrivener, "2.0.0", "38e59a1c37e989c40a22c019da245a86b257182aee45a074292c9038fab39678", [:mix], []},
40 | "scrivener_ecto": {:git, "https://github.com/drewolson/scrivener_ecto.git", "dd9b485c05cafafa90724a54c3fde07cc73acd96", []},
41 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []},
42 | "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.6"},
43 | "xain": {:hex, :xain, "0.6.0", "5b61cfe3ffc17904759ee30a699f9e0b1aefd943e996ee4cafea76e5b2f59e3a", [:mix], []}}
44 |
--------------------------------------------------------------------------------
/priv/repo/seeds.exs:
--------------------------------------------------------------------------------
1 | # Script for populating the database. You can run it as:
2 | #
3 | # mix run priv/repo/seeds.exs
4 | #
5 | # Inside the script, you can read and write to any of your
6 | # repositories directly:
7 | #
8 | # ExAdminDemo.Repo.insert!(%ExAdminDemo.SomeModel{})
9 | #
10 | # We recommend using the bang functions (`insert!`, `update!`
11 | # and so on) as they will fail if something goes wrong.
12 |
13 | alias ExAdminDemo.Product
14 | alias ExAdminDemo.User
15 | alias ExAdminDemo.Repo
16 | alias ExAdminDemo.LineItem
17 | alias ExAdminDemo.Order
18 | alias Ecto.DateTime, as: Dt
19 | import Ecto.Query
20 |
21 | Faker.start
22 |
23 | require Logger
24 |
25 | Repo.delete_all LineItem
26 | Repo.delete_all Order
27 | Repo.delete_all Product
28 | Repo.delete_all User
29 |
30 | Repo.insert!(%User{username: "admin", email: "admin@example.com"})
31 |
32 | # field :title, :string
33 | # field :description, :string
34 | # field :author, :string
35 | # field :price, :decimal
36 | # field :featured, :boolean, default: false
37 | # field :available_on, Ecto.Date
38 | # field :image_file_name, :string
39 |
40 | img = fn(fname) ->
41 | %{
42 | file_name: fname,
43 | updated_at: Ecto.DateTime.cast!("2016-03-17T00:05:01Z"),
44 | inserted_at: Ecto.DateTime.cast!("2016-03-17T00:05:01Z")
45 | }
46 | end
47 |
48 | products = [
49 | ["Programming Phoenix Productive |> Reliable |> Fast", "Don’t accept the compromise between fast and beautiful: you can have it all. Phoenix creator Chris McCord, Elixir creator José Valim, and award-winning author Bruce Tate walk you through building an application that’s fast and reliable. At every step, you’ll learn from the Phoenix creators not just what to do, but why. Packed with insider insights, this definitive guide will be your constant companion in your journey from Phoenix novice to expert, as you build the next generation of web applications.",
50 | "Chris McCord, Bruce Tate, and José Valim", Decimal.new(23.00), true, Ecto.Date.cast!("2016-03-17"), img.("programming_phoenix.jpg")],
51 | ["Programming Elixir: Functional |> Concurrent |> Pragmatic |> Fun", "You want to explore functional programming, but are put off by the academic feel (tell me about monads just one more time). You know you need concurrent applications, but also know these are almost impossible to get right. Meet Elixir, a functional, concurrent language built on the rock-solid Erlang VM. Elixir’s pragmatic syntax and built-in support for metaprogramming will make you productive and keep you interested for the long haul. This book is the introduction to Elixir for experienced programmers.",
52 | "Dave Thomas", Decimal.new(48.00), true, Ecto.Date.cast!("2016-01-01"), img.("programming_elixir.jpg")],
53 | ["Metaprogramming Elixir", "Write code that writes code with Elixir macros. Macros make metaprogramming possible and define the language itself. In this book, you’ll learn how to use macros to extend the language with fast, maintainable code and share functionality in ways you never thought possible. You’ll discover how to extend Elixir with your own first-class features, optimize performance, and create domain-specific languages.",
54 | "Chris McCord", Decimal.new(27.00), true, Ecto.Date.cast!("2016-02-14"), img.("meta_programming_elixir.jpg")],
55 | ["Elixir in Action", "Elixir in Action teaches you to apply the new Elixir programming language to practical problems associated with scalability, concurrency, fault tolerance, and high availability.",
56 | "Saša Jurić", Decimal.new(40.07), false, Ecto.Date.cast!("2016-02-14"), img.("elixir_in_action.jpg")],
57 | ["Introducing Elixir: Getting Started in Functional Programming", "Elixir is an excellent language if you want to learn about functional programming, and with this hands-on introduction, you’ll discover just how powerful and fun Elixir can be. This language combines the robust functional programming of Erlang with a syntax similar to Ruby, and includes powerful features for metaprogramming.",
58 | " Simon St. Laurent, J. David Eisenberg", Decimal.new(24.99), false, Ecto.Date.cast!("2016-02-15"), img.("introducting_elixir.jpg")],
59 | ["Learning Elixir", "Elixir, based on Erlang's virtual machine and ecosystem, makes it easier to achieve scalability, concurrency, fault tolerance, and high availability goals that are pursued by developers using any programming language or programming paradigm. Elixir is a modern programming language that utilizes the benefits offered by Erlang VM without really incorporating the complex syntaxes of Erlang.",
60 | " Kenny Ballou", Decimal.new(27.99), false, Ecto.Date.cast!("2016-02-18"), img.("learing_elixir.jpg")],
61 | ["Programming Erlang: Software for a Concurrent World (Pragmatic Programmers)", "A multi-user game, web site, cloud application, or networked database can have thousands of users all interacting at the same time. You need a powerful, industrial-strength tool to handle the really hard problems inherent in parallel, concurrent environments. You need Erlang. In this second edition of the bestselling Programming Erlang, you'll learn how to write parallel programs that scale effortlessly on multicore systems.",
62 | "Joe Armstrong", Decimal.new(39.18), true, Ecto.Date.cast!("2016-02-19"), img.("programming_erlang.jpg")],
63 | ["Designing for Scalability with Erlang/OTP: Implement Robust, Available, Fault-Tolerant Systems", "Design and build complex, scalable commercial-grade systems with the Open Telecom Platform (OTP), the open source system developed by Ericsson and written in Erlang. With this hands-on book, you’ll learn how to apply OTP libraries and techniques to develop concurrent, fault-tolerant systems with no single point of failure.",
64 | "Francesco Cesarini, Steve Vinoski", Decimal.new(37.49), false, Ecto.Date.cast!("2016-01-14"), img.("designing_for_scalability.jpg")],
65 | # ["Erlang Programming", "This book is an in-depth introduction to Erlang, a programming language ideal for any situation where concurrency, fault tolerance, and fast response is essential. Erlang is gaining widespread adoption with the advent of multi-core processors and their new scalable approach to concurrency. With this guide you'll learn how to write complex concurrent programs in Erlang, regardless of your programming background or experience.",
66 | # "Francesco Cesarini, Simon Thompson", Decimal.new(39.70), false, Ecto.Date.cast!("2016-01-04"), nil],
67 | ["Learn You Some Erlang for Great Good!: A Beginner's Guide", "Erlang is the language of choice for programmers who want to write robust, concurrent applications, but its strange syntax and functional design can intimidate the uninitiated. Luckily, there's a new weapon in the battle against Erlang-phobia: Learn You Some Erlang for Great Good!",
68 | " Fred Hebert", Decimal.new(37.95), false, Ecto.Date.cast!("2016-03-14"), img.("learn_you_some_erlang.jpg")],
69 | ["Erlang and OTP in Action", "Concurrent programming has become a required discipline for all programmers. Multi-core processors and the increasing demand for maximum performance and scalability in mission-critical applications have renewed interest in functional languages like Erlang that are designed to handle concurrent programming. Erlang, and the OTP platform, make it possible to deliver more robust applications that satisfy rigorous uptime and performance requirements.",
70 | "Martin Logan, Eric Merritt, Richard Carlsson", Decimal.new(42.05), false, Ecto.Date.cast!("2016-04-04"), img.("erlang_and_otp_in_action.jpg")],
71 | ["Introducing Erlang: Getting Started in Functional Programming", "If you’re new to Erlang, its functional style can seem difficult, but with help from this hands-on introduction, you’ll scale the learning curve and discover how enjoyable, powerful, and fun this language can be.",
72 | "Simon St. Laurent", Decimal.new(19.99), false, Ecto.Date.cast!("2016-04-01"), img.("introducting_erlang.jpg")],
73 | ["Building Web Applications with Erlang: Working with REST and Web Sockets on Yaws", "Why choose Erlang for web applications? Discover the answer hands-on by building a simple web service with this book. If you’re an experienced web developer who knows basic Erlang, you’ll learn how to work with REST, dynamic content, web sockets, and concurrency through several examples. In the process, you’ll see first-hand that Erlang is ideal for building business-critical services.",
74 | "Zachary Kessin", Decimal.new(15.84), false, Ecto.Date.cast!("2015-02-14"), img.("building_web_apps.jpg")],
75 | ["Exercises for Programmers", "When you write software, you need to be at the top of your game. Great programmers practice to keep their skills sharp. Get sharp and stay sharp with more than fifty practice exercises rooted in real-world scenarios. If you’re a new programmer, these challenges will help you learn what you need to break into the field, and if you’re a seasoned pro, you can use these exercises to learn that hot new language for your next gig.",
76 | "Brian P. Hogan", Decimal.new(24.00), false, Ecto.Date.cast!("2015-01-17"), img.("exercises_for_programmers.jpg")],
77 | ["Seven More Languages in Seven Weeks", "Great programmers aren’t born—they’re made. The industry is moving from object-oriented languages to functional languages, and you need to commit to radical improvement. New programming languages arm you with the tools and idioms you need to refine your craft. While other language primers take you through basic installation and “Hello, World,” we aim higher. Each language in Seven More Languages in Seven Weeks will take you on a step-by-step journey through the most important paradigms of our time. You’ll learn seven exciting languages: Lua, Factor, Elixir, Elm, Julia, MiniKanren, and Idris.",
78 | "Bruce Tate, Fred Daoud, Jack Moffitt, Ian Dees", Decimal.new(38.00), false, Ecto.Date.cast!("2015-01-15"), img.("seven_more_languages.jpg")],
79 | ["Seven Web Frameworks in Seven Weeks", "Whether you need a new tool or just inspiration, Seven Web Frameworks in Seven Weeks explores modern options, giving you a taste of each with ideas that will help you create better apps. You’ll see frameworks that leverage modern programming languages, employ unique architectures, live client-side instead of server-side, or embrace type systems. You’ll see everything from familiar Ruby and JavaScript to the more exotic Erlang, Haskell, and Clojure.",
80 | "Jack Moffitt, Fred Daoud", Decimal.new(38.00), false, Ecto.Date.cast!("2016-02-14"), img.("seven_web_frameworks.jpg")],
81 | ["Seven Concurrency Models in Seven Weeks", "Your software needs to leverage multiple cores, handle thousands of users and terabytes of data, and continue working in the face of both hardware and software failure. Concurrency and parallelism are the keys, and Seven Concurrency Models in Seven Weeks equips you for this new world. See how emerging technologies such as actors and functional programming address issues with traditional threads and locks development. Learn how to exploit the parallelism in your computer’s GPU and leverage clusters of machines with MapReduce and Stream Processing. And do it all with the confidence that comes from using tools that help you write crystal clear, high-quality code.",
82 | "Paul Butcher", Decimal.new(38.00), false, Ecto.Date.cast!("2016-02-14"), img.("seven_concurrency.jpg")],
83 | ]
84 | Repo.delete_all Product
85 | for p <- products do
86 | list = [:title, :description, :author, :price, :featured, :available_on, :image_file_name]
87 | |> Enum.zip(p)
88 |
89 | struct(%Product{}, list)
90 | |> Repo.insert!
91 | end
92 |
93 | nb_users = 100
94 | for n <- 1..nb_users do
95 | %User{
96 | username: Faker.Internet.user_name <> Integer.to_string(n),
97 | email: Faker.Internet.email |> String.replace("@", "#{n}@"),
98 | }
99 | |> Repo.insert!
100 | end
101 |
102 | nb_orders = 300
103 |
104 | users = Repo.all User
105 | products = Repo.all Product
106 |
107 | for _i <- 1..nb_orders do
108 | u = Enum.random users
109 | order = Repo.insert! %Order{user_id: u.id}
110 |
111 | nb_items = :rand.uniform(9) + 1
112 | for _i <- 1..nb_items do
113 | prod = Enum.random products
114 | Repo.insert! %LineItem{product_id: prod.id, order_id: order.id, price: prod.price}
115 | end
116 |
117 | id = order.id
118 | order = where(Order, [o], o.id == ^id)
119 | |> preload([:line_items])
120 | |> Repo.one!
121 |
122 | Order.recalculate_price!(order)
123 | if :rand.uniform(100) < 90, do: Order.checkout!(order)
124 | end
125 |
--------------------------------------------------------------------------------