├── test ├── fixtures │ ├── hello.txt │ ├── templates │ │ ├── path.html.eex │ │ ├── user │ │ │ ├── show.json.exs │ │ │ ├── profiles │ │ │ │ └── admin.html.eex │ │ │ ├── index.html.eex │ │ │ └── render_template.html.eex │ │ ├── safe.html.eex │ │ ├── show.html.eex │ │ └── layout │ │ │ └── app.html.eex │ ├── digest │ │ ├── priv │ │ │ ├── static │ │ │ │ ├── foo.css │ │ │ │ ├── app.js │ │ │ │ ├── phoenix.png │ │ │ │ ├── app.js.map │ │ │ │ ├── images │ │ │ │ │ └── relative.png │ │ │ │ ├── css │ │ │ │ │ └── app.css │ │ │ │ └── manifest.json │ │ │ └── output │ │ │ │ ├── foo-d978852bea6530fcd197b5445ed008fd.css │ │ │ │ └── foo-288ea8c7954498e65663c817382eeac4.css │ │ └── cleaner │ │ │ ├── cache_manifest.json │ │ │ └── latest_not_most_recent_cache_manifest.json │ ├── old_cache_manifest.json │ ├── cache_manifest.json │ ├── cache_manifest_upgrade.json │ ├── ssl │ │ ├── key.pem │ │ └── cert.pem │ └── views.exs ├── phoenix │ ├── endpoint │ │ └── watcher_test.exs │ ├── transports │ │ ├── websocket_serializer_test.exs │ │ └── long_poll_serializer_test.exs │ ├── code_reloader_test.exs │ ├── param_test.exs │ └── naming_test.exs ├── mix │ └── tasks │ │ ├── phx.digest.clean_test.exs │ │ ├── phx.gen.presence_test.exs │ │ ├── phx.gen.secret_test.exs │ │ ├── phoenix.gen.presence_test.exs │ │ ├── phx.digest_test.exs │ │ └── phx.routes_test.exs ├── test_helper.exs └── support │ ├── router_helper.exs │ └── http_client.exs ├── .babelrc ├── installer ├── test │ ├── test_helper.exs │ ├── phx_new_ecto_test.exs │ └── phx_new_web_test.exs ├── templates │ ├── phx_umbrella │ │ ├── README.md │ │ ├── apps │ │ │ ├── app_name │ │ │ │ ├── config │ │ │ │ │ ├── dev.exs │ │ │ │ │ ├── test.exs │ │ │ │ │ ├── prod.exs │ │ │ │ │ ├── config.exs │ │ │ │ │ └── prod.secret.exs │ │ │ │ ├── README.md │ │ │ │ ├── test │ │ │ │ │ └── test_helper.exs │ │ │ │ ├── lib │ │ │ │ │ └── app_name │ │ │ │ │ │ └── application.ex │ │ │ │ └── mix.exs │ │ │ └── app_name_web │ │ │ │ ├── test │ │ │ │ └── test_helper.exs │ │ │ │ ├── config │ │ │ │ ├── test.exs │ │ │ │ ├── prod.secret.exs │ │ │ │ ├── config.exs │ │ │ │ └── dev.exs │ │ │ │ ├── gitignore │ │ │ │ ├── lib │ │ │ │ ├── app_name │ │ │ │ │ └── application.ex │ │ │ │ └── app_name.ex │ │ │ │ ├── README.md │ │ │ │ └── mix.exs │ │ ├── gitignore │ │ ├── config │ │ │ ├── prod.exs │ │ │ ├── test.exs │ │ │ ├── dev.exs │ │ │ └── config.exs │ │ └── mix.exs │ ├── static │ │ ├── app.css │ │ ├── robots.txt │ │ ├── bare │ │ │ ├── app.js │ │ │ └── gitignore │ │ └── brunch │ │ │ ├── package.json │ │ │ ├── gitignore │ │ │ ├── app.js │ │ │ └── brunch-config.js │ ├── phx_assets │ │ ├── app.css │ │ ├── robots.txt │ │ ├── bare │ │ │ ├── app.js │ │ │ └── gitignore │ │ └── brunch │ │ │ ├── package.json │ │ │ ├── gitignore │ │ │ ├── app.js │ │ │ └── brunch-config.js │ ├── ecto │ │ ├── repo.ex │ │ ├── seeds.exs │ │ └── model_case.ex │ ├── new │ │ ├── web │ │ │ ├── views │ │ │ │ ├── page_view.ex │ │ │ │ ├── layout_view.ex │ │ │ │ ├── error_view.ex │ │ │ │ └── error_helpers.ex │ │ │ ├── controllers │ │ │ │ └── page_controller.ex │ │ │ ├── router.ex │ │ │ ├── gettext.ex │ │ │ ├── templates │ │ │ │ ├── page │ │ │ │ │ └── index.html.eex │ │ │ │ └── layout │ │ │ │ │ └── app.html.eex │ │ │ └── channels │ │ │ │ └── user_socket.ex │ │ ├── test │ │ │ ├── test_helper.exs │ │ │ ├── views │ │ │ │ ├── layout_view_test.exs │ │ │ │ ├── page_view_test.exs │ │ │ │ └── error_view_test.exs │ │ │ ├── controllers │ │ │ │ └── page_controller_test.exs │ │ │ └── support │ │ │ │ ├── channel_case.ex │ │ │ │ └── conn_case.ex │ │ ├── priv │ │ │ └── static │ │ │ │ └── robots.txt │ │ ├── config │ │ │ ├── test.exs │ │ │ ├── prod.secret.exs │ │ │ ├── config.exs │ │ │ └── dev.exs │ │ ├── README.md │ │ ├── lib │ │ │ ├── app_name.ex │ │ │ └── app_name │ │ │ │ └── endpoint.ex │ │ └── mix.exs │ ├── phx_web │ │ ├── views │ │ │ ├── layout_view.ex │ │ │ ├── page_view.ex │ │ │ ├── error_view.ex │ │ │ └── error_helpers.ex │ │ ├── controllers │ │ │ └── page_controller.ex │ │ ├── router.ex │ │ ├── templates │ │ │ ├── page │ │ │ │ └── index.html.eex │ │ │ └── layout │ │ │ │ └── app.html.eex │ │ ├── channels │ │ │ └── user_socket.ex │ │ └── endpoint.ex │ ├── phx_single │ │ ├── test │ │ │ └── test_helper.exs │ │ ├── config │ │ │ ├── test.exs │ │ │ ├── prod.secret.exs │ │ │ ├── config.exs │ │ │ └── dev.exs │ │ ├── README.md │ │ ├── lib │ │ │ └── app_name │ │ │ │ ├── application.ex │ │ │ │ └── web │ │ │ │ └── web.ex │ │ └── mix.exs │ ├── phx_test │ │ ├── views │ │ │ ├── page_view_test.exs │ │ │ ├── layout_view_test.exs │ │ │ └── error_view_test.exs │ │ ├── controllers │ │ │ └── page_controller_test.exs │ │ └── support │ │ │ ├── channel_case.ex │ │ │ └── conn_case.ex │ ├── phx_ecto │ │ ├── repo.ex │ │ ├── seeds.exs │ │ └── data_case.ex │ └── phx_gettext │ │ └── gettext.ex ├── README.md ├── mix.exs └── lib │ ├── mix │ └── tasks │ │ ├── local.phx.ex │ │ ├── local.phoenix.ex │ │ ├── phx.new.web.ex │ │ └── phx.new.ecto.ex │ └── phx_new │ ├── project.ex │ ├── ecto.ex │ └── umbrella.ex ├── logo.png ├── priv ├── static │ ├── favicon.ico │ └── phoenix.png └── templates │ ├── phoenix.gen.html │ ├── view.ex │ ├── new.html.eex │ ├── edit.html.eex │ ├── show.html.eex │ ├── form.html.eex │ └── index.html.eex │ ├── phx.gen.context │ ├── context_test.exs │ └── context.ex │ ├── phx.gen.html │ ├── view.ex │ ├── new.html.eex │ ├── edit.html.eex │ ├── show.html.eex │ ├── form.html.eex │ └── index.html.eex │ ├── phoenix.gen.model │ ├── model_test.exs │ ├── migration.exs │ └── model.ex │ ├── phoenix.gen.json │ ├── view.ex │ ├── changeset_view.ex │ └── controller.ex │ ├── phx.gen.embedded │ └── embedded_schema.ex │ ├── phx.gen.json │ ├── changeset_view.ex │ ├── fallback_controller.ex │ ├── view.ex │ └── controller.ex │ ├── phx.gen.schema │ ├── migration.exs │ └── schema.ex │ ├── phx.gen.channel │ ├── channel.ex │ └── channel_test.exs │ └── phoenix.gen.channel │ ├── channel.ex │ └── channel_test.exs ├── .gitignore ├── config └── config.exs ├── lib ├── phoenix │ ├── template │ │ ├── exs_engine.ex │ │ ├── html.ex │ │ ├── engine.ex │ │ └── eex_engine.ex │ ├── transports │ │ ├── serializer.ex │ │ ├── long_poll_serializer.ex │ │ └── websocket_serializer.ex │ ├── code_reloader │ │ └── proxy.ex │ ├── router │ │ └── console_formatter.ex │ ├── endpoint │ │ ├── handler.ex │ │ └── watcher.ex │ └── exceptions.ex ├── mix │ ├── tasks │ │ ├── phoenix.gen.secret.ex │ │ ├── phoenix.server.ex │ │ ├── phoenix.routes.ex │ │ ├── phx.server.ex │ │ ├── phx.gen.secret.ex │ │ ├── compile.phoenix.ex │ │ ├── phoenix.digest.ex │ │ ├── phoenix.gen.presence.ex │ │ ├── phoenix.gen.channel.ex │ │ ├── phx.gen.presence.ex │ │ ├── phx.routes.ex │ │ ├── phx.digest.ex │ │ ├── phx.gen.channel.ex │ │ ├── phx.gen.embedded.ex │ │ └── phx.digest.clean.ex │ └── phoenix │ │ └── context.ex └── phoenix.ex ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── package.json ├── LICENSE.md ├── RELEASE.md ├── brunch-config.js ├── CODE_OF_CONDUCT.md └── mix.lock /test/fixtures/hello.txt: -------------------------------------------------------------------------------- 1 | world -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["es2015"] } 2 | -------------------------------------------------------------------------------- /test/fixtures/templates/path.html.eex: -------------------------------------------------------------------------------- 1 | path 2 | -------------------------------------------------------------------------------- /installer/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /test/fixtures/templates/user/show.json.exs: -------------------------------------------------------------------------------- 1 | %{foo: "bar"} 2 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/README.md: -------------------------------------------------------------------------------- 1 | # <%= root_app_module %> 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smpallen99/phoenix/master/logo.png -------------------------------------------------------------------------------- /test/fixtures/templates/safe.html.eex: -------------------------------------------------------------------------------- 1 | Raw <%= {:safe, @message} %> 2 | -------------------------------------------------------------------------------- /test/fixtures/templates/user/profiles/admin.html.eex: -------------------------------------------------------------------------------- 1 | admin profile 2 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/static/foo.css: -------------------------------------------------------------------------------- 1 | .foo { background-color: red } 2 | -------------------------------------------------------------------------------- /test/fixtures/templates/user/index.html.eex: -------------------------------------------------------------------------------- 1 | <%= escaped_title @title %> 2 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | -------------------------------------------------------------------------------- /installer/templates/static/app.css: -------------------------------------------------------------------------------- 1 | /* This file is for your main application css. */ -------------------------------------------------------------------------------- /installer/templates/phx_assets/app.css: -------------------------------------------------------------------------------- 1 | /* This file is for your main application css. */ -------------------------------------------------------------------------------- /test/fixtures/templates/user/render_template.html.eex: -------------------------------------------------------------------------------- 1 | rendered template for <%= @name %> 2 | -------------------------------------------------------------------------------- /test/fixtures/old_cache_manifest.json: -------------------------------------------------------------------------------- 1 | {"foo.css":"foo-d978852bea6530fcd197b5445ed008fd.css"} 2 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smpallen99/phoenix/master/priv/static/favicon.ico -------------------------------------------------------------------------------- /priv/static/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smpallen99/phoenix/master/priv/static/phoenix.png -------------------------------------------------------------------------------- /test/fixtures/digest/priv/output/foo-d978852bea6530fcd197b5445ed008fd.css: -------------------------------------------------------------------------------- 1 | .foo { background-color: red } 2 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/output/foo-288ea8c7954498e65663c817382eeac4.css: -------------------------------------------------------------------------------- 1 | .foo { background-color: blue } 2 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/README.md: -------------------------------------------------------------------------------- 1 | # <%= app_module %> 2 | 3 | **TODO: Add description** 4 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.html/view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>View do 2 | use <%= base %>.Web, :view 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/templates/show.html.eex: -------------------------------------------------------------------------------- 1 |
Show! <%= @message %>
2 | <% :ok # Template is still valid %> 3 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | import_config "prod.secret.exs" 4 | -------------------------------------------------------------------------------- /installer/templates/ecto/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Repo do 2 | use Ecto.Repo, otp_app: :<%= app_name %> 3 | end 4 | -------------------------------------------------------------------------------- /installer/templates/new/web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.PageView do 2 | use <%= app_module %>.Web, :view 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/static/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | console.log('Hello World!'); 3 | })(); 4 | //# sourceMappingURL=app.js.map 5 | -------------------------------------------------------------------------------- /installer/templates/new/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start 2 | <%= if ecto do %> 3 | <%= adapter_config[:test_setup_all] %> 4 | <% end %> 5 | -------------------------------------------------------------------------------- /installer/templates/new/web/views/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.LayoutView do 2 | use <%= app_module %>.Web, :view 3 | end 4 | -------------------------------------------------------------------------------- /installer/templates/phx_web/views/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.LayoutView do 2 | use <%= web_namespace %>, :view 3 | end 4 | -------------------------------------------------------------------------------- /installer/templates/phx_web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.PageView do 2 | use <%= web_namespace %>, :view 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/static/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smpallen99/phoenix/master/test/fixtures/digest/priv/static/phoenix.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | /doc 4 | /tmp 5 | /node_modules 6 | 7 | /installer/_build 8 | /installer/tmp 9 | 10 | erl_crash.dump 11 | -------------------------------------------------------------------------------- /installer/templates/phx_single/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | <%= if ecto do %> 3 | <%= adapter_config[:test_setup_all] %> 4 | <% end %> 5 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # Do not print debug messages in production 4 | config :logger, level: :info 5 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # Print only warnings and errors during test 4 | config :logger, level: :warn 5 | -------------------------------------------------------------------------------- /installer/templates/new/test/views/layout_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.LayoutViewTest do 2 | use <%= app_module %>.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /installer/templates/new/test/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.PageViewTest do 2 | use <%= app_module %>.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/static/app.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.js","sources":["app.js"],"names":["console","log"],"mappings":"CAAA,WACEA,QAAQC,IAAI"} 2 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/static/images/relative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smpallen99/phoenix/master/test/fixtures/digest/priv/static/images/relative.png -------------------------------------------------------------------------------- /installer/templates/phx_test/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.PageViewTest do 2 | use <%= web_namespace %>.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | <%= if ecto do %> 3 | <%= adapter_config[:test_setup_all] %> 4 | <% end %> 5 | -------------------------------------------------------------------------------- /installer/templates/phx_test/views/layout_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.LayoutViewTest do 2 | use <%= web_namespace %>.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | <%= if ecto do %> 3 | <%= adapter_config[:test_setup_all] %> 4 | <% end %> 5 | -------------------------------------------------------------------------------- /test/fixtures/templates/layout/app.html.eex: -------------------------------------------------------------------------------- 1 | 2 | <%= @title || default_title() %> 3 | <%= render @view_module, @view_template, assigns %> 4 | 5 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.context/context_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect context.module %>Test do 2 | use <%= inspect context.base_module %>.DataCase 3 | 4 | alias <%= inspect context.module %> 5 | end 6 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # Disable colors during tests. 4 | config :logger, :console, colors: [enabled: false] 5 | 6 | # Use higher stacktrace depth. 7 | config :phoenix, :stacktrace_depth, 20 8 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.html/view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>View do 2 | use <%= inspect context.web_module %>, :view 3 | end 4 | -------------------------------------------------------------------------------- /installer/README.md: -------------------------------------------------------------------------------- 1 | ## mix phx.new 2 | 3 | Provides `phx.new` installer as an archive. To build and install it locally: 4 | 5 | $ cd installer 6 | $ MIX_ENV=prod mix archive.build 7 | $ mix archive.install 8 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | <%= if ecto do %>config :<%= app_name %>, ecto_repos: [<%= app_module %>.Repo]<% end %> 4 | 5 | import_config "#{Mix.env}.exs" 6 | -------------------------------------------------------------------------------- /installer/templates/new/web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.PageController do 2 | use <%= app_module %>.Web, :controller 3 | 4 | def index(conn, _params) do 5 | render conn, "index.html" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /installer/templates/phx_web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.PageController do 2 | use <%= web_namespace %>, :controller 3 | 4 | def index(conn, _params) do 5 | render conn, "index.html" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /installer/templates/phx_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 | -------------------------------------------------------------------------------- /installer/templates/static/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/templates/phx.gen.context/context.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect context.module %> do 2 | @moduledoc """ 3 | The boundary for the <%= context.name %> system. 4 | """ 5 | 6 | import Ecto.Query, warn: false 7 | alias <%= inspect schema.repo %> 8 | end 9 | -------------------------------------------------------------------------------- /installer/templates/new/priv/static/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 | -------------------------------------------------------------------------------- /installer/templates/static/bare/app.js: -------------------------------------------------------------------------------- 1 | // for phoenix_html support, including form and button helpers 2 | // copy the following scripts into your javascript bundle: 3 | // * https://raw.githubusercontent.com/phoenixframework/phoenix_html/v2.3.0/priv/static/phoenix_html.js -------------------------------------------------------------------------------- /installer/templates/phx_assets/bare/app.js: -------------------------------------------------------------------------------- 1 | // for phoenix_html support, including form and button helpers 2 | // copy the following scripts into your javascript bundle: 3 | // * https://raw.githubusercontent.com/phoenixframework/phoenix_html/v2.3.0/priv/static/phoenix_html.js -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/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 :<%= web_app_name %>, <%= endpoint_module %>, 6 | http: [port: 4001], 7 | server: false 8 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.html/new.html.eex: -------------------------------------------------------------------------------- 1 |

New <%= template_singular %>

2 | 3 | <%%= render "form.html", changeset: @changeset, 4 | action: <%= singular %>_path(@conn, :create) %> 5 | 6 | <%%= link "Back", to: <%= singular %>_path(@conn, :index) %> 7 | -------------------------------------------------------------------------------- /installer/templates/new/test/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.PageControllerTest do 2 | use <%= app_module %>.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 | -------------------------------------------------------------------------------- /installer/templates/phx_test/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.PageControllerTest do 2 | use <%= web_namespace %>.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 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.html/new.html.eex: -------------------------------------------------------------------------------- 1 |

New <%= schema.human_singular %>

2 | 3 | <%%= render "form.html", changeset: @changeset, 4 | action: <%= schema.singular %>_path(@conn, :create) %> 5 | 6 | <%%= link "Back", to: <%= schema.singular %>_path(@conn, :index) %> 7 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.html/edit.html.eex: -------------------------------------------------------------------------------- 1 |

Edit <%= template_singular %>

2 | 3 | <%%= render "form.html", changeset: @changeset, 4 | action: <%= singular %>_path(@conn, :update, @<%= singular %>) %> 5 | 6 | <%%= link "Back", to: <%= singular %>_path(@conn, :index) %> 7 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.html/edit.html.eex: -------------------------------------------------------------------------------- 1 |

Edit <%= schema.human_singular %>

2 | 3 | <%%= render "form.html", changeset: @changeset, 4 | action: <%= schema.singular %>_path(@conn, :update, @<%= schema.singular %>) %> 5 | 6 | <%%= link "Back", to: <%= schema.singular %>_path(@conn, :index) %> 7 | -------------------------------------------------------------------------------- /lib/phoenix/template/exs_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Template.ExsEngine do 2 | @moduledoc """ 3 | The Phoenix engine that handles the `.exs` extension. 4 | """ 5 | 6 | @behaviour Phoenix.Template.Engine 7 | 8 | def compile(path, _name) do 9 | path 10 | |> File.read! 11 | |> Code.string_to_quoted!(file: path) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /installer/templates/new/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 :<%= app_name %>, <%= app_module %>.Endpoint, 6 | http: [port: 4001], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | -------------------------------------------------------------------------------- /installer/templates/phx_ecto/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Repo do 2 | use Ecto.Repo, otp_app: :<%= app_name %> 3 | 4 | @doc """ 5 | Dynamically loads the repository url from the 6 | DATABASE_URL environment variable. 7 | """ 8 | def init(_, opts) do 9 | {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /installer/templates/phx_single/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 :<%= app_name %>, <%= endpoint_module %>, 6 | http: [port: 4001], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # Do not include metadata nor timestamps in development logs 4 | config :logger, :console, format: "[$level] $message\n" 5 | 6 | # Set a higher stacktrace during development. Avoid configuring such 7 | # in production as building large stacktraces may be expensive. 8 | config :phoenix, :stacktrace_depth, 20 9 | -------------------------------------------------------------------------------- /test/fixtures/cache_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "digests": { 3 | "foo-d978852bea6530fcd197b5445ed008fd.css": { 4 | "logical_path": "foo.css", 5 | "mtime": 32132171, 6 | "size": 369053, 7 | "digest": "d978852bea6530fcd197b5445ed008fd" 8 | } 9 | }, 10 | "latest": { 11 | "foo.css": "foo-d978852bea6530fcd197b5445ed008fd.css" 12 | }, 13 | "version": 1 14 | } 15 | -------------------------------------------------------------------------------- /installer/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Phx.New.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :phx_new, 6 | start_permanent: Mix.env == :prod, 7 | version: "1.3.0-rc.1", 8 | elixir: "~> 1.3 or ~> 1.4"] 9 | end 10 | 11 | # Configuration for the OTP application 12 | # 13 | # Type `mix help compile.app` for more information 14 | def application do 15 | [extra_applications: []] 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /installer/templates/phx_ecto/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 | # <%= app_module %>.Repo.insert!(%<%= app_module %>.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /installer/templates/ecto/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 | # <%= app_module %>.Repo.insert!(%<%= app_module %>.SomeModel{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will halt execution if something goes wrong. 12 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/config/prod.secret.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # In this file, we keep production configuration that 4 | # you likely want to automate and keep it away from 5 | # your version control system. 6 | # 7 | # You should document the content of this 8 | # file or create a script for recreating it, since it's 9 | # kept out of version control and might be hard to recover 10 | # or recreate for your teammates (or you later on). 11 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.html/show.html.eex: -------------------------------------------------------------------------------- 1 |

Show <%= template_singular %>

2 | 3 | 11 | 12 | <%%= link "Edit", to: <%= singular %>_path(@conn, :edit, @<%= singular %>) %> 13 | <%%= link "Back", to: <%= singular %>_path(@conn, :index) %> 14 | -------------------------------------------------------------------------------- /installer/templates/static/bare/gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | 7 | # Generated on crash by the VM 8 | erl_crash.dump 9 | 10 | # Files matching config/*.secret.exs pattern contain sensitive 11 | # data and you should not commit them into version control. 12 | # 13 | # Alternatively, you may comment the line below and commit the 14 | # secrets files as long as you replace their contents by environment 15 | # variables. 16 | /config/*.secret.exs -------------------------------------------------------------------------------- /lib/phoenix/template/html.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Template.HTML do 2 | @moduledoc """ 3 | The default HTML encoder that ships with Phoenix. 4 | 5 | It expects `{:safe, body}` as a safe response or 6 | body as a string which will be HTML escaped. 7 | """ 8 | 9 | @doc """ 10 | Encodes the HTML templates to iodata. 11 | """ 12 | def encode_to_iodata!({:safe, body}), do: body 13 | def encode_to_iodata!(body) when is_binary(body), do: Plug.HTML.html_escape(body) 14 | end 15 | -------------------------------------------------------------------------------- /test/fixtures/cache_manifest_upgrade.json: -------------------------------------------------------------------------------- 1 | { 2 | "digests": { 3 | "foo-abcdef.css": { 4 | "logical_path": "foo.css", 5 | "mtime": 32132171, 6 | "size": 369053, 7 | "digest": "abcdev" 8 | }, 9 | "foo-ghijkl.css": { 10 | "logical_path": "foo.css", 11 | "mtime": 32193492, 12 | "size": 372059, 13 | "digest": "abcdev" 14 | } 15 | }, 16 | "latest": { 17 | "foo.css": "foo-ghijkl.css" 18 | }, 19 | "version": 1 20 | } 21 | -------------------------------------------------------------------------------- /installer/templates/phx_assets/bare/gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | 7 | # Generated on crash by the VM 8 | erl_crash.dump 9 | 10 | # Files matching config/*.secret.exs pattern contain sensitive 11 | # data and you should not commit them into version control. 12 | # 13 | # Alternatively, you may comment the line below and commit the 14 | # secrets files as long as you replace their contents by environment 15 | # variables. 16 | /config/*.secret.exs 17 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.html/show.html.eex: -------------------------------------------------------------------------------- 1 |

Show <%= schema.human_singular %>

2 | 3 | 11 | 12 | <%%= link "Edit", to: <%= schema.singular %>_path(@conn, :edit, @<%= schema.singular %>) %> 13 | <%%= link "Back", to: <%= schema.singular %>_path(@conn, :index) %> 14 | -------------------------------------------------------------------------------- /test/phoenix/endpoint/watcher_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Endpoint.WatcherTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias Phoenix.Endpoint.Watcher 5 | import ExUnit.CaptureIO 6 | 7 | test "starts watching and writes to stdio" do 8 | assert capture_io(fn -> 9 | {:ok, pid} = Watcher.start_link("echo", ["hello"], cd: File.cwd!()) 10 | ref = Process.monitor(pid) 11 | assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 1000 12 | end) == "hello\n" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /installer/lib/mix/tasks/local.phx.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Local.Phx do 2 | use Mix.Task 3 | 4 | @url "https://github.com/phoenixframework/archives/raw/master/phx_new.ez" 5 | @shortdoc "Updates the Phoenix project generator locally" 6 | 7 | @moduledoc """ 8 | Updates the Phoenix project generator locally. 9 | 10 | mix local.phx 11 | 12 | Accepts the same command line options as `archive.install`. 13 | """ 14 | def run(args) do 15 | Mix.Task.run "archive.install", [@url | args] 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/mix/tasks/phoenix.gen.secret.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phoenix.Gen.Secret do 2 | @moduledoc """ 3 | Generates a secret and prints it to the terminal. 4 | 5 | mix phoenix.gen.secret [length] 6 | 7 | By default, mix phoenix.gen.secret generates a key 64 characters long. 8 | 9 | The minimum value for `length` is 32. 10 | """ 11 | use Mix.Task 12 | 13 | def run(args) do 14 | IO.puts :stderr, "mix phoenix.gen.secret is deprecated. Use phx.gen.secret instead." 15 | Mix.Tasks.Phx.Gen.Secret.run(args) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/static/css/app.css: -------------------------------------------------------------------------------- 1 | .absolute_path_logo { 2 | background-image: url("/phoenix.png"); 3 | } 4 | 5 | .relative_path_logo { 6 | background-image: url('../images/relative.png'); 7 | } 8 | 9 | .absolute_url_logo { 10 | background-image: url(http://www.phoenixframework.org/absolute.png); 11 | } 12 | 13 | .absolute_path_logo{background-image:url(/phoenix.png)} 14 | .relative_path_logo{background-image:url(../images/relative.png)} 15 | .absolute_url_logo{background-image:url(http://www.phoenixframework.org/absolute.png)} 16 | -------------------------------------------------------------------------------- /installer/templates/new/config/prod.secret.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # In this file, we keep production configuration that 4 | # you likely want to automate and keep it away from 5 | # your version control system. 6 | # 7 | # You should document the content of this 8 | # file or create a script for recreating it, since it's 9 | # kept out of version control and might be hard to recover 10 | # or recreate for your teammates (or you later on). 11 | config :<%= app_name %>, <%= app_module %>.Endpoint, 12 | secret_key_base: "<%= prod_secret_key_base %>" 13 | -------------------------------------------------------------------------------- /installer/templates/phx_single/config/prod.secret.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # In this file, we keep production configuration that 4 | # you likely want to automate and keep it away from 5 | # your version control system. 6 | # 7 | # You should document the content of this 8 | # file or create a script for recreating it, since it's 9 | # kept out of version control and might be hard to recover 10 | # or recreate for your teammates (or you later on). 11 | config :<%= app_name %>, <%= endpoint_module %>, 12 | secret_key_base: "<%= prod_secret_key_base %>" 13 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.model/model_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>Test do 2 | use <%= base %>.ModelCase 3 | 4 | alias <%= module %> 5 | 6 | @valid_attrs <%= inspect params %> 7 | @invalid_attrs %{} 8 | 9 | test "changeset with valid attributes" do 10 | changeset = <%= alias %>.changeset(%<%= alias %>{}, @valid_attrs) 11 | assert changeset.valid? 12 | end 13 | 14 | test "changeset with invalid attributes" do 15 | changeset = <%= alias %>.changeset(%<%= alias %>{}, @invalid_attrs) 16 | refute changeset.valid? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /installer/templates/phx_assets/brunch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": {}, 3 | "license": "MIT", 4 | "scripts": { 5 | "deploy": "brunch build --production", 6 | "watch": "brunch watch --stdin" 7 | }, 8 | "dependencies": { 9 | "phoenix": "file:<%= phoenix_brunch_path %>"<%= if html do %>, 10 | "phoenix_html": "file:<%= phoenix_html_brunch_path %>"<% end %> 11 | }, 12 | "devDependencies": { 13 | "babel-brunch": "6.0.6", 14 | "brunch": "2.10.7", 15 | "clean-css-brunch": "2.10.0", 16 | "uglify-js-brunch": "2.1.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.html/form.html.eex: -------------------------------------------------------------------------------- 1 | <%%= form_for @changeset, @action, fn f -> %> 2 | <%%= if @changeset.action do %> 3 |
4 |

Oops, something went wrong! Please check the errors below.

5 |
6 | <%% end %> 7 | <%= for {label, input, error} <- inputs, input do %> 8 |
9 | <%= label %> 10 | <%= input %> 11 | <%= error %> 12 |
13 | <% end %> 14 |
15 | <%%= submit "Submit", class: "btn btn-primary" %> 16 |
17 | <%% end %> 18 | -------------------------------------------------------------------------------- /lib/phoenix/template/engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Template.Engine do 2 | @moduledoc """ 3 | Specifies the API for adding custom template engines into Phoenix. 4 | 5 | Engines must implement the `compile/2` function, that receives 6 | the template file and the template name and outputs the template quoted 7 | expression: 8 | 9 | def compile(template_path, template_name) 10 | 11 | See `Phoenix.Template.EExEngine` for an example engine implementation. 12 | """ 13 | 14 | @callback compile(template_path :: binary, template_name :: binary) :: Macro.t 15 | end 16 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.html/form.html.eex: -------------------------------------------------------------------------------- 1 | <%%= form_for @changeset, @action, fn f -> %> 2 | <%%= if @changeset.action do %> 3 |
4 |

Oops, something went wrong! Please check the errors below.

5 |
6 | <%% end %> 7 | <%= for {label, input, error} <- inputs, input do %> 8 |
9 | <%= label %> 10 | <%= input %> 11 | <%= error %> 12 |
13 | <% end %> 14 |
15 | <%%= submit "Submit", class: "btn btn-primary" %> 16 |
17 | <%% end %> 18 | -------------------------------------------------------------------------------- /installer/lib/mix/tasks/local.phoenix.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Local.Phoenix do 2 | use Mix.Task 3 | 4 | @url "https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez" 5 | @shortdoc "Updates Phoenix locally" 6 | 7 | @moduledoc """ 8 | Updates Phoenix locally. 9 | 10 | mix local.phoenix 11 | 12 | Accepts the same command line options as `archive.install`. 13 | """ 14 | def run(args) do 15 | IO.puts :stderr, "mix local.phoenix is deprecated. Use local.phx instead." 16 | Mix.Task.run "archive.install", [@url | args] 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # In this file, we keep production configuration that 4 | # you likely want to automate and keep it away from 5 | # your version control system. 6 | # 7 | # You should document the content of this 8 | # file or create a script for recreating it, since it's 9 | # kept out of version control and might be hard to recover 10 | # or recreate for your teammates (or you later on). 11 | config :<%= web_app_name %>, <%= endpoint_module %>, 12 | secret_key_base: "<%= prod_secret_key_base %>" 13 | -------------------------------------------------------------------------------- /test/fixtures/digest/cleaner/cache_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "digests": { 3 | "app-1.css": { 4 | "logical_path": "app.css", 5 | "mtime": 32132171, 6 | "size": 369053, 7 | "digest": "1" 8 | }, 9 | "app-2.css": { 10 | "logical_path": "app.css", 11 | "mtime": 32132172, 12 | "size": 369053, 13 | "digest": "2" 14 | }, 15 | "app-3.css": { 16 | "logical_path": "app.css", 17 | "mtime": 32132173, 18 | "size": 369053, 19 | "digest": "3" 20 | } 21 | }, 22 | "latest": { 23 | "app.css": "app-3.css" 24 | }, 25 | "version": 1 26 | } 27 | -------------------------------------------------------------------------------- /installer/templates/static/brunch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": {}, 3 | "license": "MIT", 4 | "scripts": { 5 | "deploy": "brunch build --production", 6 | "watch": "brunch watch --stdin" 7 | }, 8 | "dependencies": { 9 | "phoenix": "file:<%= brunch_deps_prefix %><%= phoenix_path %>"<%= if html do %>, 10 | "phoenix_html": "file:<%= brunch_deps_prefix %>deps/phoenix_html"<% end %> 11 | }, 12 | "devDependencies": { 13 | "babel-brunch": "6.0.6", 14 | "brunch": "2.10.7", 15 | "clean-css-brunch": "2.10.0", 16 | "css-brunch": "2.10.0", 17 | "uglify-js-brunch": "2.1.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/digest/cleaner/latest_not_most_recent_cache_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "digests": { 3 | "app-1.css": { 4 | "logical_path": "app.css", 5 | "mtime": 32132171, 6 | "size": 369053, 7 | "digest": "1" 8 | }, 9 | "app-2.css": { 10 | "logical_path": "app.css", 11 | "mtime": 32132172, 12 | "size": 369053, 13 | "digest": "2" 14 | }, 15 | "app-3.css": { 16 | "logical_path": "app.css", 17 | "mtime": 32132170, 18 | "size": 369053, 19 | "digest": "3" 20 | } 21 | }, 22 | "latest": { 23 | "app.css": "app-3.css" 24 | }, 25 | "version": 1 26 | } 27 | -------------------------------------------------------------------------------- /lib/phoenix/transports/serializer.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Transports.Serializer do 2 | @moduledoc """ 3 | Defines a behaviour for `Phoenix.Socket.Message` serialization. 4 | """ 5 | 6 | @doc "Translates a `Phoenix.Socket.Broadcast` struct to fastlane format" 7 | @callback fastlane!(Phoenix.Socket.Broadcast.t) :: term 8 | 9 | @doc "Encodes `Phoenix.Socket.Message` struct to transport representation" 10 | @callback encode!(Phoenix.Socket.Message.t | Phoenix.Socket.Reply.t) :: term 11 | 12 | @doc "Decodes iodata into `Phoenix.Socket.Message` struct" 13 | @callback decode!(iodata, options :: Keyword.t) :: Phoenix.Socket.Message.t 14 | end 15 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.json/view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>View do 2 | use <%= base %>.Web, :view 3 | 4 | def render("index.json", %{<%= plural %>: <%= plural %>}) do 5 | %{data: render_many(<%= plural %>, <%= module %>View, "<%= singular %>.json")} 6 | end 7 | 8 | def render("show.json", %{<%= singular %>: <%= singular %>}) do 9 | %{data: render_one(<%= singular %>, <%= module %>View, "<%= singular %>.json")} 10 | end 11 | 12 | def render("<%= singular %>.json", %{<%= singular %>: <%= singular %>}) do 13 | %{id: <%= singular %>.id<%= for {k, _} <- attrs do %>, 14 | <%= k %>: <%= singular %>.<%= k %><% end %>} 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.embedded/embedded_schema.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect schema.module %> do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias <%= inspect schema.module %> 5 | 6 | embedded_schema do 7 | <%= for {k, v} <- schema.types do %> field <%= inspect k %>, <%= inspect v %><%= schema.defaults[k] %><% end %> 8 | end 9 | 10 | @doc false 11 | def changeset(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do 12 | <%= schema.singular %> 13 | |> cast(attrs, [<%= Enum.map_join(schema.attrs, ", ", &inspect(elem(&1, 0))) %>]) 14 | |> validate_required([<%= Enum.map_join(schema.attrs, ", ", &inspect(elem(&1, 0))) %>]) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | matrix: 3 | include: 4 | - otp_release: 18.3 5 | elixir: 1.3.2 6 | script: mix test 7 | - otp_release: 19.0 8 | elixir: 1.3.2 9 | script: mix test 10 | - otp_release: 18.3 11 | elixir: 1.4.2 12 | - otp_release: 19.0 13 | elixir: 1.4.2 14 | services: 15 | - redis-server 16 | sudo: false 17 | before_script: 18 | - mix deps.get --only test 19 | - nvm install 6.2 && nvm use 6.2 20 | script: 21 | - mix test 22 | - cd installer && mix test 23 | - cd ../ && npm install && npm test 24 | after_script: 25 | - cd $TRAVIS_BUILD_DIR 26 | - mix deps.get --only docs 27 | - MIX_ENV=docs mix inch.report 28 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.json/changeset_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= base %>.ChangesetView do 2 | use <%= base %>.Web, :view 3 | 4 | @doc """ 5 | Traverses and translates changeset errors. 6 | 7 | See `Ecto.Changeset.traverse_errors/2` and 8 | `<%= base %>.ErrorHelpers.translate_error/1` for more details. 9 | """ 10 | def translate_errors(changeset) do 11 | Ecto.Changeset.traverse_errors(changeset, &translate_error/1) 12 | end 13 | 14 | def render("error.json", %{changeset: changeset}) do 15 | # When encoded, the changeset returns its errors 16 | # as a JSON object. So we just pass it forward. 17 | %{errors: translate_errors(changeset)} 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /installer/templates/static/brunch/gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | 7 | # Generated on crash by the VM 8 | erl_crash.dump 9 | 10 | # Static artifacts 11 | /node_modules 12 | 13 | # Since we are building assets from web/static, 14 | # we ignore priv/static. You may want to comment 15 | # this depending on your deployment strategy. 16 | /priv/static/ 17 | 18 | # The config/prod.secret.exs file by default contains sensitive 19 | # data and you should not commit it into version control. 20 | # 21 | # Alternatively, you may comment the line below and commit the 22 | # secrets file as long as you replace its contents by environment 23 | # variables. 24 | /config/prod.secret.exs 25 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ### Environment 12 | 13 | * Elixir version (elixir -v): 14 | * Phoenix version (mix deps): 15 | * NodeJS version (node -v): 16 | * NPM version (npm -v): 17 | * Operating system: 18 | 19 | ### Expected behavior 20 | 21 | 22 | ### Actual behavior 23 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Application do 2 | @moduledoc """ 3 | The <%= app_module %> Application Service. 4 | 5 | The <%= app_name %> system business domain lives in this application. 6 | 7 | Exposes API to clients such as the `<%= app_module%>.Web` application 8 | for use in channels, controllers, and elsewhere. 9 | """ 10 | use Application 11 | 12 | def start(_type, _args) do 13 | import Supervisor.Spec, warn: false 14 | 15 | Supervisor.start_link([ 16 | <%= if ecto do %>supervisor(<%= app_module %>.Repo, []),<% end %> 17 | ], strategy: :one_for_one, name: <%= app_module %>.Supervisor) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | 7 | # Generate on crash by the VM 8 | erl_crash.dump 9 | 10 | # Static artifacts 11 | /node_modules 12 | 13 | # Since we are building assets from web/static, 14 | # we ignore priv/static. You may want to comment 15 | # this depending on your deployment strategy. 16 | /priv/static/ 17 | 18 | # Files matching config/*.secret.exs pattern contain sensitive 19 | # data and you should not commit them into version control. 20 | # 21 | # Alternatively, you may comment the line below and commit the 22 | # secrets files as long as you replace their contents by environment 23 | # variables. 24 | /config/*.secret.exs -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= root_app_module %>.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [apps_path: "apps", 6 | start_permanent: Mix.env == :prod, 7 | deps: deps()] 8 | end 9 | 10 | # Dependencies can be Hex packages: 11 | # 12 | # {:mydep, "~> 0.3.0"} 13 | # 14 | # Or git/path repositories: 15 | # 16 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 17 | # 18 | # Type "mix help deps" for more examples and options. 19 | # 20 | # Dependencies listed here are available only for this project 21 | # and cannot be accessed from applications inside the apps folder 22 | defp deps do 23 | [] 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.json/changeset_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect context.web_module %>.ChangesetView do 2 | use <%= inspect context.web_module %>, :view 3 | 4 | @doc """ 5 | Traverses and translates changeset errors. 6 | 7 | See `Ecto.Changeset.traverse_errors/2` and 8 | `<%= inspect context.web_module %>.ErrorHelpers.translate_error/1` for more details. 9 | """ 10 | def translate_errors(changeset) do 11 | Ecto.Changeset.traverse_errors(changeset, &translate_error/1) 12 | end 13 | 14 | def render("error.json", %{changeset: changeset}) do 15 | # When encoded, the changeset returns its errors 16 | # as a JSON object. So we just pass it forward. 17 | %{errors: translate_errors(changeset)} 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/mix/tasks/phx.digest.clean_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Digest.CleanTest do 2 | use ExUnit.Case 3 | 4 | test "fails when the given paths are invalid" do 5 | Mix.Tasks.Phx.Digest.Clean.run(["--output", "invalid_path"]) 6 | 7 | assert_received {:mix_shell, :error, ["The output path \"invalid_path\" does not exist"]} 8 | end 9 | 10 | test "removes old versions", config do 11 | output_path = Path.join("tmp", to_string(config.test)) 12 | input_path = "priv/static" 13 | :ok = File.mkdir_p!(output_path) 14 | 15 | Mix.Tasks.Phx.Digest.Clean.run([input_path, "-o", output_path]) 16 | 17 | msg = "Clean complete for \"#{output_path}\"" 18 | assert_received {:mix_shell, :info, [^msg]} 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /installer/templates/phx_assets/brunch/gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | 7 | # Generated on crash by the VM 8 | erl_crash.dump 9 | 10 | # Generated on crash by NPM 11 | npm-debug.log 12 | 13 | # Static artifacts 14 | /assets/node_modules 15 | 16 | # Since we are building assets from assets/, 17 | # we ignore priv/static. You may want to comment 18 | # this depending on your deployment strategy. 19 | /priv/static/ 20 | 21 | # Files matching config/*.secret.exs pattern contain sensitive 22 | # data and you should not commit them into version control. 23 | # 24 | # Alternatively, you may comment the line below and commit the 25 | # secrets files as long as you replace their contents by environment 26 | # variables. 27 | /config/*.secret.exs -------------------------------------------------------------------------------- /priv/templates/phx.gen.json/fallback_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect context.web_module %>.FallbackController do 2 | @moduledoc """ 3 | Translates controller action results into valid `Plug.Conn` responses. 4 | 5 | See `Phoenix.Controller.action_fallback/1` for more details. 6 | """ 7 | use <%= inspect context.web_module %>, :controller 8 | 9 | def call(conn, {:error, %Ecto.Changeset{} = changeset}) do 10 | conn 11 | |> put_status(:unprocessable_entity) 12 | |> render(<%= inspect context.web_module %>.ChangesetView, "error.json", changeset: changeset) 13 | end 14 | 15 | def call(conn, {:error, :not_found}) do 16 | conn 17 | |> put_status(:not_found) 18 | |> render(<%= inspect context.web_module %>.ErrorView, :"404") 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/mix/tasks/phoenix.server.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phoenix.Server do 2 | use Mix.Task 3 | 4 | @shortdoc "Starts applications and their servers" 5 | 6 | @moduledoc """ 7 | Starts the application by configuring all endpoints servers to run. 8 | 9 | ## Command line options 10 | 11 | This task accepts the same command-line arguments as `run`. 12 | For additional information, refer to the documentation for 13 | `Mix.Tasks.Run`. 14 | 15 | For example, to run `phoenix.server` without checking dependencies: 16 | 17 | mix phoenix.server --no-deps-check 18 | 19 | The `--no-halt` flag is automatically added. 20 | """ 21 | def run(args) do 22 | IO.puts :stderr, "mix phoenix.server is deprecated. Use phx.server instead." 23 | Mix.Tasks.Phx.Server.run(args) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.model/migration.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= base %>.Repo.Migrations.Create<%= scoped %> do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:<%= plural %><%= if binary_id do %>, primary_key: false<% end %>) do 6 | <%= if binary_id do %> add :id, :binary_id, primary_key: true 7 | <% end %><%= for {k, v} <- attrs do %> add <%= inspect k %>, <%= inspect v %><%= migration_defaults[k] %> 8 | <% end %><%= for {_, i, _, s} <- assocs do %> add <%= if(String.ends_with?(inspect(i), "_id"), do: inspect(i), else: inspect(i) <> "_id") %>, references(<%= inspect(s) %>, on_delete: :nothing<%= if binary_id do %>, type: :binary_id<% end %>) 9 | <% end %> 10 | timestamps() 11 | end 12 | <%= for index <- indexes do %> 13 | <%= index %><% end %> 14 | end 15 | end 16 | 17 | -------------------------------------------------------------------------------- /installer/templates/phx_assets/brunch/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 | <%= if html do %>import "phoenix_html"<% end %> 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 | -------------------------------------------------------------------------------- /installer/templates/static/brunch/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 | <%= if html do %>import "phoenix_html"<% end %> 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 | -------------------------------------------------------------------------------- /installer/templates/new/web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Router do 2 | use <%= app_module %>.Web, :router<%= if html do %> 3 | 4 | pipeline :browser do 5 | plug :accepts, ["html"] 6 | plug :fetch_session 7 | plug :fetch_flash 8 | plug :protect_from_forgery 9 | plug :put_secure_browser_headers 10 | end<% end %> 11 | 12 | pipeline :api do 13 | plug :accepts, ["json"] 14 | end<%= if html do %> 15 | 16 | scope "/", <%= app_module %> do 17 | pipe_through :browser # Use the default browser stack 18 | 19 | get "/", PageController, :index 20 | end 21 | 22 | # Other scopes may use custom stacks. 23 | # scope "/api", <%= app_module %> do 24 | # pipe_through :api 25 | # end<% else %> 26 | 27 | scope "/api", <%= app_module %> do 28 | pipe_through :api 29 | end<% end %> 30 | end 31 | -------------------------------------------------------------------------------- /installer/templates/phx_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.Router do 2 | use <%= web_namespace %>, :router<%= if html do %> 3 | 4 | pipeline :browser do 5 | plug :accepts, ["html"] 6 | plug :fetch_session 7 | plug :fetch_flash 8 | plug :protect_from_forgery 9 | plug :put_secure_browser_headers 10 | end<% end %> 11 | 12 | pipeline :api do 13 | plug :accepts, ["json"] 14 | end<%= if html do %> 15 | 16 | scope "/", <%= web_namespace %> do 17 | pipe_through :browser # Use the default browser stack 18 | 19 | get "/", PageController, :index 20 | end 21 | 22 | # Other scopes may use custom stacks. 23 | # scope "/api", <%= web_namespace %> do 24 | # pipe_through :api 25 | # end<% else %> 26 | 27 | scope "/api", <%= web_namespace %> do 28 | pipe_through :api 29 | end<% end %> 30 | end 31 | -------------------------------------------------------------------------------- /installer/templates/new/web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import <%= app_module %>.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](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :<%= app_name %> 24 | end 25 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.Application do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | import Supervisor.Spec 6 | 7 | # Define workers and child supervisors to be supervised 8 | children = [ 9 | # Start the endpoint when the application starts 10 | supervisor(<%= endpoint_module %>, []), 11 | # Start your own worker by calling: <%= web_namespace %>.Worker.start_link(arg1, arg2, arg3) 12 | # worker(<%= web_namespace %>.Worker, [arg1, arg2, arg3]), 13 | ] 14 | 15 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 16 | # for other strategies and supported options 17 | opts = [strategy: :one_for_one, name: <%= web_namespace %>.Supervisor] 18 | Supervisor.start_link(children, opts) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /installer/templates/phx_gettext/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import <%= web_namespace %>.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](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :<%= web_app_name %> 24 | end 25 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/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 | use Mix.Config 4 | 5 | # By default, the umbrella project as well as each child 6 | # application will require this configuration file, ensuring 7 | # they all use the same configuration. While one could 8 | # configure all applications here, we prefer to delegate 9 | # back to each application for organization purposes. 10 | import_config "../apps/*/config/config.exs" 11 | 12 | # Configures Elixir's Logger 13 | config :logger, :console, 14 | format: "$time $metadata[$level] $message\n", 15 | metadata: [:request_id] 16 | 17 | # Import environment specific config. This must remain at the bottom 18 | # of this file so it overrides the configuration defined above. 19 | import_config "#{Mix.env}.exs" 20 | -------------------------------------------------------------------------------- /test/phoenix/transports/websocket_serializer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Tranports.WebSocketSerializerTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias Phoenix.Transports.WebSocketSerializer 5 | alias Phoenix.Socket.Message 6 | 7 | @msg_json [123, [[34, ["topic"], 34], 58, [34, ["t"], 34], 44, [34, ["ref"], 34], 58, "null", 44, [34, ["payload"], 34], 58, [34, ["m"], 34], 44, [34, ["event"], 34], 58, [34, ["e"], 34]], 125] 8 | 9 | test "encode!/1 encodes `Phoenix.Socket.Message` as JSON" do 10 | msg = %Message{topic: "t", event: "e", payload: "m"} 11 | assert WebSocketSerializer.encode!(msg) == {:socket_push, :text, @msg_json} 12 | end 13 | 14 | test "decode!/2 decodes `Phoenix.Socket.Message` from JSON" do 15 | assert %Message{topic: "t", event: "e", payload: "m"} == 16 | WebSocketSerializer.decode!(@msg_json, opcode: :text) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.schema/migration.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect schema.repo %>.Migrations.Create<%= inspect schema.module %> do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:<%= schema.table %><%= if schema.binary_id do %>, primary_key: false<% end %>) do 6 | <%= if schema.binary_id do %> add :id, :binary_id, primary_key: true 7 | <% end %><%= for {k, v} <- schema.attrs do %> add <%= inspect k %>, <%= inspect v %><%= schema.migration_defaults[k] %> 8 | <% end %><%= for {_, i, _, s} <- schema.assocs do %> add <%= if(String.ends_with?(inspect(i), "_id"), do: inspect(i), else: inspect(i) <> "_id") %>, references(<%= inspect(s) %>, on_delete: :nothing<%= if schema.binary_id do %>, type: :binary_id<% end %>) 9 | <% end %> 10 | timestamps() 11 | end 12 | <%= for index <- schema.indexes do %> 13 | <%= index %><% end %> 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/README.md: -------------------------------------------------------------------------------- 1 | # <%= web_namespace %> 2 | 3 | To start your Phoenix server: 4 | 5 | * Install dependencies with `mix deps.get` 6 | * Create and migrate your database with `mix ecto.create && mix ecto.migrate` 7 | * Install Node.js dependencies with `cd assets && npm install` 8 | * Start Phoenix endpoint with `mix phx.server` 9 | 10 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 11 | 12 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment). 13 | 14 | ## Learn more 15 | 16 | * Official website: http://www.phoenixframework.org/ 17 | * Guides: http://phoenixframework.org/docs/overview 18 | * Docs: https://hexdocs.pm/phoenix 19 | * Mailing list: http://groups.google.com/group/phoenix-talk 20 | * Source: https://github.com/phoenixframework/phoenix 21 | -------------------------------------------------------------------------------- /installer/templates/new/README.md: -------------------------------------------------------------------------------- 1 | # <%= app_module %> 2 | 3 | To start your Phoenix app: 4 | 5 | * Install dependencies with `mix deps.get`<%= if ecto do %> 6 | * Create and migrate your database with `mix ecto.create && mix ecto.migrate`<% end %><%= if brunch do %> 7 | * Install Node.js dependencies with `npm install`<% end %> 8 | * Start Phoenix endpoint with `mix phoenix.server` 9 | 10 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 11 | 12 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment). 13 | 14 | ## Learn more 15 | 16 | * Official website: http://www.phoenixframework.org/ 17 | * Guides: http://phoenixframework.org/docs/overview 18 | * Docs: https://hexdocs.pm/phoenix 19 | * Mailing list: http://groups.google.com/group/phoenix-talk 20 | * Source: https://github.com/phoenixframework/phoenix 21 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.model/model.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= module %> do 2 | use <%= base %>.Web, :model 3 | 4 | schema <%= inspect plural %> do 5 | <%= for {k, _} <- attrs do %> field <%= inspect k %>, <%= inspect types[k] %><%= schema_defaults[k] %> 6 | <% end %><%= for {k, _, m, _} <- assocs do %> belongs_to <%= inspect k %>, <%= m %><%= if(String.ends_with?(inspect(k), "_id"), do: "", else: ", foreign_key: " <> inspect(k) <> "_id") %> 7 | <% end %> 8 | timestamps() 9 | end 10 | 11 | @doc """ 12 | Builds a changeset based on the `struct` and `params`. 13 | """ 14 | def changeset(struct, params \\ %{}) do 15 | struct 16 | |> cast(params, [<%= Enum.map_join(attrs, ", ", &inspect(elem(&1, 0))) %>]) 17 | |> validate_required([<%= Enum.map_join(attrs, ", ", &inspect(elem(&1, 0))) %>]) 18 | <%= for k <- uniques do %> |> unique_constraint(<%= inspect k %>) 19 | <% end %> end 20 | end 21 | -------------------------------------------------------------------------------- /lib/phoenix/template/eex_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Template.EExEngine do 2 | @moduledoc """ 3 | The Phoenix engine that handles the `.eex` extension. 4 | """ 5 | 6 | @behaviour Phoenix.Template.Engine 7 | 8 | def compile(path, name) do 9 | EEx.compile_file(path, engine: engine_for(name), line: 1, trim: true) 10 | end 11 | 12 | defp engine_for(name) do 13 | case Phoenix.Template.format_encoder(name) do 14 | Phoenix.Template.HTML -> 15 | unless Code.ensure_loaded?(Phoenix.HTML.Engine) do 16 | raise "Could not load Phoenix.HTML.Engine to use with .html.eex templates. " <> 17 | "You can configure your own format encoder for HTML but we recommend " <> 18 | "adding phoenix_html as a dependency as it provides XSS protection." 19 | end 20 | Phoenix.HTML.Engine 21 | _ -> 22 | EEx.SmartEngine 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /installer/templates/phx_single/README.md: -------------------------------------------------------------------------------- 1 | # <%= app_module %> 2 | 3 | To start your Phoenix server: 4 | 5 | * Install dependencies with `mix deps.get`<%= if ecto do %> 6 | * Create and migrate your database with `mix ecto.create && mix ecto.migrate`<% end %><%= if brunch do %> 7 | * Install Node.js dependencies with `cd assets && npm install`<% end %> 8 | * Start Phoenix endpoint with `mix phx.server` 9 | 10 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 11 | 12 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment). 13 | 14 | ## Learn more 15 | 16 | * Official website: http://www.phoenixframework.org/ 17 | * Guides: http://phoenixframework.org/docs/overview 18 | * Docs: https://hexdocs.pm/phoenix 19 | * Mailing list: http://groups.google.com/group/phoenix-talk 20 | * Source: https://github.com/phoenixframework/phoenix 21 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.channel/channel.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>Channel do 2 | use <%= web_module %>, :channel 3 | 4 | def join("<%= singular %>:lobby", payload, socket) do 5 | if authorized?(payload) do 6 | {:ok, socket} 7 | else 8 | {:error, %{reason: "unauthorized"}} 9 | end 10 | end 11 | 12 | # Channels can be used in a request/response fashion 13 | # by sending replies to requests from the client 14 | def handle_in("ping", payload, socket) do 15 | {:reply, {:ok, payload}, socket} 16 | end 17 | 18 | # It is also common to receive messages from the client and 19 | # broadcast to everyone in the current topic (<%= singular %>:lobby). 20 | def handle_in("shout", payload, socket) do 21 | broadcast socket, "shout", payload 22 | {:noreply, socket} 23 | end 24 | 25 | # Add authorization logic here as required. 26 | defp authorized?(_payload) do 27 | true 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.channel/channel.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>Channel do 2 | use <%= base %>.Web, :channel 3 | 4 | def join("<%= singular %>:lobby", payload, socket) do 5 | if authorized?(payload) do 6 | {:ok, socket} 7 | else 8 | {:error, %{reason: "unauthorized"}} 9 | end 10 | end 11 | 12 | # Channels can be used in a request/response fashion 13 | # by sending replies to requests from the client 14 | def handle_in("ping", payload, socket) do 15 | {:reply, {:ok, payload}, socket} 16 | end 17 | 18 | # It is also common to receive messages from the client and 19 | # broadcast to everyone in the current topic (<%= singular %>:lobby). 20 | def handle_in("shout", payload, socket) do 21 | broadcast socket, "shout", payload 22 | {:noreply, socket} 23 | end 24 | 25 | # Add authorization logic here as required. 26 | defp authorized?(_payload) do 27 | true 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /installer/templates/new/web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.ErrorView do 2 | use <%= app_module %>.Web, :view 3 | 4 | <%= if html do %>def render("404.html", _assigns) do 5 | "Page not found" 6 | end 7 | 8 | def render("500.html", _assigns) do 9 | "Internal server 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<% else %>def render("404.json", _assigns) do 17 | %{errors: %{detail: "Page not found"}} 18 | end 19 | 20 | def render("500.json", _assigns) do 21 | %{errors: %{detail: "Internal server error"}} 22 | end 23 | 24 | # In case no render clause matches or no 25 | # template is found, let's render it as 500 26 | def template_not_found(_template, assigns) do 27 | render "500.json", assigns 28 | end<% end %> 29 | end 30 | -------------------------------------------------------------------------------- /installer/templates/phx_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.ErrorView do 2 | use <%= web_namespace %>, :view 3 | 4 | <%= if html do %>def render("404.html", _assigns) do 5 | "Page not found" 6 | end 7 | 8 | def render("500.html", _assigns) do 9 | "Internal server 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<% else %>def render("404.json", _assigns) do 17 | %{errors: %{detail: "Page not found"}} 18 | end 19 | 20 | def render("500.json", _assigns) do 21 | %{errors: %{detail: "Internal server error"}} 22 | end 23 | 24 | # In case no render clause matches or no 25 | # template is found, let's render it as 500 26 | def template_not_found(_template, assigns) do 27 | render "500.json", assigns 28 | end<% end %> 29 | end 30 | -------------------------------------------------------------------------------- /lib/mix/tasks/phoenix.routes.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phoenix.Routes do 2 | use Mix.Task 3 | 4 | @moduledoc """ 5 | Prints all routes for the default or a given router. 6 | 7 | $ mix phoenix.routes 8 | $ mix phoenix.routes MyApp.AnotherRouter 9 | 10 | The default router is inflected from the application 11 | name unless a configuration named `:namespace` 12 | is set inside your application configuration. For example, 13 | the configuration: 14 | 15 | config :my_app, 16 | namespace: My.App 17 | 18 | will exhibit the routes for `My.App.Router` when this 19 | task is invoked without arguments. 20 | 21 | Umbrella projects do not have a default router and 22 | therefore always expect a router to be given. 23 | """ 24 | 25 | def run(args, base \\ Mix.Phoenix.base()) do 26 | IO.puts :stderr, "mix phoenix.router is deprecated. Use phx.router instead." 27 | Mix.Tasks.Phx.Routes.run(args, base) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/fixtures/digest/priv/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MyPhoenixApp", 3 | "short_name": "MyPhoenixApp", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#F67938", 7 | "description": "A simple Phoenix app.", 8 | "icons": [{ 9 | "src": "images/touch/homescreen48.png", 10 | "sizes": "48x48", 11 | "type": "image/png" 12 | }, { 13 | "src": "images/touch/homescreen72.png", 14 | "sizes": "72x72", 15 | "type": "image/png" 16 | }, { 17 | "src": "images/touch/homescreen96.png", 18 | "sizes": "96x96", 19 | "type": "image/png" 20 | }, { 21 | "src": "images/touch/homescreen144.png", 22 | "sizes": "144x144", 23 | "type": "image/png" 24 | }, { 25 | "src": "images/touch/homescreen168.png", 26 | "sizes": "168x168", 27 | "type": "image/png" 28 | }, { 29 | "src": "images/touch/homescreen192.png", 30 | "sizes": "192x192", 31 | "type": "image/png" 32 | }] 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phoenix", 3 | "version": "1.3.0-rc.1", 4 | "description": "The official JavaScript client for the Phoenix web framework.", 5 | "license": "MIT", 6 | "main": "./priv/static/phoenix.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/phoenixframework/phoenix.git" 10 | }, 11 | "author": "Chris McCord (http://www.phoenixframework.org)", 12 | "devDependencies": { 13 | "babel-brunch": "~6.0.0", 14 | "brunch": "~2.6.5", 15 | "jsdom": "9.8.3", 16 | "jsdom-global": "2.1.0", 17 | "mocha": "~2.4.4", 18 | "mock-socket": "^6.0.1", 19 | "sinon": "^1.17.6", 20 | "uglify-js-brunch": "~2.0.1" 21 | }, 22 | "files": ["README.md", "LICENSE.md", "package.json", "priv/static/phoenix.js", "assets/js/phoenix.js"], 23 | "scripts": { 24 | "test": "./node_modules/.bin/mocha ./assets/test/**/*.js --compilers js:babel-register -r jsdom-global/register" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.channel/channel_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>ChannelTest do 2 | use <%= base %>.ChannelCase 3 | 4 | alias <%= module %>Channel 5 | 6 | setup do 7 | {:ok, _, socket} = 8 | socket("user_id", %{some: :assign}) 9 | |> subscribe_and_join(<%= scoped %>Channel, "<%= singular %>:lobby") 10 | 11 | {:ok, socket: socket} 12 | end 13 | 14 | test "ping replies with status ok", %{socket: socket} do 15 | ref = push socket, "ping", %{"hello" => "there"} 16 | assert_reply ref, :ok, %{"hello" => "there"} 17 | end 18 | 19 | test "shout broadcasts to <%= singular %>:lobby", %{socket: socket} do 20 | push socket, "shout", %{"hello" => "all"} 21 | assert_broadcast "shout", %{"hello" => "all"} 22 | end 23 | 24 | test "broadcasts are pushed to the client", %{socket: socket} do 25 | broadcast_from! socket, "broadcast", %{"some" => "data"} 26 | assert_push "broadcast", %{"some" => "data"} 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.channel/channel_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>ChannelTest do 2 | use <%= web_module %>.ChannelCase 3 | 4 | alias <%= module %>Channel 5 | 6 | setup do 7 | {:ok, _, socket} = 8 | socket("user_id", %{some: :assign}) 9 | |> subscribe_and_join(<%= alias %>Channel, "<%= singular %>:lobby") 10 | 11 | {:ok, socket: socket} 12 | end 13 | 14 | test "ping replies with status ok", %{socket: socket} do 15 | ref = push socket, "ping", %{"hello" => "there"} 16 | assert_reply ref, :ok, %{"hello" => "there"} 17 | end 18 | 19 | test "shout broadcasts to <%= singular %>:lobby", %{socket: socket} do 20 | push socket, "shout", %{"hello" => "all"} 21 | assert_broadcast "shout", %{"hello" => "all"} 22 | end 23 | 24 | test "broadcasts are pushed to the client", %{socket: socket} do 25 | broadcast_from! socket, "broadcast", %{"some" => "data"} 26 | assert_push "broadcast", %{"some" => "data"} 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.server.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Server do 2 | use Mix.Task 3 | 4 | @shortdoc "Starts applications and their servers" 5 | 6 | @moduledoc """ 7 | Starts the application by configuring all endpoints servers to run. 8 | 9 | ## Command line options 10 | 11 | This task accepts the same command-line arguments as `run`. 12 | For additional information, refer to the documentation for 13 | `Mix.Tasks.Run`. 14 | 15 | For example, to run `phx.server` without checking dependencies: 16 | 17 | mix phx.server --no-deps-check 18 | 19 | The `--no-halt` flag is automatically added. 20 | """ 21 | def run(args) do 22 | Application.put_env(:phoenix, :serve_endpoints, true, persistent: true) 23 | Mix.Tasks.Run.run run_args() ++ args 24 | end 25 | 26 | defp run_args do 27 | if iex_running?(), do: [], else: ["--no-halt"] 28 | end 29 | 30 | defp iex_running? do 31 | Code.ensure_loaded?(IEx) and IEx.started? 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | Code.require_file("support/router_helper.exs", __DIR__) 2 | 3 | # Starts web server applications 4 | Application.ensure_all_started(:cowboy) 5 | 6 | # TODO v1.4: Remove this since Elixir v1.3 will no longer be supported 7 | if Version.match?(System.version, "~> 1.3.0") do 8 | ExUnit.configure exclude: [:phoenix_new, :phx_new] 9 | end 10 | 11 | # Used whenever a router fails. We default to simply 12 | # rendering a short string. 13 | defmodule Phoenix.ErrorView do 14 | def render("404.json", %{kind: kind, reason: _reason, stack: _stack, conn: conn}) do 15 | %{error: "Got 404 from #{kind} with #{conn.method}"} 16 | end 17 | 18 | def render(template, %{conn: conn}) do 19 | unless conn.private.phoenix_endpoint do 20 | raise "no endpoint in error view" 21 | end 22 | "#{template} from Phoenix.ErrorView" 23 | end 24 | end 25 | 26 | # For mix tests 27 | Mix.shell(Mix.Shell.Process) 28 | 29 | ExUnit.start(assert_receive_timeout: 200) 30 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.json/view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>View do 2 | use <%= inspect context.web_module %>, :view 3 | alias <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>View 4 | 5 | def render("index.json", %{<%= schema.plural %>: <%= schema.plural %>}) do 6 | %{data: render_many(<%= schema.plural %>, <%= inspect schema.alias %>View, "<%= schema.singular %>.json")} 7 | end 8 | 9 | def render("show.json", %{<%= schema.singular %>: <%= schema.singular %>}) do 10 | %{data: render_one(<%= schema.singular %>, <%= inspect schema.alias %>View, "<%= schema.singular %>.json")} 11 | end 12 | 13 | def render("<%= schema.singular %>.json", %{<%= schema.singular %>: <%= schema.singular %>}) do 14 | %{id: <%= schema.singular %>.id<%= for {k, _} <- schema.attrs do %>, 15 | <%= k %>: <%= schema.singular %>.<%= k %><% end %>} 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /installer/templates/phx_single/lib/app_name/application.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Application 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 8 | 9 | # Define workers and child supervisors to be supervised 10 | children = [<%= if ecto do %> 11 | # Start the Ecto repository 12 | supervisor(<%= app_module %>.Repo, []),<% end %> 13 | # Start the endpoint when the application starts 14 | supervisor(<%= endpoint_module %>, []), 15 | # Start your own worker by calling: <%= app_module %>.Worker.start_link(arg1, arg2, arg3) 16 | # worker(<%= app_module %>.Worker, [arg1, arg2, arg3]), 17 | ] 18 | 19 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 20 | # for other strategies and supported options 21 | opts = [strategy: :one_for_one, name: <%= app_module %>.Supervisor] 22 | Supervisor.start_link(children, opts) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.schema/schema.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect schema.module %> do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias <%= inspect schema.module %> 5 | 6 | <%= if schema.binary_id do %> 7 | @primary_key {:id, :binary_id, autogenerate: true} 8 | @foreign_key_type :binary_id<% end %> 9 | schema <%= inspect schema.table %> do 10 | <%= for {k, v} <- schema.types do %> field <%= inspect k %>, <%= inspect v %><%= schema.defaults[k] %> 11 | <% end %><%= for {_, k, _, _} <- schema.assocs do %> field <%= inspect k %>, <%= if schema.binary_id do %>:binary_id<% else %>:id<% end %> 12 | <% end %> 13 | timestamps() 14 | end 15 | 16 | @doc false 17 | def changeset(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do 18 | <%= schema.singular %> 19 | |> cast(attrs, [<%= Enum.map_join(schema.attrs, ", ", &inspect(elem(&1, 0))) %>]) 20 | |> validate_required([<%= Enum.map_join(schema.attrs, ", ", &inspect(elem(&1, 0))) %>]) 21 | <%= for k <- schema.uniques do %> |> unique_constraint(<%= inspect k %>) 22 | <% end %> end 23 | end 24 | -------------------------------------------------------------------------------- /test/mix/tasks/phx.gen.presence_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "../../../installer/test/mix_helper.exs", __DIR__ 2 | 3 | defmodule Mix.Tasks.Phx.Gen.PresenceTest do 4 | use ExUnit.Case 5 | import MixHelper 6 | 7 | setup do 8 | Mix.Task.clear() 9 | :ok 10 | end 11 | 12 | test "generates presence" do 13 | in_tmp_project "generates presence", fn -> 14 | Mix.Tasks.Phx.Gen.Presence.run(["MyPresence"]) 15 | 16 | assert_file "lib/phoenix/web/channels/my_presence.ex", fn file -> 17 | assert file =~ ~S|defmodule Phoenix.Web.MyPresence do| 18 | assert file =~ ~S|use Phoenix.Presence, otp_app: :phoenix| 19 | end 20 | end 21 | end 22 | 23 | test "passing no args defaults to Presence" do 24 | in_tmp_project "generates presence", fn -> 25 | Mix.Tasks.Phx.Gen.Presence.run([]) 26 | 27 | assert_file "lib/phoenix/web/channels/presence.ex", fn file -> 28 | assert file =~ ~S|defmodule Phoenix.Web.Presence do| 29 | assert file =~ ~S|use Phoenix.Presence, otp_app: :phoenix| 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.gen.secret.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Gen.Secret do 2 | @shortdoc "Generates a secret" 3 | 4 | @moduledoc """ 5 | Generates a secret and prints it to the terminal. 6 | 7 | mix phx.gen.secret [length] 8 | 9 | By default, mix phoenix.gen.secret generates a key 64 characters long. 10 | 11 | The minimum value for `length` is 32. 12 | """ 13 | use Mix.Task 14 | 15 | def run([]), do: run(["64"]) 16 | def run([int]), do: int |> parse!() |> random_string() |> Mix.shell.info() 17 | def run([_|_]), do: invalid_args!() 18 | 19 | defp parse!(int) do 20 | case Integer.parse(int) do 21 | {int, ""} -> int 22 | _ -> invalid_args!() 23 | end 24 | end 25 | 26 | defp random_string(length) when length > 31 do 27 | :crypto.strong_rand_bytes(length) |> Base.encode64 |> binary_part(0, length) 28 | end 29 | defp random_string(_), do: Mix.raise "The secret should be at least 32 characters long" 30 | 31 | @spec invalid_args!() :: no_return() 32 | defp invalid_args! do 33 | Mix.raise "mix phx.gen.secret expects a length as integer or no argument at all" 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/mix/tasks/phx.gen.secret_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "../../../installer/test/mix_helper.exs", __DIR__ 2 | 3 | defmodule Mix.Tasks.Phx.Gen.SecretTest do 4 | use ExUnit.Case 5 | import Mix.Tasks.Phx.Gen.Secret 6 | 7 | test "generates a secret" do 8 | run [] 9 | assert_receive {:mix_shell, :info, [secret]} when byte_size(secret) == 64 10 | end 11 | 12 | test "generates a secret with custom length" do 13 | run ["32"] 14 | assert_receive {:mix_shell, :info, [secret]} when byte_size(secret) == 32 15 | end 16 | 17 | test "raises on invalid args" do 18 | message = "mix phx.gen.secret expects a length as integer or no argument at all" 19 | assert_raise Mix.Error, message, fn -> run ["bad"] end 20 | assert_raise Mix.Error, message, fn -> run ["32bad"] end 21 | assert_raise Mix.Error, message, fn -> run ["32", "bad"] end 22 | end 23 | 24 | test "raises when length is too short" do 25 | message = "The secret should be at least 32 characters long" 26 | assert_raise Mix.Error, message, fn -> run ["0"] end 27 | assert_raise Mix.Error, message, fn -> run ["31"] end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.html/index.html.eex: -------------------------------------------------------------------------------- 1 |

Listing <%= template_plural %>

2 | 3 | 4 | 5 | 6 | <%= for {k, _} <- attrs do %> 7 | <% end %> 8 | 9 | 10 | 11 | 12 | <%%= for <%= singular %> <- @<%= plural %> do %> 13 | 14 | <%= for {k, _} <- attrs do %> 15 | <% end %> 16 | 21 | 22 | <%% end %> 23 | 24 |
<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>
<%%= <%= singular %>.<%= k %> %> 17 | <%%= link "Show", to: <%= singular %>_path(@conn, :show, <%= singular %>), class: "btn btn-default btn-xs" %> 18 | <%%= link "Edit", to: <%= singular %>_path(@conn, :edit, <%= singular %>), class: "btn btn-default btn-xs" %> 19 | <%%= link "Delete", to: <%= singular %>_path(@conn, :delete, <%= singular %>), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %> 20 |
25 | 26 | <%%= link "New <%= template_singular %>", to: <%= singular %>_path(@conn, :new) %> 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2014 Chris McCord 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /installer/templates/new/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 |
7 |
8 |

Resources

9 | 20 |
21 | 22 |
23 |

Help

24 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /installer/templates/phx_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 |
7 |
8 |

Resources

9 | 20 |
21 | 22 |
23 |

Help

24 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Instructions 2 | 3 | **IMPORTANT**: when building the archive, it must be done in the minimum supported Erlang and Elixir versions. 4 | 5 | 1. Check related deps for required version bumps and compatibility (`phoenix_ecto`, `phoenix_pubsub_redis`, `phoenix_html`) 6 | 2. Bump version in related files below 7 | 3. Update `phoenix_dep` in `installer/lib/phoenix_new.ex` and `installer/lib/phx_new/generator.ex` to "~> version to be released" 8 | 4. Run tests, commit, push code 9 | 5. Publish packages and docs after pruning any extraneous uncommitted files 10 | 6. Run `MIX_ENV=prod mix archive.build` and `MIX_ENV=prod mix archive.build -o phx_new.ez` inside "installer" directory to build new installers 11 | 7. Copy new installers to "phoenixframework/archives" project 12 | 8. Test installer by generating a new app, running `mix deps.get`, and compiling 13 | 9. Start -dev version in related files below 14 | 10. Update `phoenix_dep` in `installer/lib/phoenix_new.ex` back to git 15 | 11. Publish to `npm` with `npm publish` 16 | 17 | ## Files with version 18 | 19 | * `CHANGELOG` 20 | * `mix.exs` 21 | * `installer/mix.exs` 22 | * `package.json` 23 | -------------------------------------------------------------------------------- /installer/templates/phx_test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.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 | import other functionality to make it easier 8 | to build common datastructures and query the data layer. 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 | # The default endpoint for testing 24 | @endpoint <%= endpoint_module %> 25 | end 26 | end 27 | 28 | <%= if ecto do %> 29 | setup tags do 30 | <%= adapter_config[:test_setup] %> 31 | unless tags[:async] do 32 | <%= adapter_config[:test_async] %> 33 | end 34 | :ok 35 | end 36 | <% else %> 37 | setup _tags do 38 | :ok 39 | end 40 | <% end %> 41 | end 42 | -------------------------------------------------------------------------------- /lib/mix/tasks/compile.phoenix.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Compile.Phoenix do 2 | use Mix.Task 3 | @recursive true 4 | 5 | @moduledoc """ 6 | Compiles Phoenix source files that support code reloading. 7 | """ 8 | 9 | @doc false 10 | def run(_args) do 11 | {:ok, _} = Application.ensure_all_started(:phoenix) 12 | 13 | case touch() do 14 | [] -> :noop 15 | _ -> :ok 16 | end 17 | end 18 | 19 | @doc false 20 | def touch do 21 | Mix.Phoenix.modules 22 | |> modules_for_recompilation 23 | |> modules_to_file_paths 24 | |> Stream.map(&touch_if_exists(&1)) 25 | |> Stream.filter(&(&1 == :ok)) 26 | |> Enum.to_list() 27 | end 28 | defp touch_if_exists(path) do 29 | :file.change_time(path, :calendar.local_time()) 30 | end 31 | 32 | defp modules_for_recompilation(modules) do 33 | Stream.filter modules, fn mod -> 34 | Code.ensure_loaded?(mod) and 35 | function_exported?(mod, :__phoenix_recompile__?, 0) and 36 | mod.__phoenix_recompile__? 37 | end 38 | end 39 | 40 | defp modules_to_file_paths(modules) do 41 | Stream.map(modules, fn mod -> mod.__info__(:compile)[:source] end) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/mix/tasks/phoenix.gen.presence_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "../../../installer/test/mix_helper.exs", __DIR__ 2 | 3 | defmodule Mix.Tasks.Phoenix.Gen.PresenceTest do 4 | use ExUnit.Case 5 | import MixHelper 6 | import ExUnit.CaptureIO 7 | 8 | setup do 9 | Mix.Task.clear() 10 | :ok 11 | end 12 | 13 | test "generates presence" do 14 | in_tmp "deprecated: generates presence", fn -> 15 | capture_io(:stderr, fn -> 16 | Mix.Tasks.Phoenix.Gen.Presence.run(["MyPresence"]) 17 | end) 18 | 19 | assert_file "web/channels/my_presence.ex", fn file -> 20 | assert file =~ ~S|defmodule Phoenix.MyPresence do| 21 | assert file =~ ~S|use Phoenix.Presence, otp_app: :phoenix| 22 | end 23 | end 24 | end 25 | 26 | test "passing no args defaults to Presence" do 27 | in_tmp "deprecated: generates presence", fn -> 28 | capture_io(:stderr, fn -> 29 | Mix.Tasks.Phoenix.Gen.Presence.run([]) 30 | end) 31 | 32 | assert_file "web/channels/presence.ex", fn file -> 33 | assert file =~ ~S|defmodule Phoenix.Presence do| 34 | assert file =~ ~S|use Phoenix.Presence, otp_app: :phoenix| 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /installer/lib/mix/tasks/phx.new.web.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.New.Web do 2 | @moduledoc """ 3 | Creates a new Phoenix web project within an umbrella application. 4 | 5 | It expects the name of the otp app as the first argument and 6 | for the command to be run inside your umbrella application's 7 | apps directory: 8 | 9 | $ cd my_umbrella/apps 10 | $ mix phx.new.web APP [--module MODULE] [--app APP] 11 | 12 | This task is inteded to create a bare Phoenix project without 13 | database integration, which interfaces with your greater 14 | umbrella application(s). 15 | 16 | ## Examples 17 | 18 | mix phx.new.web hello_web 19 | 20 | Is equivalent to: 21 | 22 | mix phx.new.web hello_web --module Hello.Web 23 | 24 | Supports the same options as the `phx.new` task. 25 | See `Mix.Tasks.Phx.New` for details. 26 | """ 27 | use Mix.Task 28 | 29 | def run([]) do 30 | Mix.Tasks.Help.run(["phx.new.web"]) 31 | end 32 | def run([path | _] = args) do 33 | unless Phx.New.Generator.in_umbrella?(path) do 34 | Mix.raise "The web task can only be run within an umbrella's apps directory" 35 | end 36 | 37 | Mix.Tasks.Phx.New.run(args, Phx.New.Web) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/mix/tasks/phoenix.digest.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phoenix.Digest do 2 | use Mix.Task 3 | 4 | @recursive true 5 | 6 | @moduledoc """ 7 | Digests and compress static files. 8 | 9 | mix phoenix.digest 10 | mix phoenix.digest priv/static -o /www/public 11 | 12 | The first argument is the path where the static files are located. The 13 | `-o` option indicates the path that will be used to save the digested and 14 | compressed files. 15 | 16 | If no path is given, it will use `priv/static` as the input and output path. 17 | 18 | The output folder will contain: 19 | 20 | * the original file 21 | * the file compressed with gzip 22 | * a file containing the original file name and its digest 23 | * a compressed file containing the file name and its digest 24 | * a cache manifest file 25 | 26 | Example of generated files: 27 | 28 | * app.js 29 | * app.js.gz 30 | * app-eb0a5b9302e8d32828d8a73f137cc8f0.js 31 | * app-eb0a5b9302e8d32828d8a73f137cc8f0.js.gz 32 | * cache_manifest.json 33 | """ 34 | 35 | def run(args) do 36 | IO.puts :stderr, "mix phoenix.digest is deprecated. Use phx.digest instead." 37 | Mix.Tasks.Phx.Digest.run(args) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/phoenix/transports/long_poll_serializer.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Transports.LongPollSerializer do 2 | @moduledoc false 3 | 4 | @behaviour Phoenix.Transports.Serializer 5 | 6 | alias Phoenix.Socket.Reply 7 | alias Phoenix.Socket.Message 8 | alias Phoenix.Socket.Broadcast 9 | 10 | @doc """ 11 | Translates a `Phoenix.Socket.Broadcast` into a `Phoenix.Socket.Message`. 12 | """ 13 | def fastlane!(%Broadcast{} = msg) do 14 | %Message{topic: msg.topic, 15 | event: msg.event, 16 | payload: msg.payload} 17 | end 18 | 19 | @doc """ 20 | Normalizes a `Phoenix.Socket.Message` struct. 21 | 22 | Encoding is handled downstream in the LongPoll controller. 23 | """ 24 | def encode!(%Reply{} = reply) do 25 | %Message{ 26 | topic: reply.topic, 27 | event: "phx_reply", 28 | ref: reply.ref, 29 | payload: %{status: reply.status, response: reply.payload} 30 | } 31 | end 32 | def encode!(%Message{} = msg), do: msg 33 | 34 | @doc """ 35 | Decodes JSON String into `Phoenix.Socket.Message` struct. 36 | """ 37 | def decode!(message, _opts) do 38 | message 39 | |> Poison.decode!() 40 | |> Phoenix.Socket.Message.from_map!() 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.html/index.html.eex: -------------------------------------------------------------------------------- 1 |

Listing <%= schema.human_plural %>

2 | 3 | 4 | 5 | 6 | <%= for {k, _} <- schema.attrs do %> 7 | <% end %> 8 | 9 | 10 | 11 | 12 | <%%= for <%= schema.singular %> <- @<%= schema.plural %> do %> 13 | 14 | <%= for {k, _} <- schema.attrs do %> 15 | <% end %> 16 | 21 | 22 | <%% end %> 23 | 24 |
<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>
<%%= <%= schema.singular %>.<%= k %> %> 17 | <%%= link "Show", to: <%= schema.singular %>_path(@conn, :show, <%= schema.singular %>), class: "btn btn-default btn-xs" %> 18 | <%%= link "Edit", to: <%= schema.singular %>_path(@conn, :edit, <%= schema.singular %>), class: "btn btn-default btn-xs" %> 19 | <%%= link "Delete", to: <%= schema.singular %>_path(@conn, :delete, <%= schema.singular %>), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %> 20 |
25 | 26 | <%%= link "New <%= schema.human_singular %>", to: <%= schema.singular %>_path(@conn, :new) %> 27 | -------------------------------------------------------------------------------- /installer/templates/new/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 | <%= if namespaced? or ecto do %># General application configuration 9 | config :<%= app_name %><%= if namespaced? do %>, 10 | namespace: <%= app_module %><% end %><%= if ecto do %>, 11 | ecto_repos: [<%= app_module %>.Repo]<% end %> 12 | 13 | <% end %># Configures the endpoint 14 | config :<%= app_name %>, <%= app_module %>.Endpoint, 15 | url: [host: "localhost"], 16 | secret_key_base: "<%= secret_key_base %>", 17 | render_errors: [view: <%= app_module %>.ErrorView, accepts: ~w(<%= if html do %>html <% end %>json)], 18 | pubsub: [name: <%= app_module %>.PubSub, 19 | adapter: Phoenix.PubSub.PG2] 20 | 21 | # Configures Elixir's Logger 22 | config :logger, :console, 23 | format: "$time $metadata[$level] $message\n", 24 | metadata: [:request_id] 25 | <%= generator_config %> 26 | # Import environment specific config. This must remain at the bottom 27 | # of this file so it overrides the configuration defined above. 28 | import_config "#{Mix.env}.exs" 29 | -------------------------------------------------------------------------------- /installer/templates/new/lib/app_name.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %> 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 8 | 9 | # Define workers and child supervisors to be supervised 10 | children = [<%= if ecto do %> 11 | # Start the Ecto repository 12 | supervisor(<%= app_module %>.Repo, []),<% end %> 13 | # Start the endpoint when the application starts 14 | supervisor(<%= app_module %>.Endpoint, []), 15 | # Start your own worker by calling: <%= app_module %>.Worker.start_link(arg1, arg2, arg3) 16 | # worker(<%= app_module %>.Worker, [arg1, arg2, arg3]), 17 | ] 18 | 19 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 20 | # for other strategies and supported options 21 | opts = [strategy: :one_for_one, name: <%= app_module %>.Supervisor] 22 | Supervisor.start_link(children, opts) 23 | end 24 | 25 | # Tell Phoenix to update the endpoint configuration 26 | # whenever the application is updated. 27 | def config_change(changed, _new, removed) do 28 | <%= app_module %>.Endpoint.config_change(changed, removed) 29 | :ok 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /installer/templates/new/test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.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 | import 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 | <%= if ecto do %> 23 | alias <%= app_module %>.Repo 24 | import Ecto 25 | import Ecto.Changeset 26 | import Ecto.Query 27 | <% end %> 28 | 29 | # The default endpoint for testing 30 | @endpoint <%= app_module %>.Endpoint 31 | end 32 | end 33 | 34 | setup tags do 35 | <%= if ecto do %> <%= adapter_config[:test_setup] %> 36 | 37 | unless tags[:async] do 38 | <%= adapter_config[:test_async] %> 39 | end 40 | <% else %> 41 | _ = tags 42 | <% end %> 43 | :ok 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/support/router_helper.exs: -------------------------------------------------------------------------------- 1 | defmodule RouterHelper do 2 | @moduledoc """ 3 | Conveniences for testing routers and controllers. 4 | 5 | Must not be used to test endpoints as it does some 6 | pre-processing (like fetching params) which could 7 | skew endpoint tests. 8 | """ 9 | 10 | import Plug.Test 11 | 12 | @session Plug.Session.init( 13 | store: :cookie, 14 | key: "_app", 15 | encryption_salt: "yadayada", 16 | signing_salt: "yadayada" 17 | ) 18 | 19 | defmacro __using__(_) do 20 | quote do 21 | use Plug.Test 22 | import RouterHelper 23 | end 24 | end 25 | 26 | def with_session(conn) do 27 | conn 28 | |> Map.put(:secret_key_base, String.duplicate("abcdefgh", 8)) 29 | |> Plug.Session.call(@session) 30 | |> Plug.Conn.fetch_session() 31 | end 32 | 33 | def call(router, verb, path, params \\ nil, script_name \\ []) do 34 | verb 35 | |> conn(path, params) 36 | |> Plug.Conn.fetch_query_params 37 | |> Map.put(:script_name, script_name) 38 | |> router.call(router.init([])) 39 | end 40 | 41 | def action(controller, verb, action, params \\ nil) do 42 | conn = conn(verb, "/", params) |> Plug.Conn.fetch_query_params 43 | controller.call(conn, controller.init(action)) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /installer/templates/new/test/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.ErrorViewTest do 2 | use <%= app_module %>.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | <%= if html do %>test "renders 404.html" do 8 | assert render_to_string(<%= app_module %>.ErrorView, "404.html", []) == 9 | "Page not found" 10 | end 11 | 12 | test "render 500.html" do 13 | assert render_to_string(<%= app_module %>.ErrorView, "500.html", []) == 14 | "Internal server error" 15 | end 16 | 17 | test "render any other" do 18 | assert render_to_string(<%= app_module %>.ErrorView, "505.html", []) == 19 | "Internal server error" 20 | end<% else %>test "renders 404.json" do 21 | assert render(<%= app_module %>.ErrorView, "404.json", []) == 22 | %{errors: %{detail: "Page not found"}} 23 | end 24 | 25 | test "render 500.json" do 26 | assert render(<%= app_module %>.ErrorView, "500.json", []) == 27 | %{errors: %{detail: "Internal server error"}} 28 | end 29 | 30 | test "render any other" do 31 | assert render(<%= app_module %>.ErrorView, "505.json", []) == 32 | %{errors: %{detail: "Internal server error"}} 33 | end<% end %> 34 | end 35 | -------------------------------------------------------------------------------- /installer/templates/new/web/templates/layout/app.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Hello <%= app_module %>! 11 | "> 12 | 13 | 14 | 15 |
16 |
17 | 22 | 23 |
24 | 25 | 26 | 27 | 28 |
29 | <%%= render @view_module, @view_template, assigns %> 30 |
31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /installer/templates/phx_web/templates/layout/app.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Hello <%= app_module %>! 11 | "> 12 | 13 | 14 | 15 |
16 |
17 | 22 | 23 |
24 | 25 | 26 | 27 | 28 |
29 | <%%= render @view_module, @view_template, assigns %> 30 |
31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /installer/templates/phx_test/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.ErrorViewTest do 2 | use <%= web_namespace %>.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | <%= if html do %>test "renders 404.html" do 8 | assert render_to_string(<%= web_namespace %>.ErrorView, "404.html", []) == 9 | "Page not found" 10 | end 11 | 12 | test "render 500.html" do 13 | assert render_to_string(<%= web_namespace %>.ErrorView, "500.html", []) == 14 | "Internal server error" 15 | end 16 | 17 | test "render any other" do 18 | assert render_to_string(<%= web_namespace %>.ErrorView, "505.html", []) == 19 | "Internal server error" 20 | end<% else %>test "renders 404.json" do 21 | assert render(<%= web_namespace %>.ErrorView, "404.json", []) == 22 | %{errors: %{detail: "Page not found"}} 23 | end 24 | 25 | test "render 500.json" do 26 | assert render(<%= web_namespace %>.ErrorView, "500.json", []) == 27 | %{errors: %{detail: "Internal server error"}} 28 | end 29 | 30 | test "render any other" do 31 | assert render(<%= web_namespace %>.ErrorView, "505.json", []) == 32 | %{errors: %{detail: "Internal server error"}} 33 | end<% end %> 34 | end 35 | -------------------------------------------------------------------------------- /installer/templates/phx_single/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 | <%= if namespaced? || ecto || generators do %># General application configuration 9 | config :<%= app_name %><%= if namespaced? do %>, 10 | namespace: <%= app_module %><% end %><%= if ecto do %>, 11 | ecto_repos: [<%= app_module %>.Repo]<% end %><%= if generators do %>, 12 | generators: <%= inspect generators %><% end %> 13 | 14 | <% end %># Configures the endpoint 15 | config :<%= app_name %>, <%= endpoint_module %>, 16 | url: [host: "localhost"], 17 | secret_key_base: "<%= secret_key_base %>", 18 | render_errors: [view: <%= web_namespace %>.ErrorView, accepts: ~w(<%= if html do %>html <% end %>json)], 19 | pubsub: [name: <%= app_module %>.PubSub, 20 | adapter: Phoenix.PubSub.PG2] 21 | 22 | # Configures Elixir's Logger 23 | config :logger, :console, 24 | format: "$time $metadata[$level] $message\n", 25 | metadata: [:request_id] 26 | 27 | # Import environment specific config. This must remain at the bottom 28 | # of this file so it overrides the configuration defined above. 29 | import_config "#{Mix.env}.exs" 30 | -------------------------------------------------------------------------------- /installer/templates/phx_test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.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 | import other functionality to make it easier 8 | to build common datastructures and query the data layer. 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 | import <%= web_namespace %>.Router.Helpers 23 | 24 | # The default endpoint for testing 25 | @endpoint <%= endpoint_module %> 26 | end 27 | end 28 | 29 | <%= if ecto do %> 30 | setup tags do 31 | <%= adapter_config[:test_setup] %> 32 | unless tags[:async] do 33 | <%= adapter_config[:test_async] %> 34 | end 35 | {:ok, conn: Phoenix.ConnTest.build_conn()} 36 | end 37 | <% else %> 38 | setup _tags do 39 | {:ok, conn: Phoenix.ConnTest.build_conn()} 40 | end 41 | <% end %> 42 | end 43 | -------------------------------------------------------------------------------- /lib/phoenix/transports/websocket_serializer.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Transports.WebSocketSerializer do 2 | @moduledoc false 3 | 4 | @behaviour Phoenix.Transports.Serializer 5 | 6 | alias Phoenix.Socket.Reply 7 | alias Phoenix.Socket.Message 8 | alias Phoenix.Socket.Broadcast 9 | 10 | @doc """ 11 | Translates a `Phoenix.Socket.Broadcast` into a `Phoenix.Socket.Message`. 12 | """ 13 | def fastlane!(%Broadcast{} = msg) do 14 | {:socket_push, :text, Poison.encode_to_iodata!(%Message{ 15 | topic: msg.topic, 16 | event: msg.event, 17 | payload: msg.payload 18 | })} 19 | end 20 | 21 | @doc """ 22 | Encodes a `Phoenix.Socket.Message` struct to JSON string. 23 | """ 24 | def encode!(%Reply{} = reply) do 25 | {:socket_push, :text, Poison.encode_to_iodata!(%Message{ 26 | topic: reply.topic, 27 | event: "phx_reply", 28 | ref: reply.ref, 29 | payload: %{status: reply.status, response: reply.payload} 30 | })} 31 | end 32 | def encode!(%Message{} = msg) do 33 | {:socket_push, :text, Poison.encode_to_iodata!(msg)} 34 | end 35 | 36 | @doc """ 37 | Decodes JSON String into `Phoenix.Socket.Message` struct. 38 | """ 39 | def decode!(message, _opts) do 40 | message 41 | |> Poison.decode!() 42 | |> Phoenix.Socket.Message.from_map!() 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/phoenix/transports/long_poll_serializer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Tranports.LongPollSerializerTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias Phoenix.Transports.LongPollSerializer 5 | alias Phoenix.Socket.{Message, Broadcast, Reply} 6 | 7 | @msg_json [123, [[34, ["topic"], 34], 58, [34, ["t"], 34], 44, [34, ["ref"], 34], 58, "null", 44, [34, ["payload"], 34], 58, [34, ["m"], 34], 44, [34, ["event"], 34], 58, [34, ["e"], 34]], 125] 8 | 9 | test "fastlane!/1 translates `Phoenix.Socket.Broadcast` into 'Phoenix.Socket.Message'" do 10 | broadcast = %Broadcast{topic: "t", event: "e", payload: "m"} 11 | assert LongPollSerializer.fastlane!(broadcast) == 12 | %Message{topic: broadcast.topic, event: broadcast.event, payload: broadcast.payload} 13 | end 14 | 15 | test "encode!/1 encodes `Phoenix.Socket.Message` as JSON" do 16 | reply = %Reply{topic: "t", payload: "m", ref: "foo", status: "bar"} 17 | assert LongPollSerializer.encode!(reply) == 18 | %Message{topic: reply.topic, event: "phx_reply", ref: reply.ref, payload: %{status: reply.status, response: reply.payload}} 19 | end 20 | 21 | test "decode!/2 decodes `Phoenix.Socket.Message` from JSON" do 22 | assert %Message{topic: "t", event: "e", payload: "m"} == 23 | LongPollSerializer.decode!(@msg_json, opcode: :text) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /brunch-config.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | sourceMaps: false, 3 | production: true, 4 | 5 | modules: { 6 | definition: false, 7 | // The wrapper for browsers in a way that: 8 | // 9 | // 1. Phoenix.Socket, Phoenix.Channel and so on are available 10 | // 2. the exports variable does not leak 11 | // 3. the Socket, Channel variables and so on do not leak 12 | wrapper: function(path, code){ 13 | return "(function (global, factory) {\n" 14 | + "typeof exports === 'object' ? factory(exports) :\n" 15 | + "typeof define === 'function' && define.amd ? define(['exports'], factory) :\n" 16 | + "factory(global.Phoenix = global.Phoenix || {});\n" 17 | + "}(this, (function (exports) {\n" 18 | + code 19 | + "\n})));"; 20 | } 21 | }, 22 | 23 | files: { 24 | javascripts: { 25 | joinTo: 'phoenix.js' 26 | }, 27 | }, 28 | 29 | conventions: { 30 | assets: /^(static)/ 31 | }, 32 | 33 | // Phoenix paths configuration 34 | paths: { 35 | // Which directories to watch 36 | watched: ["assets/js"], 37 | 38 | // Where to compile files to 39 | public: "priv/static" 40 | }, 41 | 42 | // Configure your plugins 43 | plugins: { 44 | babel: { 45 | // Do not use ES6 compiler in vendor code 46 | ignore: [/^(assets\/vendor)/] 47 | } 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /installer/templates/new/web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", <%= app_module %>.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 | # <%= app_module %>.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{}) 34 | # 35 | # Returning `nil` makes this socket anonymous. 36 | def id(_socket), do: nil 37 | end 38 | -------------------------------------------------------------------------------- /installer/templates/phx_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", <%= web_namespace %>.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: "user_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 | # <%= endpoint_module %>.broadcast("user_socket:#{user.id}", "disconnect", %{}) 34 | # 35 | # Returning `nil` makes this socket anonymous. 36 | def id(_socket), do: nil 37 | end 38 | -------------------------------------------------------------------------------- /installer/templates/new/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.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 | import 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 | <%= if ecto do %> 23 | alias <%= app_module %>.Repo 24 | import Ecto 25 | import Ecto.Changeset 26 | import Ecto.Query 27 | <% end %> 28 | import <%= app_module %>.Router.Helpers 29 | 30 | # The default endpoint for testing 31 | @endpoint <%= app_module %>.Endpoint 32 | end 33 | end 34 | 35 | setup tags do 36 | <%= if ecto do %> <%= adapter_config[:test_setup] %> 37 | 38 | unless tags[:async] do 39 | <%= adapter_config[:test_async] %> 40 | end 41 | <% else %> 42 | _ = tags 43 | <% end %> 44 | {:ok, conn: Phoenix.ConnTest.build_conn()} 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/phoenix/code_reloader_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.CodeReloaderTest do 2 | use ExUnit.Case, async: true 3 | use RouterHelper 4 | 5 | defmodule Endpoint do 6 | def config(:reloadable_compilers) do 7 | [:gettext, :phoenix, :elixir] 8 | end 9 | end 10 | 11 | def reload!(_) do 12 | {:error, "oops"} 13 | end 14 | 15 | test "compile.phoenix tasks touches files" do 16 | assert Mix.Tasks.Compile.Phoenix.run([]) == :noop 17 | end 18 | 19 | test "reloads on every request" do 20 | pid = Process.whereis(Phoenix.CodeReloader.Server) 21 | :erlang.trace(pid, true, [:receive]) 22 | 23 | opts = Phoenix.CodeReloader.init([]) 24 | conn = conn(:get, "/") 25 | |> Plug.Conn.put_private(:phoenix_endpoint, Endpoint) 26 | |> Phoenix.CodeReloader.call(opts) 27 | assert conn.state == :unset 28 | 29 | assert_receive {:trace, ^pid, :receive, {_, _, {:reload!, Endpoint}}} 30 | end 31 | 32 | test "renders compilation error on failure" do 33 | opts = Phoenix.CodeReloader.init(reloader: &__MODULE__.reload!/1) 34 | conn = conn(:get, "/") 35 | |> Plug.Conn.put_private(:phoenix_endpoint, Endpoint) 36 | |> Phoenix.CodeReloader.call(opts) 37 | assert conn.state == :sent 38 | assert conn.status == 500 39 | assert conn.resp_body =~ "oops" 40 | assert conn.resp_body =~ "CompileError" 41 | assert conn.resp_body =~ "Compilation error" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /installer/templates/new/web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | <%= if html do %> 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 | <% end %> 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. 25 | # Ecto will pass the :count keyword if the error message is 26 | # meant to be pluralized. 27 | # On your own code and templates, depending on whether you 28 | # need the message to be pluralized or not, this could be 29 | # written simply as: 30 | # 31 | # dngettext "errors", "1 file", "%{count} files", count 32 | # dgettext "errors", "is invalid" 33 | # 34 | if count = opts[:count] do 35 | Gettext.dngettext(<%= app_module %>.Gettext, "errors", msg, msg, count, opts) 36 | else 37 | Gettext.dgettext(<%= app_module %>.Gettext, "errors", msg, opts) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /installer/templates/new/lib/app_name/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :<%= app_name %> 3 | 4 | socket "/socket", <%= app_module %>.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: :<%= app_name %>, gzip: false, 12 | only: ~w(css fonts images js 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<%= if html do %> 17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 18 | plug Phoenix.LiveReloader<% end %> 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 | # The session will be stored in the cookie and signed, 34 | # this means its contents can be read but not tampered with. 35 | # Set :encryption_salt if you would also like to encrypt it. 36 | plug Plug.Session, 37 | store: :cookie, 38 | key: "_<%= app_name %>_key", 39 | signing_salt: "<%= signing_salt %>" 40 | 41 | plug <%= app_module %>.Router 42 | end 43 | -------------------------------------------------------------------------------- /installer/templates/phx_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | <%= if html do %> 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 | <% end %> 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. 25 | # Ecto will pass the :count keyword if the error message is 26 | # meant to be pluralized. 27 | # On your own code and templates, depending on whether you 28 | # need the message to be pluralized or not, this could be 29 | # written simply as: 30 | # 31 | # dngettext "errors", "1 file", "%{count} files", count 32 | # dgettext "errors", "is invalid" 33 | # 34 | if count = opts[:count] do 35 | Gettext.dngettext(<%= web_namespace %>.Gettext, "errors", msg, msg, count, opts) 36 | else 37 | Gettext.dgettext(<%= web_namespace %>.Gettext, "errors", msg, opts) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/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 | <%= if namespaced? || ecto || generators do %># General application configuration 9 | config :<%= web_app_name %><%= if namespaced? do %>, 10 | namespace: <%= web_namespace %><% end %><%= if ecto do %>, 11 | ecto_repos: [<%= app_module %>.Repo]<% end %><%= if generators do %>, 12 | generators: <%= inspect generators %><% end %> 13 | 14 | <% end %># Configures the endpoint 15 | config :<%= web_app_name %>, <%= endpoint_module %>, 16 | url: [host: "localhost"], 17 | secret_key_base: "<%= secret_key_base %>", 18 | render_errors: [view: <%= web_namespace %>.ErrorView, accepts: ~w(<%= if html do %>html <% end %>json)], 19 | pubsub: [name: <%= web_namespace %>.PubSub, 20 | adapter: Phoenix.PubSub.PG2] 21 | 22 | # Configures Elixir's Logger 23 | config :logger, :console, 24 | format: "$time $metadata[$level] $message\n", 25 | metadata: [:request_id] 26 | 27 | config :<%= web_app_name %>, :generators, 28 | context_app: <%= if app_name == web_app_name, do: false, else: ":#{app_name}" %> 29 | 30 | # Import environment specific config. This must remain at the bottom 31 | # of this file so it overrides the configuration defined above. 32 | import_config "#{Mix.env}.exs" 33 | -------------------------------------------------------------------------------- /lib/phoenix/code_reloader/proxy.ex: -------------------------------------------------------------------------------- 1 | # A tiny proxy that stores all output sent to the group leader 2 | # while forwarding all requests to it. 3 | defmodule Phoenix.CodeReloader.Proxy do 4 | @moduledoc false 5 | use GenServer 6 | 7 | def start() do 8 | GenServer.start(__MODULE__, "") 9 | end 10 | 11 | def stop(proxy) do 12 | GenServer.call(proxy, :stop) 13 | end 14 | 15 | ## Callbacks 16 | 17 | def handle_call(:stop, _from, output) do 18 | {:stop, :normal, output, output} 19 | end 20 | 21 | def handle_info(msg, output) do 22 | case msg do 23 | {:io_request, from, reply, {:put_chars, chars}} -> 24 | put_chars(from, reply, chars, output) 25 | 26 | {:io_request, from, reply, {:put_chars, m, f, as}} -> 27 | put_chars(from, reply, apply(m, f, as), output) 28 | 29 | {:io_request, from, reply, {:put_chars, _encoding, chars}} -> 30 | put_chars(from, reply, chars, output) 31 | 32 | {:io_request, from, reply, {:put_chars, _encoding, m, f, as}} -> 33 | put_chars(from, reply, apply(m, f, as), output) 34 | 35 | {:io_request, _from, _reply, _request} = msg -> 36 | send(Process.group_leader, msg) 37 | {:noreply, output} 38 | 39 | _ -> 40 | {:noreply, output} 41 | end 42 | end 43 | 44 | defp put_chars(from, reply, chars, output) do 45 | send(Process.group_leader, {:io_request, from, reply, {:put_chars, chars}}) 46 | {:noreply, output <> IO.chardata_to_string(chars)} 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/mix/tasks/phx.digest_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "../../../installer/test/mix_helper.exs", __DIR__ 2 | 3 | defmodule Mix.Tasks.Phx.DigestTest do 4 | use ExUnit.Case 5 | import MixHelper 6 | 7 | test "fails when the given paths are invalid" do 8 | Mix.Tasks.Phx.Digest.run(["invalid_path"]) 9 | assert_received {:mix_shell, :error, ["The input path \"invalid_path\" does not exist"]} 10 | end 11 | 12 | @output_path "mix_phoenix_digest" 13 | test "digests and compress files" do 14 | in_tmp @output_path, fn -> 15 | File.mkdir_p!("priv/static") 16 | 17 | Mix.Tasks.Phx.Digest.run(["priv/static", "-o", @output_path]) 18 | assert_received {:mix_shell, :info, ["Check your digested files at \"mix_phoenix_digest\""]} 19 | end 20 | end 21 | 22 | @output_path "mix_phoenix_digest_no_input" 23 | test "digests and compress files without the input path" do 24 | in_tmp @output_path, fn -> 25 | File.mkdir_p!("priv/static") 26 | Mix.Tasks.Phx.Digest.run(["-o", @output_path]) 27 | assert_received {:mix_shell, :info, ["Check your digested files at \"mix_phoenix_digest_no_input\""]} 28 | end 29 | end 30 | 31 | @input_path "input_path" 32 | test "uses the input path as output path when no outputh path is given" do 33 | in_tmp @input_path, fn -> 34 | File.mkdir_p!(@input_path) 35 | 36 | Mix.Tasks.Phx.Digest.run([@input_path]) 37 | assert_received {:mix_shell, :info, ["Check your digested files at \"input_path\""]} 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /installer/test/phx_new_ecto_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "mix_helper.exs", __DIR__ 2 | 3 | defmodule Mix.Tasks.Phx.New.EctoTest do 4 | use ExUnit.Case 5 | import MixHelper 6 | import ExUnit.CaptureIO 7 | 8 | setup do 9 | # The shell asks to install deps. 10 | # We will politely say not. 11 | send self(), {:mix_shell_input, :yes?, false} 12 | :ok 13 | end 14 | 15 | test "new without args" do 16 | in_tmp_umbrella_project "new without args", fn -> 17 | assert capture_io(fn -> Mix.Tasks.Phx.New.Ecto.run([]) end) =~ 18 | "Creates a new Ecto project within an umbrella application." 19 | end 20 | end 21 | 22 | test "new outside umbrella", config do 23 | in_tmp config.test, fn -> 24 | assert_raise Mix.Error, ~r"The ecto task can only be run within an umbrella's apps directory", fn -> 25 | Mix.Tasks.Phx.New.Ecto.run ["007invalid"] 26 | end 27 | end 28 | end 29 | 30 | test "new with defaults", config do 31 | in_tmp_umbrella_project config.test, fn -> 32 | Mix.Tasks.Phx.New.Ecto.run(["phx_ecto"]) 33 | 34 | # Install dependencies? 35 | assert_received {:mix_shell, :yes?, ["\nFetch and install dependencies?"]} 36 | 37 | # Instructions 38 | assert_received {:mix_shell, :info, ["\nWe are all set!" <> _ = msg]} 39 | assert msg =~ "$ cd phx_ecto" 40 | assert msg =~ "$ iex -S mix" 41 | refute msg =~ "$ phx.server" 42 | 43 | assert_received {:mix_shell, :info, ["Before moving on" <> _]} 44 | end 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /lib/phoenix/router/console_formatter.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Router.ConsoleFormatter do 2 | @moduledoc false 3 | alias Phoenix.Router.Route 4 | 5 | @doc """ 6 | Format the routes for printing. 7 | """ 8 | def format(router) do 9 | routes = router.__routes__ 10 | column_widths = calculate_column_widths(routes) 11 | Enum.map_join(routes, "", &format_route(&1, column_widths)) 12 | end 13 | 14 | defp calculate_column_widths(routes) do 15 | Enum.reduce routes, {0, 0, 0}, fn(route, acc) -> 16 | %Route{verb: verb, path: path, helper: helper} = route 17 | verb = verb_name(verb) 18 | {verb_len, path_len, route_name_len} = acc 19 | route_name = route_name(helper) 20 | 21 | {max(verb_len, String.length(verb)), 22 | max(path_len, String.length(path)), 23 | max(route_name_len, String.length(route_name))} 24 | end 25 | end 26 | 27 | defp format_route(route, column_widths) do 28 | %Route{verb: verb, path: path, plug: plug, 29 | opts: opts, helper: helper} = route 30 | verb = verb_name(verb) 31 | route_name = route_name(helper) 32 | {verb_len, path_len, route_name_len} = column_widths 33 | 34 | String.pad_leading(route_name, route_name_len) <> " " <> 35 | String.pad_trailing(verb, verb_len) <> " " <> 36 | String.pad_trailing(path, path_len) <> " " <> 37 | "#{inspect(plug)} #{inspect(opts)}\n" 38 | end 39 | 40 | defp route_name(nil), do: "" 41 | defp route_name(name), do: name <> "_path" 42 | 43 | defp verb_name(verb), do: verb |> to_string() |> String.upcase() 44 | end 45 | -------------------------------------------------------------------------------- /installer/templates/phx_ecto/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.DataCase do 2 | @moduledoc """ 3 | This module defines the setup for tests requiring 4 | access to the application's data layer. 5 | 6 | You may define functions here to be used as helpers in 7 | your tests. 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 <%= app_module %>.Repo 20 | 21 | import Ecto 22 | import Ecto.Changeset 23 | import Ecto.Query 24 | import <%= app_module %>.DataCase 25 | end 26 | end 27 | 28 | setup tags do 29 | <%= adapter_config[:test_setup] %> 30 | 31 | unless tags[:async] do 32 | <%= adapter_config[:test_async] %> 33 | end 34 | 35 | :ok 36 | end 37 | 38 | @doc """ 39 | A helper that transform changeset errors to a map of messages. 40 | 41 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 42 | assert "password is too short" in errors_on(changeset).password 43 | assert %{password: ["password is too short"]} = errors_on(changeset) 44 | 45 | """ 46 | def errors_on(changeset) do 47 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 48 | Enum.reduce(opts, message, fn {key, value}, acc -> 49 | String.replace(acc, "%{#{key}}", to_string(value)) 50 | end) 51 | end) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/mix/tasks/phoenix.gen.presence.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phoenix.Gen.Presence do 2 | @moduledoc """ 3 | Generates a Presence tracker for your application. 4 | 5 | mix phoenix.gen.presence 6 | 7 | mix phoenix.gen.presence MyPresence 8 | 9 | The only argument is the module name of the Presence tracker, 10 | which defaults to Presence. 11 | 12 | A new file will be generated in: 13 | 14 | * web/channels/presence.ex 15 | 16 | Where `presence.ex` is the snake cased version of the module name provided. 17 | """ 18 | use Mix.Task 19 | 20 | def run([]) do 21 | run(["Presence"]) 22 | end 23 | def run([alias_name]) do 24 | IO.puts :stderr, "mix phoenix.gen.presence is deprecated. Use phx.gen.presence instead." 25 | inflections = Mix.Phoenix.inflect(alias_name) 26 | binding = inflections ++ [ 27 | otp_app: Mix.Phoenix.otp_app(), 28 | pubsub_server: Module.concat(inflections[:base], PubSub) 29 | ] 30 | files = [ 31 | {:eex, "presence.ex", "web/channels/#{binding[:path]}.ex"}, 32 | ] 33 | Mix.Phoenix.copy_from paths(), "priv/templates/phx.gen.presence", "", binding, files 34 | 35 | Mix.shell.info """ 36 | 37 | Add your new module to your supervision tree, 38 | in lib/#{binding[:otp_app]}.ex: 39 | 40 | children = [ 41 | ... 42 | supervisor(#{binding[:module]}, []), 43 | ] 44 | 45 | You're all set! See the Phoenix.Presence docs for more details: 46 | http://hexdocs.pm/phoenix/Phoenix.Presence.html 47 | """ 48 | end 49 | 50 | defp paths do 51 | [".", :phoenix] 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/phoenix/param_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.ParamTest do 2 | use ExUnit.Case, async: true 3 | 4 | import Phoenix.Param 5 | 6 | test "to_param for integers" do 7 | assert to_param(1) == "1" 8 | end 9 | 10 | test "to_param for binaries" do 11 | assert to_param("foo") == "foo" 12 | end 13 | 14 | test "to_param for atoms" do 15 | assert to_param(:foo) == "foo" 16 | assert to_param(true) == "true" 17 | assert to_param(false) == "false" 18 | assert_raise ArgumentError, fn -> to_param(nil) end 19 | end 20 | 21 | test "to_param for maps" do 22 | assert_raise ArgumentError, fn -> to_param(%{id: 1}) end 23 | end 24 | 25 | test "to_param for structs" do 26 | defmodule Foo do 27 | defstruct [:id] 28 | end 29 | assert to_param(struct(Foo, id: 1)) == "1" 30 | assert to_param(struct(Foo, id: "foo")) == "foo" 31 | end 32 | 33 | test "to_param for derivable structs without id" do 34 | msg = ~r"cannot derive Phoenix.Param for struct Phoenix.ParamTest.Bar" 35 | assert_raise ArgumentError, msg, fn -> 36 | defmodule Bar do 37 | @derive Phoenix.Param 38 | defstruct [:uuid] 39 | end 40 | end 41 | 42 | defmodule Bar do 43 | @derive {Phoenix.Param, key: :uuid} 44 | defstruct [:uuid] 45 | end 46 | 47 | assert to_param(struct(Bar, uuid: 1)) == "1" 48 | assert to_param(struct(Bar, uuid: "foo")) == "foo" 49 | 50 | msg = ~r"cannot convert Phoenix.ParamTest.Bar to param, key :uuid contains a nil value" 51 | assert_raise ArgumentError, msg, fn -> 52 | to_param(struct(Bar, uuid: nil)) 53 | end 54 | end 55 | end -------------------------------------------------------------------------------- /lib/mix/tasks/phoenix.gen.channel.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phoenix.Gen.Channel do 2 | @moduledoc """ 3 | Generates a Phoenix channel. 4 | 5 | mix phoenix.gen.channel Room 6 | 7 | Accepts the module name for the channel 8 | 9 | The generated model will contain: 10 | 11 | * a channel in web/channels 12 | * a channel_test in test/channels 13 | 14 | """ 15 | use Mix.Task 16 | 17 | def run(args) do 18 | IO.puts :stderr, "mix phoenix.gen.channel is deprecated. Use phx.gen.channel instead." 19 | 20 | [module] = validate_args!(args) 21 | 22 | binding = Mix.Phoenix.inflect(module) 23 | path = binding[:path] 24 | 25 | Mix.Phoenix.check_module_name_availability!(binding[:module] <> "Channel") 26 | 27 | Mix.Phoenix.copy_from paths(), "priv/templates/phoenix.gen.channel", "", binding, [ 28 | {:eex, "channel.ex", "web/channels/#{path}_channel.ex"}, 29 | {:eex, "channel_test.exs", "test/channels/#{path}_channel_test.exs"}, 30 | ] 31 | 32 | Mix.shell.info """ 33 | 34 | Add the channel to your `web/channels/user_socket.ex` handler, for example: 35 | 36 | channel "#{binding[:singular]}:lobby", #{binding[:module]}Channel 37 | """ 38 | end 39 | 40 | @spec raise_with_help() :: no_return() 41 | defp raise_with_help do 42 | Mix.raise """ 43 | mix phoenix.gen.channel expects just the module name: 44 | 45 | mix phoenix.gen.channel Room 46 | """ 47 | end 48 | 49 | defp validate_args!(args) do 50 | unless length(args) == 1 do 51 | raise_with_help() 52 | end 53 | args 54 | end 55 | 56 | defp paths do 57 | [".", :phoenix] 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /test/mix/tasks/phx.routes_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "../../../installer/test/mix_helper.exs", __DIR__ 2 | 3 | defmodule PhoenixTest.Web.Router do 4 | use Phoenix.Router 5 | get "/", PageController, :index, as: :page 6 | end 7 | 8 | defmodule PhoenixTestOld.Router do 9 | use Phoenix.Router 10 | get "/old", PageController, :index, as: :page 11 | end 12 | 13 | 14 | defmodule Mix.Tasks.Phx.RoutesTest do 15 | use ExUnit.Case, async: true 16 | 17 | test "format routes for specific router" do 18 | Mix.Tasks.Phx.Routes.run(["PhoenixTest.Web.Router"]) 19 | assert_received {:mix_shell, :info, [routes]} 20 | assert routes =~ "page_path GET / PageController :index" 21 | end 22 | 23 | test "prints error when explicit router cannot be found" do 24 | assert_raise Mix.Error, "the provided router, Foo.UnknownBar.CantFindBaz, does not exist", fn -> 25 | Mix.Tasks.Phx.Routes.run(["Foo.UnknownBar.CantFindBaz"]) 26 | end 27 | end 28 | 29 | test "prints error when implicit router cannot be found" do 30 | assert_raise Mix.Error, ~r/no router found at Foo.Web.Router or Foo.Router/, fn -> 31 | Mix.Tasks.Phx.Routes.run([], Foo) 32 | end 33 | end 34 | 35 | test "implicit router detection for web namescape" do 36 | Mix.Tasks.Phx.Routes.run([], PhoenixTest) 37 | assert_received {:mix_shell, :info, [routes]} 38 | assert routes =~ "page_path GET / PageController :index" 39 | end 40 | 41 | test "implicit router detection fallback for old namescape" do 42 | Mix.Tasks.Phx.Routes.run([], PhoenixTestOld) 43 | assert_received {:mix_shell, :info, [routes]} 44 | assert routes =~ "page_path GET /old PageController :index" 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /installer/test/phx_new_web_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "mix_helper.exs", __DIR__ 2 | 3 | defmodule Mix.Tasks.Phx.New.WebTest do 4 | use ExUnit.Case 5 | import MixHelper 6 | import ExUnit.CaptureIO 7 | 8 | @app_name "phx_web" 9 | 10 | setup do 11 | # The shell asks to install deps. 12 | # We will politely say not. 13 | send self(), {:mix_shell_input, :yes?, false} 14 | :ok 15 | end 16 | 17 | test "new without args" do 18 | in_tmp_umbrella_project "new without args", fn -> 19 | assert capture_io(fn -> Mix.Tasks.Phx.New.Web.run([]) end) =~ 20 | "Creates a new Phoenix web project within an umbrella application." 21 | end 22 | end 23 | 24 | test "new outside umbrella", config do 25 | in_tmp config.test, fn -> 26 | assert_raise Mix.Error, ~r"The web task can only be run within an umbrella's apps directory", fn -> 27 | Mix.Tasks.Phx.New.Web.run ["007invalid"] 28 | end 29 | end 30 | end 31 | 32 | test "new with defaults" do 33 | in_tmp_umbrella_project "new with defaults", fn -> 34 | Mix.Tasks.Phx.New.Web.run([@app_name]) 35 | 36 | assert_file "#{@app_name}/config/config.exs", fn file -> 37 | assert file =~ "config :#{@app_name}, :generators," 38 | assert file =~ "context_app: false" 39 | end 40 | 41 | # Install dependencies? 42 | assert_received {:mix_shell, :yes?, ["\nFetch and install dependencies?"]} 43 | 44 | # Instructions 45 | assert_received {:mix_shell, :info, ["\nWe are all set!" <> _ = msg]} 46 | assert msg =~ "$ cd phx_web" 47 | assert msg =~ "$ mix phx.server" 48 | 49 | refute_received {:mix_shell, :info, ["Before moving on" <> _]} 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /installer/lib/mix/tasks/phx.new.ecto.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.New.Ecto do 2 | @moduledoc """ 3 | Creates a new Ecto project within an umbrella application. 4 | 5 | This task is inteded to create a bare Ecto project without 6 | web integration, which serves as a core application of your 7 | domain for web applications and your greater umbrella 8 | platform to integrate with. 9 | 10 | It expects the name of the project as an argument. 11 | 12 | $ cd my_umbrella/apps 13 | $ mix phx.new.ecto APP [--module MODULE] [--app APP] 14 | 15 | A project at the given APP directory will be created. The 16 | application name and module name will be retrieved 17 | from the application name, unless `--module` or `--app` is given. 18 | 19 | ## Options 20 | 21 | * `--app` - the name of the OTP application 22 | 23 | * `--module` - the name of the base module in 24 | the generated skeleton 25 | 26 | * `--database` - specify the database adapter for ecto. 27 | Values can be `postgres`, `mysql`, `mssql`, `sqlite` or 28 | `mongodb`. Defaults to `postgres` 29 | 30 | * `--binary-id` - use `binary_id` as primary key type 31 | in Ecto schemas 32 | 33 | ## Examples 34 | 35 | mix phx.new.ecto hello_ecto 36 | 37 | Is equivalent to: 38 | 39 | mix phx.new.ecto hello_ecto --module HelloEcto 40 | """ 41 | 42 | use Mix.Task 43 | import Phx.New.Generator 44 | 45 | def run([]) do 46 | Mix.Tasks.Help.run(["phx.new.ecto"]) 47 | end 48 | def run([path | _] = args) do 49 | unless in_umbrella?(path) do 50 | Mix.raise "The ecto task can only be run within an umbrella's apps directory" 51 | end 52 | 53 | Mix.Tasks.Phx.New.run(args, Phx.New.Ecto) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /installer/templates/phx_assets/brunch/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 | // http://brunch.io/docs/config#-files- 9 | // joinTo: { 10 | // "js/app.js": /^(js)/, 11 | // "js/vendor.js": /^(vendor)|(deps)/ 12 | // } 13 | // 14 | // To change the order of concatenation of files, explicitly mention here 15 | // order: { 16 | // before: [ 17 | // "vendor/js/jquery-2.1.1.js", 18 | // "vendor/js/bootstrap.min.js" 19 | // ] 20 | // } 21 | }, 22 | stylesheets: { 23 | joinTo: "css/app.css" 24 | }, 25 | templates: { 26 | joinTo: "js/app.js" 27 | } 28 | }, 29 | 30 | conventions: { 31 | // This option sets where we should place non-css and non-js assets in. 32 | // By default, we set this to "/assets/static". Files in this directory 33 | // will be copied to `paths.public`, which is "priv/static" by default. 34 | assets: /^(static)/ 35 | }, 36 | 37 | // Phoenix paths configuration 38 | paths: { 39 | // Dependencies and current project directories to watch 40 | watched: ["static", "css", "js", "vendor"], 41 | // Where to compile files to 42 | public: "../priv/static" 43 | }, 44 | 45 | // Configure your plugins 46 | plugins: { 47 | babel: { 48 | // Do not use ES6 compiler in vendor code 49 | ignore: [/vendor/] 50 | } 51 | }, 52 | 53 | modules: { 54 | autoRequire: { 55 | "js/app.js": ["js/app"] 56 | } 57 | }, 58 | 59 | npm: { 60 | enabled: true 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :<%= app_name %>, 6 | version: "0.0.1", 7 | build_path: "../../_build", 8 | config_path: "../../config/config.exs", 9 | deps_path: "../../deps", 10 | lockfile: "../../mix.lock", 11 | elixir: "~> 1.4", 12 | elixirc_paths: elixirc_paths(Mix.env), 13 | start_permanent: Mix.env == :prod, 14 | aliases: aliases(), 15 | deps: deps()] 16 | end 17 | 18 | # Configuration for the OTP application. 19 | # 20 | # Type `mix help compile.app` for more information. 21 | def application do 22 | [mod: {<%= app_module %>.Application, []}, 23 | extra_applications: [:logger, :runtime_tools]] 24 | end 25 | 26 | # Specifies which paths to compile per environment. 27 | defp elixirc_paths(:test), do: ["lib", "test/support"] 28 | defp elixirc_paths(_), do: ["lib"] 29 | 30 | # Specifies your project dependencies. 31 | # 32 | # Type `mix help deps` for examples and options. 33 | defp deps do 34 | [<%= if ecto do %>{:<%= adapter_app %>, ">= 0.0.0"}, 35 | {:ecto, "~> 2.1"}<% end %>] 36 | end 37 | 38 | # Aliases are shortcuts or tasks specific to the current project.<%= if ecto do %> 39 | # For example, to create, migrate and run the seeds file at once: 40 | # 41 | # $ mix ecto.setup<% end %> 42 | # 43 | # See the documentation for `Mix` for more info on aliases. 44 | defp aliases do 45 | [<%= if ecto do %>"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 46 | "ecto.reset": ["ecto.drop", "ecto.setup"], 47 | "test": ["ecto.create --quiet", "ecto.migrate", "test"]<% end %>] 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/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 :<%= web_app_name %>, <%= endpoint_module %>, 10 | http: [port: 4000], 11 | debug_errors: true, 12 | code_reloader: true, 13 | check_origin: false, 14 | watchers: <%= if brunch do %>[node: ["node_modules/.bin/brunch", "watch", "--stdin", 15 | cd: Path.expand("../assets", __DIR__)]]<% else %>[]<% end %> 16 | 17 | # ## SSL Support 18 | # 19 | # In order to use HTTPS in development, a self-signed 20 | # certificate can be generated by running the following 21 | # command from your terminal: 22 | # 23 | # openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem 24 | # 25 | # The `http:` config above can be replaced with: 26 | # 27 | # https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"], 28 | # 29 | # If desired, both `http:` and `https:` keys can be 30 | # configured to run both http and https servers on 31 | # different ports. 32 | 33 | <%= if html do %># Watch static and templates for browser reloading. 34 | config :<%= web_app_name %>, <%= endpoint_module %>, 35 | live_reload: [ 36 | patterns: [ 37 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, 38 | ~r{priv/gettext/.*(po)$}, 39 | ~r{lib/<%= web_app_name %>/views/.*(ex)$}, 40 | ~r{lib/<%= web_app_name %>/templates/.*(eex)$} 41 | ] 42 | ] 43 | 44 | <% end %> 45 | -------------------------------------------------------------------------------- /test/fixtures/ssl/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0g3Mlh7ZRYiE+btNbePpRGzdj4NIyleZj5LXxRe8xZs/WHb4 3 | j1+UfUxRrAO141UYcE1ZXkpg8x64uUiRm281JPvHGe8CqZlckZQGVF77Bv8oH6gG 4 | TxyrI7Gdni2CJgMqR9A4Kwdlczk7d+fVvQCl8B5izEmCTXWu8RG/QU1BHQLygT/D 5 | SHgeGjzQaaE0OoD/Hxa/zkVPEugjNtxFYVWGQ0gzqsTPFz1CkIp1+CJHzm/fhFtF 6 | aS0aDeFyMW1BRC8CTYXyYdcM9cSn/ahhgmKhYKY3EbRaiE4dfH2rqzvixc+OOEQV 7 | pMPDCVDG0ms/FhCrLzVBoY4/WiZVv6aq2jznmwIDAQABAoIBAFhR/QfSCME32dG3 8 | c6MVBWwD6lUBeoW5t5OqxpbUmEbuNABaZcDDC4hzopOVK9FeYlw16bG/zGvtKvad 9 | ELwuUkYup1S8Ln5pQYbkmpS3Kw2SE6jb2WtCPqNPd1qe/+5Dvm9bmYJeJcYA9oRA 10 | Mpq5vwvretcywVsYdGpgb+5hMVOkunw25I4eNeqZHX1ZKmcgq4I/UcZzoF2qPUC6 11 | jtU4reZn8yTgki0YRJVKvw2QLKTegVbK0JHhHDQBxHyfaoj+gd1abZHHMcG0k82k 12 | Hr86xOJYVGcmZeKk9P/VIPCASgfAbIR23lFkyTxH+GGcrUQvn/kvruKvpQqXgDRo 13 | pz+0IUECgYEA79u2t6SNjZf9z01ZZdWvI0q4q5/v/uuIx05NCbpugJh9XVCoJ88T 14 | jLf7Ul8EKFj9i4CG9HlrtFkIwgKoJGiiIOdUD0vNwSgFEwht8KnbxHASsqo8UU1x 15 | bjgKBPKz3c2hJu4iXQikjHD+wAl2m7X+K0Z62gcY2IKdFI67RpsxwWMCgYEA4DCd 16 | cIWtznK40PboyeUIYtgrNKSaS1kbQOb2nj3HhJ3X3cbFvk5Yl28ac4cjmidXvN7Q 17 | 3bthZFI0ItagLs1raMbiX4kKo2ISeS2jkJZK2PQFWh7Brc4Q+RktTIRFoHyP35uO 18 | dvHJH+rEz0oURbjqSlDbB/eFysiOK8mRUTd38mkCgYARWu5/nzJ22laNF2WujqWb 19 | gh6WnH37DgPZl/rPB2RTfbUkeV+RcdRSTEWtEh705GuEGoqpSdfXNtIBZ7vO1ptU 20 | kihs6uk6XrDvTZ7W2RODxTA1KUgwAdCBTyC6du040VYlwPlPjf6KAusL7iNc5PA9 21 | JV5iRD0x/VFsWV+HnlcdTQKBgQDPn3ZPPR4n8csDi4cvYzMPB4+L410Zpt48jyma 22 | hzB9uwit1WZQxpH5POXMVD0+iG0S92+Lyft6Qz8RfJ9AePGeSYJgY7Q8d5kQLJos 23 | T2Pl5KgIPC+2XP8PEqgHEwDAjltYBOI9edKAApZeOwbnQ0eHp7YRfMSldnNkTfqM 24 | ssgc8QKBgDflG6bfSrjOjgyppsII4tXKdpnRa/UwN98D3/ZU5VJA8jR8yzHuMico 25 | bpeooh03uH8xLalXlXNRAamSv03KprvXRXeA+GeAeoseB1qHJ7Z2pkkqiHFG2XxY 26 | u0jAsKamaT6YjcLqloxOdPSyDoXon6pKxvSsiUg1l9ZASexuTiZV 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.gen.presence.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Gen.Presence do 2 | @shortdoc "Generates a Presence tracker" 3 | 4 | @moduledoc """ 5 | Generates a Presence tracker for your application. 6 | 7 | mix phx.gen.presence 8 | 9 | mix phx.gen.presence MyPresence 10 | 11 | The only argument is the module name of the Presence tracker, 12 | which defaults to Presence. 13 | 14 | A new file will be generated in: 15 | 16 | * lib/my_app/web/channels/presence.ex 17 | 18 | Where `presence.ex` is the snake cased version of the module name provided. 19 | """ 20 | use Mix.Task 21 | 22 | def run([]) do 23 | run(["Presence"]) 24 | end 25 | def run([alias_name]) do 26 | web_prefix = Mix.Phoenix.web_path(Mix.Phoenix.otp_app()) 27 | inflections = Mix.Phoenix.inflect(alias_name) 28 | inflections = Keyword.put(inflections, :module, "#{inflections[:web_module]}.#{inflections[:scoped]}") 29 | 30 | binding = inflections ++ [ 31 | otp_app: Mix.Phoenix.otp_app(), 32 | pubsub_server: Module.concat(inflections[:base], PubSub) 33 | ] 34 | files = [ 35 | {:eex, "presence.ex", Path.join(web_prefix, "channels/#{binding[:path]}.ex")}, 36 | ] 37 | Mix.Phoenix.copy_from paths(), "priv/templates/phx.gen.presence", "", binding, files 38 | 39 | Mix.shell.info """ 40 | 41 | Add your new module to your supervision tree, 42 | in lib/#{Mix.Phoenix.otp_app()}/application.ex: 43 | 44 | children = [ 45 | ... 46 | supervisor(#{binding[:module]}, []), 47 | ] 48 | 49 | You're all set! See the Phoenix.Presence docs for more details: 50 | http://hexdocs.pm/phoenix/Phoenix.Presence.html 51 | """ 52 | end 53 | 54 | defp paths do 55 | [".", :phoenix] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /installer/lib/phx_new/project.ex: -------------------------------------------------------------------------------- 1 | defmodule Phx.New.Project do 2 | @moduledoc false 3 | alias Phx.New.Project 4 | 5 | defstruct base_path: nil, 6 | app: nil, 7 | app_mod: nil, 8 | app_path: nil, 9 | root_app: nil, 10 | root_mod: nil, 11 | project_path: nil, 12 | web_app: nil, 13 | web_namespace: nil, 14 | web_path: nil, 15 | opts: :unset, 16 | in_umbrella?: false, 17 | web_applications: [], 18 | binding: [] 19 | 20 | def new(project_path, opts) do 21 | project_path = Path.expand(project_path) 22 | app = opts[:app] || Path.basename(project_path) 23 | app_mod = Module.concat([opts[:module] || Macro.camelize(app)]) 24 | 25 | %Project{base_path: project_path, 26 | app: app, 27 | app_mod: app_mod, 28 | root_app: app, 29 | root_mod: app_mod, 30 | opts: opts} 31 | end 32 | 33 | def ecto?(%Project{binding: binding}) do 34 | Keyword.fetch!(binding, :ecto) 35 | end 36 | 37 | def html?(%Project{binding: binding}) do 38 | Keyword.fetch!(binding, :html) 39 | end 40 | 41 | def brunch?(%Project{binding: binding}) do 42 | Keyword.fetch!(binding, :brunch) 43 | end 44 | 45 | def join_path(%Project{} = project, location, path) 46 | when location in [:project, :app, :web] do 47 | 48 | project 49 | |> Map.fetch!(:"#{location}_path") 50 | |> Path.join(path) 51 | |> expand_path_with_bindings(project) 52 | end 53 | defp expand_path_with_bindings(path, %Project{} = project) do 54 | Regex.replace(~r/:[a-zA-Z0-9_]+/, path, fn ":" <> key, _ -> 55 | project |> Map.fetch!(:"#{key}") |> to_string() 56 | end) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/fixtures/ssl/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEwTCCA6mgAwIBAgIJAJmNQmFFDZgVMA0GCSqGSIb3DQEBBQUAMIGbMQswCQYD 3 | VQQGEwJVUzENMAsGA1UECBMET2hpbzEPMA0GA1UEBxMGRGF5dG9uMRowGAYDVQQK 4 | ExFQaG9lbml4IEZyYW1ld29yazEaMBgGA1UECxMRU3lzdGVtIEFyY2hpdGVjdHMx 5 | EjAQBgNVBAMTCWxvY2FsaG9zdDEgMB4GCSqGSIb3DQEJARYRcGhvZW5peEBsb2Nh 6 | bGhvc3QwHhcNMTQwNTI1MDAxNTM5WhcNMTUwNTI1MDAxNTM5WjCBmzELMAkGA1UE 7 | BhMCVVMxDTALBgNVBAgTBE9oaW8xDzANBgNVBAcTBkRheXRvbjEaMBgGA1UEChMR 8 | UGhvZW5peCBGcmFtZXdvcmsxGjAYBgNVBAsTEVN5c3RlbSBBcmNoaXRlY3RzMRIw 9 | EAYDVQQDEwlsb2NhbGhvc3QxIDAeBgkqhkiG9w0BCQEWEXBob2VuaXhAbG9jYWxo 10 | b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0g3Mlh7ZRYiE+btN 11 | bePpRGzdj4NIyleZj5LXxRe8xZs/WHb4j1+UfUxRrAO141UYcE1ZXkpg8x64uUiR 12 | m281JPvHGe8CqZlckZQGVF77Bv8oH6gGTxyrI7Gdni2CJgMqR9A4Kwdlczk7d+fV 13 | vQCl8B5izEmCTXWu8RG/QU1BHQLygT/DSHgeGjzQaaE0OoD/Hxa/zkVPEugjNtxF 14 | YVWGQ0gzqsTPFz1CkIp1+CJHzm/fhFtFaS0aDeFyMW1BRC8CTYXyYdcM9cSn/ahh 15 | gmKhYKY3EbRaiE4dfH2rqzvixc+OOEQVpMPDCVDG0ms/FhCrLzVBoY4/WiZVv6aq 16 | 2jznmwIDAQABo4IBBDCCAQAwHQYDVR0OBBYEFPQA6slwgMEFuEbYd+lG4WICZb1u 17 | MIHQBgNVHSMEgcgwgcWAFPQA6slwgMEFuEbYd+lG4WICZb1uoYGhpIGeMIGbMQsw 18 | CQYDVQQGEwJVUzENMAsGA1UECBMET2hpbzEPMA0GA1UEBxMGRGF5dG9uMRowGAYD 19 | VQQKExFQaG9lbml4IEZyYW1ld29yazEaMBgGA1UECxMRU3lzdGVtIEFyY2hpdGVj 20 | dHMxEjAQBgNVBAMTCWxvY2FsaG9zdDEgMB4GCSqGSIb3DQEJARYRcGhvZW5peEBs 21 | b2NhbGhvc3SCCQCZjUJhRQ2YFTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA 22 | A4IBAQAZkmKd/KPN3FXsgYU+2iNKHAdzByEugGbTSdba5WBAwwJCumhGUy8CFXY6 23 | MhfrQjj7mkkU4UFHkHXZCWbucfTGxWFaizkkaStsY+5K6sKVFQZw8kc5llDD4dyG 24 | JNGX99VYtVvJG/GS/rXiOTHJvR4WYbgJ5DPYI+PVWBVIp5wG9T2G8TinTnguFGC6 25 | /EFmYf06XGUAKWsMfzNm8Dm7fkQ94W27FVtJ9RvdeObi2aJt37bE9DVqUkjZ1qtz 26 | fz3uJ5UyoNFn5tCBOdEooivPlvl4wqSTppIpflMNlE82KhSRnESnZF9JQUGW3Lnd 27 | 2H3UOAYVUDhGVP/J9ZJLTcr8O8zA 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /installer/templates/ecto/model_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.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 <%= app_module %>.Repo 20 | 21 | import Ecto 22 | import Ecto.Changeset 23 | import Ecto.Query 24 | import <%= app_module %>.ModelCase 25 | end 26 | end 27 | 28 | setup tags do 29 | <%= adapter_config[:test_setup] %> 30 | 31 | unless tags[:async] do 32 | <%= adapter_config[:test_async] %> 33 | end 34 | 35 | :ok 36 | end 37 | 38 | @doc """ 39 | Helper for returning list of errors in a struct when given certain data. 40 | 41 | ## Examples 42 | 43 | Given a User schema that lists `:name` as a required field and validates 44 | `:password` to be safe, it would return: 45 | 46 | iex> errors_on(%User{}, %{password: "password"}) 47 | [password: "is unsafe", name: "is blank"] 48 | 49 | You could then write your assertion like: 50 | 51 | assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"}) 52 | """ 53 | def errors_on(struct, data) do 54 | struct.__struct__.changeset(struct, data) 55 | |> Ecto.Changeset.traverse_errors(&<%= app_module %>.ErrorHelpers.translate_error/1) 56 | |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/phoenix.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix do 2 | @moduledoc """ 3 | This is the documentation for the Phoenix project. 4 | 5 | By default, Phoenix applications depend on the following packages: 6 | 7 | * [Ecto](https://hexdocs.pm/ecto) - a language integrated query and 8 | database wrapper 9 | 10 | * [Phoenix](https://hexdocs.pm/phoenix) - the Phoenix web framework 11 | (these docs) 12 | 13 | * [Phoenix Pubsub](https://hexdocs.pm/phoenix_pubsub) - a distributed 14 | pubsub system with presence support 15 | 16 | * [Phoenix HTML](https://hexdocs.pm/phoenix_html) - conveniences for 17 | working with HTML in Phoenix 18 | 19 | * [Plug](https://hexdocs.pm/plug) - a specification and conveniences 20 | for composable modules in between web applications 21 | 22 | * [Gettext](https://hexdocs.pm/gettext) - Internationalization and 23 | localization through gettext 24 | 25 | There are also optional packages depending on your configuration: 26 | 27 | * [Phoenix PubSub Redis](https://hexdocs.pm/phoenix_pubsub_redis) - use 28 | Redis to power Phoenix PubSub system 29 | 30 | """ 31 | use Application 32 | 33 | @doc false 34 | def start(_type, _args) do 35 | # Warm up caches 36 | _ = Phoenix.Template.engines 37 | _ = Phoenix.Template.format_encoder("index.html") 38 | 39 | # Configure proper system flags from Phoenix only 40 | if stacktrace_depth = Application.get_env(:phoenix, :stacktrace_depth) do 41 | :erlang.system_flag(:backtrace_depth, stacktrace_depth) 42 | end 43 | 44 | # Start the supervision tree 45 | import Supervisor.Spec 46 | 47 | children = [ 48 | # Code reloading must be serial across all Phoenix apps 49 | worker(Phoenix.CodeReloader.Server, []) 50 | ] 51 | 52 | Supervisor.start_link(children, strategy: :one_for_one, name: Phoenix.Supervisor) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.routes.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Routes do 2 | use Mix.Task 3 | alias Phoenix.Router.ConsoleFormatter 4 | 5 | @shortdoc "Prints all routes" 6 | 7 | @moduledoc """ 8 | Prints all routes for the default or a given router. 9 | 10 | $ mix phx.routes 11 | $ mix phx.routes MyApp.AnotherRouter 12 | 13 | The default router is inflected from the application 14 | name unless a configuration named `:namespace` 15 | is set inside your application configuration. For example, 16 | the configuration: 17 | 18 | config :my_app, 19 | namespace: My.App 20 | 21 | will exhibit the routes for `My.App.Router` when this 22 | task is invoked without arguments. 23 | 24 | Umbrella projects do not have a default router and 25 | therefore always expect a router to be given. 26 | """ 27 | 28 | def run(args, base \\ Mix.Phoenix.base()) do 29 | Mix.Task.run "compile", args 30 | 31 | args 32 | |> Enum.at(0) 33 | |> router(base) 34 | |> ConsoleFormatter.format() 35 | |> Mix.shell.info() 36 | end 37 | 38 | defp router(nil, base) do 39 | if Mix.Project.umbrella?() do 40 | Mix.raise "umbrella applications require an explicit router to be given to phx.routes" 41 | end 42 | web_router = app_mod(base, "Web.Router") 43 | old_router = app_mod(base, "Router") 44 | 45 | loaded(web_router) || loaded(old_router) || Mix.raise """ 46 | no router found at #{inspect web_router} or #{inspect old_router}. 47 | An explicit router module may be given to phx.routes. 48 | """ 49 | end 50 | defp router(router_name, _base) do 51 | arg_router = Module.concat("Elixir", router_name) 52 | loaded(arg_router) || Mix.raise "the provided router, #{inspect arg_router}, does not exist" 53 | end 54 | 55 | defp loaded(module), do: Code.ensure_loaded?(module) && module 56 | 57 | defp app_mod(base, name), do: Module.concat([base, name]) 58 | end 59 | -------------------------------------------------------------------------------- /installer/templates/phx_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= endpoint_module %> do 2 | use Phoenix.Endpoint, otp_app: :<%= web_app_name %> 3 | 4 | socket "/socket", <%= web_namespace %>.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: :<%= web_app_name %>, gzip: false, 12 | only: ~w(css fonts images js 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<%= if html do %> 17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 18 | plug Phoenix.LiveReloader<% end %> 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 | # The session will be stored in the cookie and signed, 34 | # this means its contents can be read but not tampered with. 35 | # Set :encryption_salt if you would also like to encrypt it. 36 | plug Plug.Session, 37 | store: :cookie, 38 | key: "_<%= web_app_name %>_key", 39 | signing_salt: "<%= signing_salt %>" 40 | 41 | plug <%= web_namespace %>.Router 42 | 43 | @doc """ 44 | Dynamically loads configuration from the system environment 45 | on startup. 46 | 47 | It receives the endpoint configuration from the config files 48 | and must return the updated configuration. 49 | """ 50 | def load_from_system_env(config) do 51 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" 52 | {:ok, Keyword.put(config, :http, [:inet6, port: port])} 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /installer/templates/new/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 | # 10 | # In order to use HTTPS in development, a self-signed 11 | # certificate can be generated by running the following 12 | # command from your terminal: 13 | # 14 | # openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem 15 | # 16 | # The `http:` config below can be replaced with: 17 | # https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"], 18 | # 19 | # If desired, both `http:` and `https:` keys can be 20 | # configured to run both http and https servers on 21 | # different ports. 22 | config :<%= app_name %>, <%= app_module %>.Endpoint, 23 | http: [port: 4000], 24 | debug_errors: true, 25 | code_reloader: true, 26 | check_origin: false, 27 | watchers: <%= if brunch do %>[node: ["node_modules/.bin/brunch", "watch", "--stdin", 28 | cd: Path.expand("../", __DIR__)]]<% else %>[]<% end %> 29 | 30 | 31 | <%= if html do %># Watch static and templates for browser reloading. 32 | config :<%= app_name %>, <%= app_module %>.Endpoint, 33 | live_reload: [ 34 | patterns: [ 35 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, 36 | ~r{priv/gettext/.*(po)$}, 37 | ~r{web/views/.*(ex)$}, 38 | ~r{web/templates/.*(eex)$} 39 | ] 40 | ] 41 | 42 | <% end %># Do not include metadata nor timestamps in development logs 43 | config :logger, :console, format: "[$level] $message\n" 44 | 45 | # Set a higher stacktrace during development. Avoid configuring such 46 | # in production as building large stacktraces may be expensive. 47 | config :phoenix, :stacktrace_depth, 20 48 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %>.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :<%= web_app_name %>, 6 | version: "0.0.1", 7 | build_path: "../../_build", 8 | config_path: "../../config/config.exs", 9 | deps_path: "../../deps", 10 | lockfile: "../../mix.lock", 11 | elixir: "~> 1.4", 12 | elixirc_paths: elixirc_paths(Mix.env), 13 | compilers: [:phoenix, :gettext] ++ Mix.compilers, 14 | start_permanent: Mix.env == :prod, 15 | aliases: aliases(), 16 | deps: deps()] 17 | end 18 | 19 | # Configuration for the OTP application. 20 | # 21 | # Type `mix help compile.app` for more information. 22 | def application do 23 | [mod: {<%= web_namespace %>.Application, []}, 24 | extra_applications: [:logger, :runtime_tools]] 25 | end 26 | 27 | # Specifies which paths to compile per environment. 28 | defp elixirc_paths(:test), do: ["lib", "test/support"] 29 | defp elixirc_paths(_), do: ["lib"] 30 | 31 | # Specifies your project dependencies. 32 | # 33 | # Type `mix help deps` for examples and options. 34 | defp deps do 35 | [<%= phoenix_dep %>, 36 | {:phoenix_pubsub, "~> 1.0"},<%= if ecto do %> 37 | {:phoenix_ecto, "~> 3.2"},<% end %><%= if html do %> 38 | {:phoenix_html, "~> 2.6"}, 39 | {:phoenix_live_reload, "~> 1.0", only: :dev},<% end %> 40 | {:gettext, "~> 0.11"},<%= if app_name != web_app_name do %> 41 | {:<%= app_name %>, in_umbrella: true},<% end %> 42 | {:cowboy, "~> 1.0"}] 43 | end 44 | 45 | # Aliases are shortcuts or tasks specific to the current project.<%= if ecto do %> 46 | # For example, we extend the test task to create and migrate the database.<% end %> 47 | # 48 | # See the documentation for `Mix` for more info on aliases. 49 | defp aliases do 50 | [<%= if ecto do %>"test": ["ecto.create --quiet", "ecto.migrate", "test"]<% end %>] 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /installer/templates/static/brunch/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 | // http://brunch.io/docs/config#-files- 9 | // joinTo: { 10 | // "js/app.js": /^(web\/static\/js)|(node_modules)/, 11 | // "js/vendor.js": /^(web\/static\/vendor)/ 12 | // } 13 | // 14 | // To change the order of concatenation of files, explicitly mention here 15 | // order: { 16 | // before: [ 17 | // "web/static/vendor/js/jquery-2.1.1.js", 18 | // "web/static/vendor/js/bootstrap.min.js" 19 | // ] 20 | // } 21 | }, 22 | stylesheets: { 23 | joinTo: "css/app.css", 24 | order: { 25 | after: ["web/static/css/app.css"] // concat app.css last 26 | } 27 | }, 28 | templates: { 29 | joinTo: "js/app.js" 30 | } 31 | }, 32 | 33 | conventions: { 34 | // This option sets where we should place non-css and non-js assets in. 35 | // By default, we set this to "/web/static/assets". Files in this directory 36 | // will be copied to `paths.public`, which is "priv/static" by default. 37 | assets: /^(web\/static\/assets)/ 38 | }, 39 | 40 | // Phoenix paths configuration 41 | paths: { 42 | // Dependencies and current project directories to watch 43 | watched: [ 44 | "web/static", 45 | "test/static" 46 | ], 47 | 48 | // Where to compile files to 49 | public: "priv/static" 50 | }, 51 | 52 | // Configure your plugins 53 | plugins: { 54 | babel: { 55 | // Do not use ES6 compiler in vendor code 56 | ignore: [/web\/static\/vendor/] 57 | } 58 | }, 59 | 60 | modules: { 61 | autoRequire: { 62 | "js/app.js": ["web/static/js/app"] 63 | } 64 | }, 65 | 66 | npm: { 67 | enabled: true 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /installer/templates/phx_single/lib/app_name/web/web.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %> 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 <%= web_namespace %>, :controller 9 | use <%= web_namespace %>, :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 controller do 20 | quote do 21 | use Phoenix.Controller, namespace: <%= web_namespace %> 22 | import Plug.Conn 23 | import <%= web_namespace %>.Router.Helpers 24 | import <%= web_namespace %>.Gettext 25 | end 26 | end 27 | 28 | def view do 29 | quote do 30 | use Phoenix.View, root: "lib/<%= app_name %>/web/templates", 31 | namespace: <%= web_namespace %> 32 | 33 | # Import convenience functions from controllers 34 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]<%= if html do %> 35 | 36 | # Use all HTML functionality (forms, tags, etc) 37 | use Phoenix.HTML<% end %> 38 | 39 | import <%= web_namespace %>.Router.Helpers 40 | import <%= web_namespace %>.ErrorHelpers 41 | import <%= web_namespace %>.Gettext 42 | end 43 | end 44 | 45 | def router do 46 | quote do 47 | use Phoenix.Router 48 | import Plug.Conn 49 | import Phoenix.Controller 50 | end 51 | end 52 | 53 | def channel do 54 | quote do 55 | use Phoenix.Channel 56 | import <%= web_namespace %>.Gettext 57 | end 58 | end 59 | 60 | @doc """ 61 | When used, dispatch to the appropriate controller/view/etc. 62 | """ 63 | defmacro __using__(which) when is_atom(which) do 64 | apply(__MODULE__, which, []) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= web_namespace %> 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 <%= web_namespace %>, :controller 9 | use <%= web_namespace %>, :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 controller do 20 | quote do 21 | use Phoenix.Controller, namespace: <%= web_namespace %> 22 | import Plug.Conn 23 | import <%= web_namespace %>.Router.Helpers 24 | import <%= web_namespace %>.Gettext 25 | end 26 | end 27 | 28 | def view do 29 | quote do 30 | use Phoenix.View, root: "lib/<%= web_app_name %>/templates", 31 | namespace: <%= web_namespace %> 32 | 33 | # Import convenience functions from controllers 34 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]<%= if html do %> 35 | 36 | # Use all HTML functionality (forms, tags, etc) 37 | use Phoenix.HTML<% end %> 38 | 39 | import <%= web_namespace %>.Router.Helpers 40 | import <%= web_namespace %>.ErrorHelpers 41 | import <%= web_namespace %>.Gettext 42 | end 43 | end 44 | 45 | def router do 46 | quote do 47 | use Phoenix.Router 48 | import Plug.Conn 49 | import Phoenix.Controller 50 | end 51 | end 52 | 53 | def channel do 54 | quote do 55 | use Phoenix.Channel 56 | import <%= web_namespace %>.Gettext 57 | end 58 | end 59 | 60 | @doc """ 61 | When used, dispatch to the appropriate controller/view/etc. 62 | """ 63 | defmacro __using__(which) when is_atom(which) do 64 | apply(__MODULE__, which, []) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /installer/lib/phx_new/ecto.ex: -------------------------------------------------------------------------------- 1 | defmodule Phx.New.Ecto do 2 | @moduledoc false 3 | use Phx.New.Generator 4 | alias Phx.New.{Project} 5 | 6 | @pre "phx_umbrella/apps/app_name" 7 | 8 | template :new, [ 9 | {:eex, "#{@pre}/config/config.exs", :app, "config/config.exs"}, 10 | {:eex, "#{@pre}/config/dev.exs", :app, "config/dev.exs"}, 11 | {:eex, "#{@pre}/config/prod.exs", :app, "config/prod.exs"}, 12 | {:eex, "#{@pre}/config/prod.secret.exs", :app, "config/prod.secret.exs"}, 13 | {:eex, "#{@pre}/config/test.exs", :app, "config/test.exs"}, 14 | {:eex, "#{@pre}/lib/app_name/application.ex", :app, "lib/:app/application.ex"}, 15 | {:eex, "#{@pre}/test/test_helper.exs", :app, "test/test_helper.exs"}, 16 | {:eex, "#{@pre}/README.md", :app, "README.md"}, 17 | {:eex, "#{@pre}/mix.exs", :app, "mix.exs"}, 18 | {:text, "phx_assets/bare/gitignore", :app, ".gitignore"}, 19 | ] 20 | 21 | template :ecto, [ 22 | {:eex, "phx_ecto/repo.ex", :app, "lib/:app/repo.ex"}, 23 | {:keep, "phx_ecto/priv/repo/migrations", :app, "priv/repo/migrations"}, 24 | {:eex, "phx_ecto/seeds.exs", :app, "priv/repo/seeds.exs"}, 25 | {:eex, "phx_ecto/data_case.ex", :app, "test/support/data_case.ex"}, 26 | ] 27 | 28 | def prepare_project(%Project{} = project) do 29 | project_path = Path.expand(project.base_path) 30 | 31 | %Project{project | 32 | in_umbrella?: true, 33 | app_path: project_path, 34 | project_path: project_path} 35 | end 36 | 37 | def generate(%Project{} = project) do 38 | copy_from project, __MODULE__, template_files(:new) 39 | 40 | if Project.ecto?(project), do: gen_ecto(project) 41 | 42 | project 43 | end 44 | 45 | defp gen_ecto(project) do 46 | copy_from(project, __MODULE__, template_files(:ecto)) 47 | gen_ecto_config(project) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.digest.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Digest do 2 | use Mix.Task 3 | @default_input_path "priv/static" 4 | 5 | @shortdoc "Digests and compresses static files" 6 | @recursive true 7 | 8 | @moduledoc """ 9 | Digests and compress static files. 10 | 11 | mix phx.digest 12 | mix phx.digest priv/static -o /www/public 13 | 14 | The first argument is the path where the static files are located. The 15 | `-o` option indicates the path that will be used to save the digested and 16 | compressed files. 17 | 18 | If no path is given, it will use `priv/static` as the input and output path. 19 | 20 | The output folder will contain: 21 | 22 | * the original file 23 | * the file compressed with gzip 24 | * a file containing the original file name and its digest 25 | * a compressed file containing the file name and its digest 26 | * a cache manifest file 27 | 28 | Example of generated files: 29 | 30 | * app.js 31 | * app.js.gz 32 | * app-eb0a5b9302e8d32828d8a73f137cc8f0.js 33 | * app-eb0a5b9302e8d32828d8a73f137cc8f0.js.gz 34 | * cache_manifest.json 35 | """ 36 | 37 | def run(args) do 38 | {opts, args, _} = OptionParser.parse(args, aliases: [o: :output]) 39 | input_path = List.first(args) || @default_input_path 40 | output_path = opts[:output] || input_path 41 | 42 | {:ok, _} = Application.ensure_all_started(:phoenix) 43 | 44 | case Phoenix.Digester.compile(input_path, output_path) do 45 | :ok -> 46 | # We need to call build structure so everything we have 47 | # generated into priv is copied to _build in case we have 48 | # build_embedded set to true. In case it's not true, 49 | # build structure is mostly a no-op, so we are fine. 50 | Mix.Project.build_structure() 51 | Mix.shell.info [:green, "Check your digested files at #{inspect output_path}"] 52 | {:error, :invalid_path} -> 53 | Mix.shell.error "The input path #{inspect input_path} does not exist" 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /installer/templates/phx_single/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 :<%= app_name %>, <%= endpoint_module %>, 10 | http: [port: 4000], 11 | debug_errors: true, 12 | code_reloader: true, 13 | check_origin: false, 14 | watchers: <%= if brunch do %>[node: ["node_modules/.bin/brunch", "watch", "--stdin", 15 | cd: Path.expand("../assets", __DIR__)]]<% else %>[]<% end %> 16 | 17 | # ## SSL Support 18 | # 19 | # In order to use HTTPS in development, a self-signed 20 | # certificate can be generated by running the following 21 | # command from your terminal: 22 | # 23 | # openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem 24 | # 25 | # The `http:` config above can be replaced with: 26 | # 27 | # https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"], 28 | # 29 | # If desired, both `http:` and `https:` keys can be 30 | # configured to run both http and https servers on 31 | # different ports. 32 | 33 | <%= if html do %># Watch static and templates for browser reloading. 34 | config :<%= app_name %>, <%= endpoint_module %>, 35 | live_reload: [ 36 | patterns: [ 37 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, 38 | ~r{priv/gettext/.*(po)$}, 39 | ~r{lib/<%= app_name %>/web/views/.*(ex)$}, 40 | ~r{lib/<%= app_name %>/web/templates/.*(eex)$} 41 | ] 42 | ] 43 | 44 | <% end %># Do not include metadata nor timestamps in development logs 45 | config :logger, :console, format: "[$level] $message\n" 46 | 47 | # Set a higher stacktrace during development. Avoid configuring such 48 | # in production as building large stacktraces may be expensive. 49 | config :phoenix, :stacktrace_depth, 20 50 | -------------------------------------------------------------------------------- /installer/templates/new/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :<%= app_name %>, 6 | version: "0.0.1",<%= if in_umbrella do %> 7 | build_path: "../../_build", 8 | config_path: "../../config/config.exs", 9 | deps_path: "../../deps", 10 | lockfile: "../../mix.lock",<% end %> 11 | elixir: "~> 1.4", 12 | elixirc_paths: elixirc_paths(Mix.env), 13 | compilers: [:phoenix, :gettext] ++ Mix.compilers, 14 | start_permanent: Mix.env == :prod,<%= if ecto do %> 15 | aliases: aliases(),<% end %> 16 | deps: deps()] 17 | end 18 | 19 | # Configuration for the OTP application. 20 | # 21 | # Type `mix help compile.app` for more information. 22 | def application do 23 | [mod: {<%= app_module %>, []}, 24 | extra_applications: [:logger]] 25 | end 26 | 27 | # Specifies which paths to compile per environment. 28 | defp elixirc_paths(:test), do: ["lib", "web", "test/support"] 29 | defp elixirc_paths(_), do: ["lib", "web"] 30 | 31 | # Specifies your project dependencies. 32 | # 33 | # Type `mix help deps` for examples and options. 34 | defp deps do 35 | [<%= phoenix_dep %>, 36 | {:phoenix_pubsub, "~> 1.0"},<%= if ecto do %> 37 | {:phoenix_ecto, "~> 3.2"}, 38 | {<%= inspect adapter_app %>, ">= 0.0.0"},<% end %><%= if html do %> 39 | {:phoenix_html, "~> 2.6"}, 40 | {:phoenix_live_reload, "~> 1.0", only: :dev},<% end %> 41 | {:gettext, "~> 0.11"}, 42 | {:cowboy, "~> 1.0"}] 43 | end<%= if ecto do %> 44 | 45 | # Aliases are shortcuts or tasks specific to the current project. 46 | # For example, to create, migrate and run the seeds file at once: 47 | # 48 | # $ mix ecto.setup 49 | # 50 | # See the documentation for `Mix` for more info on aliases. 51 | defp aliases do 52 | ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 53 | "ecto.reset": ["ecto.drop", "ecto.setup"], 54 | "test": ["ecto.create --quiet", "ecto.migrate", "test"]] 55 | end<% end %> 56 | end 57 | -------------------------------------------------------------------------------- /priv/templates/phoenix.gen.json/controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= module %>Controller do 2 | use <%= base %>.Web, :controller 3 | 4 | alias <%= module %> 5 | 6 | def index(conn, _params) do 7 | <%= plural %> = Repo.all(<%= alias %>) 8 | render(conn, "index.json", <%= plural %>: <%= plural %>) 9 | end 10 | 11 | def create(conn, %{<%= inspect singular %> => <%= singular %>_params}) do 12 | changeset = <%= alias %>.changeset(%<%= alias %>{}, <%= singular %>_params) 13 | 14 | case Repo.insert(changeset) do 15 | {:ok, <%= singular %>} -> 16 | conn 17 | |> put_status(:created) 18 | |> put_resp_header("location", <%= singular %>_path(conn, :show, <%= singular %>)) 19 | |> render("show.json", <%= singular %>: <%= singular %>) 20 | {:error, changeset} -> 21 | conn 22 | |> put_status(:unprocessable_entity) 23 | |> render(<%= base %>.ChangesetView, "error.json", changeset: changeset) 24 | end 25 | end 26 | 27 | def show(conn, %{"id" => id}) do 28 | <%= singular %> = Repo.get!(<%= alias %>, id) 29 | render(conn, "show.json", <%= singular %>: <%= singular %>) 30 | end 31 | 32 | def update(conn, %{"id" => id, <%= inspect singular %> => <%= singular %>_params}) do 33 | <%= singular %> = Repo.get!(<%= alias %>, id) 34 | changeset = <%= alias %>.changeset(<%= singular %>, <%= singular %>_params) 35 | 36 | case Repo.update(changeset) do 37 | {:ok, <%= singular %>} -> 38 | render(conn, "show.json", <%= singular %>: <%= singular %>) 39 | {:error, changeset} -> 40 | conn 41 | |> put_status(:unprocessable_entity) 42 | |> render(<%= base %>.ChangesetView, "error.json", changeset: changeset) 43 | end 44 | end 45 | 46 | def delete(conn, %{"id" => id}) do 47 | <%= singular %> = Repo.get!(<%= alias %>, id) 48 | 49 | # Here we use delete! (with a bang) because we expect 50 | # it to always work (and if it does not, it will raise). 51 | Repo.delete!(<%= singular %>) 52 | 53 | send_resp(conn, :no_content, "") 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.gen.channel.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Gen.Channel do 2 | @shortdoc "Generates a Phoenix channel" 3 | 4 | @moduledoc """ 5 | Generates a Phoenix channel. 6 | 7 | mix phx.gen.channel Room 8 | 9 | Accepts the module name for the channel 10 | 11 | The generated files will contain: 12 | 13 | For a regular application: 14 | 15 | * a channel in lib/my_app/web/channels 16 | * a channel_test in test/web/channels 17 | 18 | For an umbrella application: 19 | 20 | * a channel in lib/my_app/channels 21 | * a channel_test in test/channels 22 | 23 | """ 24 | use Mix.Task 25 | 26 | def run(args) do 27 | [channel_name] = validate_args!(args) 28 | otp_app = Mix.Phoenix.otp_app() 29 | 30 | web_prefix = Mix.Phoenix.web_path(otp_app) 31 | test_prefix = Mix.Phoenix.web_test_path(otp_app) 32 | binding = Mix.Phoenix.inflect(channel_name) 33 | binding = Keyword.put(binding, :module, "#{binding[:web_module]}.#{binding[:scoped]}") 34 | 35 | Mix.Phoenix.check_module_name_availability!(binding[:module] <> "Channel") 36 | 37 | Mix.Phoenix.copy_from paths(), "priv/templates/phx.gen.channel", "", binding, [ 38 | {:eex, "channel.ex", Path.join(web_prefix, "channels/#{binding[:path]}_channel.ex")}, 39 | {:eex, "channel_test.exs", Path.join(test_prefix, "channels/#{binding[:path]}_channel_test.exs")}, 40 | ] 41 | 42 | Mix.shell.info """ 43 | 44 | Add the channel to your `#{Mix.Phoenix.web_path(otp_app, "channels/user_socket.ex")}` handler, for example: 45 | 46 | channel "#{binding[:singular]}:lobby", #{binding[:module]}Channel 47 | """ 48 | end 49 | 50 | @spec raise_with_help() :: no_return() 51 | defp raise_with_help do 52 | Mix.raise """ 53 | mix phx.gen.channel expects just the module name: 54 | 55 | mix phx.gen.channel Room 56 | """ 57 | end 58 | 59 | defp validate_args!(args) do 60 | unless length(args) == 1 do 61 | raise_with_help() 62 | end 63 | args 64 | end 65 | 66 | defp paths do 67 | [".", :phoenix] 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/phoenix/naming_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.NamingTest do 2 | use ExUnit.Case, async: true 3 | alias Phoenix.Naming 4 | 5 | doctest Naming 6 | 7 | test "underscore/1 converts Strings to underscore" do 8 | assert Naming.underscore("FooBar") == "foo_bar" 9 | assert Naming.underscore("Foobar") == "foobar" 10 | assert Naming.underscore("APIWorld") == "api_world" 11 | assert Naming.underscore("ErlangVM") == "erlang_vm" 12 | assert Naming.underscore("API.V1.User") == "api/v1/user" 13 | assert Naming.underscore("") == "" 14 | assert Naming.underscore("FooBar1") == "foo_bar1" 15 | assert Naming.underscore("fooBar1") == "foo_bar1" 16 | end 17 | 18 | test "camelize/1 converts Strings to camel case" do 19 | assert Naming.camelize("foo_bar") == "FooBar" 20 | assert Naming.camelize("foo__bar") == "FooBar" 21 | assert Naming.camelize("foobar") == "Foobar" 22 | assert Naming.camelize("_foobar") == "Foobar" 23 | assert Naming.camelize("__foobar") == "Foobar" 24 | assert Naming.camelize("_FooBar") == "FooBar" 25 | assert Naming.camelize("foobar_") == "Foobar" 26 | assert Naming.camelize("foobar_1") == "Foobar1" 27 | assert Naming.camelize("") == "" 28 | assert Naming.camelize("_foo_bar") == "FooBar" 29 | assert Naming.camelize("foo_bar_1") == "FooBar1" 30 | end 31 | 32 | test "camelize/2 converts Strings to lower camel case" do 33 | assert Naming.camelize("foo_bar", :lower) == "fooBar" 34 | assert Naming.camelize("foo__bar", :lower) == "fooBar" 35 | assert Naming.camelize("foobar", :lower) == "foobar" 36 | assert Naming.camelize("_foobar", :lower) == "foobar" 37 | assert Naming.camelize("__foobar", :lower) == "foobar" 38 | assert Naming.camelize("_FooBar", :lower) == "fooBar" 39 | assert Naming.camelize("foobar_", :lower) == "foobar" 40 | assert Naming.camelize("foobar_1", :lower) == "foobar1" 41 | assert Naming.camelize("", :lower) == "" 42 | assert Naming.camelize("_foo_bar", :lower) == "fooBar" 43 | assert Naming.camelize("foo_bar_1", :lower) == "fooBar1" 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /installer/templates/phx_single/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= app_module %>.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :<%= app_name %>, 6 | version: "0.0.1",<%= if in_umbrella do %> 7 | build_path: "../../_build", 8 | config_path: "../../config/config.exs", 9 | deps_path: "../../deps", 10 | lockfile: "../../mix.lock",<% end %> 11 | elixir: "~> 1.4", 12 | elixirc_paths: elixirc_paths(Mix.env), 13 | compilers: [:phoenix, :gettext] ++ Mix.compilers, 14 | start_permanent: Mix.env == :prod,<%= if ecto do %> 15 | aliases: aliases(),<% end %> 16 | deps: deps()] 17 | end 18 | 19 | # Configuration for the OTP application. 20 | # 21 | # Type `mix help compile.app` for more information. 22 | def application do 23 | [mod: {<%= app_module %>.Application, []}, 24 | extra_applications: [:logger, :runtime_tools]] 25 | end 26 | 27 | # Specifies which paths to compile per environment. 28 | defp elixirc_paths(:test), do: ["lib", "test/support"] 29 | defp elixirc_paths(_), do: ["lib"] 30 | 31 | # Specifies your project dependencies. 32 | # 33 | # Type `mix help deps` for examples and options. 34 | defp deps do 35 | [<%= phoenix_dep %>, 36 | {:phoenix_pubsub, "~> 1.0"},<%= if ecto do %> 37 | {:phoenix_ecto, "~> 3.2"}, 38 | {<%= inspect adapter_app %>, ">= 0.0.0"},<% end %><%= if html do %> 39 | {:phoenix_html, "~> 2.6"}, 40 | {:phoenix_live_reload, "~> 1.0", only: :dev},<% end %> 41 | {:gettext, "~> 0.11"}, 42 | {:cowboy, "~> 1.0"}] 43 | end<%= if ecto do %> 44 | 45 | # Aliases are shortcuts or tasks specific to the current project. 46 | # For example, to create, migrate and run the seeds file at once: 47 | # 48 | # $ mix ecto.setup 49 | # 50 | # See the documentation for `Mix` for more info on aliases. 51 | defp aliases do 52 | ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 53 | "ecto.reset": ["ecto.drop", "ecto.setup"], 54 | "test": ["ecto.create --quiet", "ecto.migrate", "test"]] 55 | end<% end %> 56 | end 57 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.gen.embedded.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Gen.Embedded do 2 | @shortdoc "Generates an embedded Ecto schema file" 3 | 4 | @moduledoc """ 5 | Generates an embedded Ecto schema for casting/validating data outside the DB. 6 | 7 | mix phx.gen.embedded Blog.Post blog_posts title:string views:integer 8 | 9 | The first argument is the schema module followed by its plural 10 | name (used as the table name). 11 | 12 | The generated schema above will contain: 13 | 14 | * an embedded schema file in lib/my_app/blog/post.ex. 15 | 16 | ## Attributes 17 | 18 | The resource fields are given using `name:type` syntax 19 | where type are the types supported by Ecto. Omitting 20 | the type makes it default to `:string`: 21 | 22 | mix phx.gen.embedded Blog.Post blog_posts title views:integer 23 | 24 | The following types are supported: 25 | 26 | #{for attr <- Mix.Phoenix.Schema.valid_types(), do: " * `#{inspect attr}`\n"} 27 | * `:datetime` - An alias for `:naive_datetime` 28 | """ 29 | use Mix.Task 30 | 31 | alias Mix.Tasks.Phx.Gen 32 | alias Mix.Phoenix.Schema 33 | 34 | def run(args) do 35 | if Mix.Project.umbrella?() do 36 | Mix.raise "mix phx.gen.embedded can only be run inside an application directory" 37 | end 38 | 39 | schema = build(args, []) 40 | 41 | paths = Mix.Phoenix.generator_paths() 42 | 43 | prompt_for_conflicts(schema) 44 | 45 | copy_new_files(schema, paths, schema: schema) 46 | end 47 | 48 | def build(args, _opts), do: Gen.Schema.build(args, [embedded: true], Gen.Schema) 49 | 50 | defp prompt_for_conflicts(schema) do 51 | schema 52 | |> files_to_be_generated() 53 | |> Mix.Phoenix.prompt_for_conflicts() 54 | end 55 | 56 | def files_to_be_generated(%Schema{} = schema) do 57 | [{:eex, "embedded_schema.ex", schema.file}] 58 | end 59 | 60 | def copy_new_files(%Schema{} = schema, paths, binding) do 61 | files = files_to_be_generated(schema) 62 | Mix.Phoenix.copy_from(paths,"priv/templates/phx.gen.embedded", "", binding, files) 63 | 64 | schema 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/mix/tasks/phx.digest.clean.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Phx.Digest.Clean do 2 | use Mix.Task 3 | @default_output_path "priv/static" 4 | @default_age 3600 5 | @default_keep 2 6 | 7 | @shortdoc "Removes old versions of static assets." 8 | @recursive true 9 | 10 | @moduledoc """ 11 | Removes old versions of compiled assets. 12 | 13 | By default, it will keep the latest version and 14 | 2 previous versions as well as any digest created 15 | in the last hour. 16 | 17 | mix phx.digest.clean 18 | mix phx.digest.clean -o /www/public 19 | mix phx.digest.clean --age 600 --keep 3 20 | 21 | ## Options 22 | 23 | * `-o, --output` - indicates the path to your compiled 24 | assets directory. Defaults to `priv/static`. 25 | 26 | * `--age` - specifies a maximum age (in seconds) for assets. 27 | Files older than age that are not in the last `--keep` versions 28 | will be removed. Defaults to 3600 (1 hour). 29 | 30 | * `--keep` - specifies how many previous versions of assets to keep. 31 | Defaults to 2 previous version. 32 | 33 | """ 34 | def run(args) do 35 | switches = [output: :string, age: :integer, keep: :integer] 36 | {opts, _, _} = OptionParser.parse(args, switches: switches, aliases: [o: :output]) 37 | output_path = opts[:output] || @default_output_path 38 | age = opts[:age] || @default_age 39 | keep = opts[:keep] || @default_keep 40 | 41 | {:ok, _} = Application.ensure_all_started(:phoenix) 42 | 43 | case Phoenix.Digester.clean(output_path, age, keep) do 44 | :ok -> 45 | # We need to call build structure so everything we have cleaned from 46 | # priv is removed from _build in case we have build_embedded set to 47 | # true. In case it's not true, build structure is mostly a no-op, so we 48 | # are fine. 49 | Mix.Project.build_structure() 50 | Mix.shell.info [:green, "Clean complete for #{inspect output_path}"] 51 | {:error, :invalid_path} -> 52 | Mix.shell.error "The output path #{inspect output_path} does not exist" 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/mix/phoenix/context.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Phoenix.Context do 2 | @moduledoc false 3 | 4 | alias Mix.Phoenix.{Context, Schema} 5 | 6 | defstruct name: nil, 7 | module: nil, 8 | schema: nil, 9 | alias: nil, 10 | base_module: nil, 11 | web_module: nil, 12 | basename: nil, 13 | file: nil, 14 | test_file: nil, 15 | dir: nil, 16 | generate?: true, 17 | context_app: nil, 18 | opts: [] 19 | 20 | def valid?(context) do 21 | context =~ ~r/^[A-Z]\w*(\.[A-Z]\w*)*$/ 22 | end 23 | 24 | def new(context_name, %Schema{} = schema, opts) do 25 | ctx_app = opts[:context_app] || Mix.Phoenix.context_app() 26 | base = Module.concat([Mix.Phoenix.context_base(ctx_app)]) 27 | module = Module.concat(base, context_name) 28 | alias = module |> Module.split() |> tl() |> Module.concat() 29 | basename = Phoenix.Naming.underscore(context_name) 30 | dir = Mix.Phoenix.context_lib_path(ctx_app, basename) 31 | test_dir = Mix.Phoenix.context_app_path(ctx_app, "test") 32 | file = Path.join([dir, basename <> ".ex"]) 33 | test_file = Path.join([test_dir, basename <> "_test.exs"]) 34 | generate? = Keyword.get(opts, :context, true) 35 | 36 | %Context{ 37 | name: context_name, 38 | module: module, 39 | schema: schema, 40 | alias: alias, 41 | base_module: base, 42 | web_module: web_module(), 43 | basename: basename, 44 | file: file, 45 | test_file: test_file, 46 | dir: dir, 47 | generate?: generate?, 48 | context_app: ctx_app, 49 | opts: opts} 50 | end 51 | 52 | def pre_existing?(%Context{file: file}), do: File.exists?(file) 53 | 54 | def pre_existing_tests?(%Context{test_file: file}), do: File.exists?(file) 55 | 56 | defp web_module do 57 | base = Module.concat([Mix.Phoenix.base()]) 58 | case base |> Module.split() |> Enum.reverse() do 59 | ["Web" | _] -> base 60 | _ -> Module.concat(base, "Web") 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/support/http_client.exs: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Integration.HTTPClient do 2 | @doc """ 3 | Performs HTTP Request and returns Response 4 | 5 | * method - The http method, for example :get, :post, :put, etc 6 | * url - The string url, for example "http://example.com" 7 | * headers - The map of headers 8 | * body - The optional string body. If the body is a map, it is converted 9 | to a URI encoded string of parameters 10 | 11 | ## Examples 12 | 13 | iex> HTTPClient.request(:get, "http://127.0.0.1", %{}) 14 | {:ok, %Response{..}) 15 | 16 | iex> HTTPClient.request(:post, "http://127.0.0.1", %{}, param1: "val1") 17 | {:ok, %Response{..}) 18 | 19 | iex> HTTPClient.request(:get, "http://unknownhost", %{}, param1: "val1") 20 | {:error, ...} 21 | 22 | """ 23 | def request(method, url, headers, body \\ "") 24 | def request(method, url, headers, body) when is_map body do 25 | request(method, url, headers, URI.encode_query(body)) 26 | end 27 | def request(method, url, headers, body) do 28 | url = String.to_charlist(url) 29 | headers = headers |> Map.put_new("content-type", "text/html") 30 | ct_type = headers["content-type"] |> String.to_charlist 31 | 32 | header = Enum.map headers, fn {k, v} -> 33 | {String.to_charlist(k), String.to_charlist(v)} 34 | end 35 | 36 | # Generate a random profile per request to avoid reuse 37 | profile = :crypto.strong_rand_bytes(4) |> Base.encode16 |> String.to_atom 38 | {:ok, pid} = :inets.start(:httpc, profile: profile) 39 | 40 | resp = 41 | case method do 42 | :get -> :httpc.request(:get, {url, header}, [], [body_format: :binary], pid) 43 | _ -> :httpc.request(method, {url, header, ct_type, body}, [], [body_format: :binary], pid) 44 | end 45 | 46 | :inets.stop(:httpc, pid) 47 | format_resp(resp) 48 | end 49 | 50 | defp format_resp({:ok, {{_http, status, _status_phrase}, headers, body}}) do 51 | {:ok, %{status: status, headers: headers, body: body}} 52 | end 53 | defp format_resp({:error, reason}), do: {:error, reason} 54 | end 55 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, optional: false]}]}, 2 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, 3 | "earmark": {:hex, :earmark, "1.1.1", "433136b7f2e99cde88b745b3a0cfc3fbc81fe58b918a09b40fce7f00db4d8187", [:mix], []}, 4 | "ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, 5 | "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []}, 6 | "inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]}, 7 | "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []}, 8 | "phoenix_guides": {:git, "https://github.com/phoenixframework/phoenix_guides.git", "7d0e9f72197473a67371fabde18dc0b3eae86920", []}, 9 | "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, 10 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []}, 11 | "plug": {:hex, :plug, "1.3.2", "8391d8ba2e2c187de069211110a882599e851f64550c556163b5130e1e2dbc1b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, 12 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, 13 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []}, 14 | "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "f6892c8b55004008ce2d52be7d98b156f3e34569", []}} 15 | -------------------------------------------------------------------------------- /installer/lib/phx_new/umbrella.ex: -------------------------------------------------------------------------------- 1 | defmodule Phx.New.Umbrella do 2 | @moduledoc false 3 | use Phx.New.Generator 4 | alias Phx.New.{Ecto, Web, Project} 5 | 6 | template :new, [ 7 | {:eex, "phx_umbrella/gitignore", :project, ".gitignore"}, 8 | {:eex, "phx_umbrella/config/config.exs", :project, "config/config.exs"}, 9 | {:eex, "phx_umbrella/config/dev.exs", :project, "config/dev.exs"}, 10 | {:eex, "phx_umbrella/config/test.exs", :project, "config/test.exs"}, 11 | {:eex, "phx_umbrella/config/prod.exs", :project, "config/prod.exs"}, 12 | {:eex, "phx_umbrella/mix.exs", :project, "mix.exs"}, 13 | {:eex, "phx_umbrella/README.md", :project, "README.md"}, 14 | ] 15 | 16 | def prepare_project(%Project{app: app} = project) when not is_nil(app) do 17 | project 18 | |> put_app() 19 | |> put_web() 20 | |> put_root_app() 21 | end 22 | defp put_app(project) do 23 | project_path = Path.expand(project.base_path <> "_umbrella") 24 | app_path = Path.join(project_path, "apps/#{project.app}") 25 | 26 | %Project{project | 27 | in_umbrella?: true, 28 | app_path: app_path, 29 | project_path: project_path} 30 | end 31 | def put_web(%Project{opts: opts} = project) do 32 | web_app = :"#{project.app}_web" 33 | web_namespace = Module.concat([opts[:web_module] || "#{project.app_mod}.Web"]) 34 | 35 | %Project{project | 36 | web_app: web_app, 37 | web_namespace: web_namespace, 38 | web_path: Path.join(project.project_path, "apps/#{web_app}/")} 39 | end 40 | 41 | defp put_root_app(%Project{app: app} = project) do 42 | %Project{project | 43 | root_app: :"#{app}_umbrella", 44 | root_mod: Module.concat(project.app_mod, "Umbrella")} 45 | end 46 | 47 | def generate(%Project{} = project) do 48 | if in_umbrella?(project.project_path) do 49 | Mix.raise "Unable to nest umbrella project within apps" 50 | end 51 | copy_from project, __MODULE__, template_files(:new) 52 | 53 | project 54 | |> Web.generate() 55 | |> Ecto.generate() 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/phoenix/endpoint/handler.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Endpoint.Handler do 2 | @moduledoc """ 3 | API for exporting a webserver. 4 | 5 | A handler will need to implement a `child_spec/3` function which takes: 6 | 7 | * the scheme of the endpoint :http or :https 8 | * phoenix top-most endpoint module 9 | * phoenix app configuration for the specified scheme 10 | 11 | It has to return a supervisor child specification. 12 | """ 13 | 14 | @doc """ 15 | Provides a server child specification to be started under the endpoint. 16 | """ 17 | @callback child_spec(scheme :: atom, endpoint :: module, config :: Keyword.t) :: Supervisor.Spec.spec 18 | 19 | use Supervisor 20 | require Logger 21 | 22 | @doc false 23 | def start_link(otp_app, endpoint, opts \\ []) do 24 | Supervisor.start_link(__MODULE__, {otp_app, endpoint}, opts) 25 | end 26 | 27 | @doc false 28 | def init({otp_app, endpoint}) do 29 | handler = endpoint.config(:handler) 30 | children = 31 | for {scheme, port} <- [http: 4000, https: 4040], 32 | config = endpoint.config(scheme) do 33 | handler.child_spec(scheme, endpoint, default(config, otp_app, port)) 34 | end 35 | supervise(children, strategy: :one_for_one) 36 | end 37 | 38 | defp default(config, otp_app, port) when is_list(config) do 39 | {config_keywords, config_other} = Enum.partition(config, &keyword_item?/1) 40 | 41 | config_keywords = 42 | config_keywords 43 | |> Keyword.put_new(:otp_app, otp_app) 44 | |> Keyword.put_new(:port, port) 45 | 46 | config_keywords 47 | |> Keyword.put(:port, to_port(config_keywords[:port])) 48 | |> Kernel.++(config_other) 49 | end 50 | 51 | defp keyword_item?({key, _}) when is_atom(key), do: true 52 | defp keyword_item?(_), do: false 53 | 54 | # TODO v1.4: Deprecate {:system, env_var} 55 | defp to_port(nil), do: raise "server can't start because :port in config is nil, please use a valid port number" 56 | defp to_port(binary) when is_binary(binary), do: String.to_integer(binary) 57 | defp to_port(integer) when is_integer(integer), do: integer 58 | defp to_port({:system, env_var}), do: to_port(System.get_env(env_var)) 59 | end 60 | -------------------------------------------------------------------------------- /priv/templates/phx.gen.json/controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Controller do 2 | use <%= inspect context.web_module %>, :controller 3 | 4 | alias <%= inspect context.module %> 5 | alias <%= inspect schema.module %> 6 | 7 | action_fallback <%= inspect context.web_module %>.FallbackController 8 | 9 | def index(conn, _params) do 10 | <%= schema.plural %> = <%= inspect context.alias %>.list_<%= schema.plural %>() 11 | render(conn, "index.json", <%= schema.plural %>: <%= schema.plural %>) 12 | end 13 | 14 | def create(conn, %{<%= inspect schema.singular %> => <%= schema.singular %>_params}) do 15 | with {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} <- <%= inspect context.alias %>.create_<%= schema.singular %>(<%= schema.singular %>_params) do 16 | conn 17 | |> put_status(:created) 18 | |> put_resp_header("location", <%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>)) 19 | |> render("show.json", <%= schema.singular %>: <%= schema.singular %>) 20 | end 21 | end 22 | 23 | def show(conn, %{"id" => id}) do 24 | <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id) 25 | render(conn, "show.json", <%= schema.singular %>: <%= schema.singular %>) 26 | end 27 | 28 | def update(conn, %{"id" => id, <%= inspect schema.singular %> => <%= schema.singular %>_params}) do 29 | <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id) 30 | 31 | with {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} <- <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params) do 32 | render(conn, "show.json", <%= schema.singular %>: <%= schema.singular %>) 33 | end 34 | end 35 | 36 | def delete(conn, %{"id" => id}) do 37 | <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id) 38 | with {:ok, %<%= inspect schema.alias %>{}} <- <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>) do 39 | send_resp(conn, :no_content, "") 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/fixtures/views.exs: -------------------------------------------------------------------------------- 1 | defmodule MyApp.View do 2 | use Phoenix.View, root: "test/fixtures/templates" 3 | 4 | def escaped_title(title) do 5 | {:safe, Plug.HTML.html_escape(title)} 6 | end 7 | end 8 | 9 | defmodule MyApp.LayoutView do 10 | use Phoenix.View, root: "test/fixtures/templates" 11 | 12 | def default_title do 13 | "MyApp" 14 | end 15 | end 16 | 17 | defmodule MyApp.User do 18 | defstruct name: "name" 19 | end 20 | 21 | defmodule MyApp.PathView do 22 | use Phoenix.View, root: "test/fixtures/templates", path: "" 23 | end 24 | 25 | defmodule MyApp.UserView do 26 | use Phoenix.View, root: "test/fixtures/templates", pattern: "**/*" 27 | 28 | def escaped_title(title) do 29 | {:safe, Plug.HTML.html_escape(title)} 30 | end 31 | 32 | def render("show.text", %{user: user, prefix: prefix}) do 33 | "show user: " <> prefix <> user.name 34 | end 35 | 36 | def render("show.text", %{user: user}) do 37 | "show user: " <> user.name 38 | end 39 | 40 | def render("data.text", %{data: data}) do 41 | "show data: " <> data.name 42 | end 43 | 44 | def render("edit.html", %{} = assigns) do 45 | "EDIT#{assigns[:layout]} - #{assigns[:title]}" 46 | end 47 | 48 | def render("existing.html", _), do: "rendered existing" 49 | 50 | def render("inner.html", assigns) do 51 | """ 52 | View module is #{assigns.view_module} and view template is #{assigns.view_template} 53 | """ 54 | end 55 | 56 | def render("render_template.html" = tpl, %{name: name}) do 57 | render_template(tpl, %{name: String.upcase(name)}) 58 | end 59 | end 60 | 61 | defmodule MyApp.Templates.UserView do 62 | use Phoenix.View, root: "test/fixtures" 63 | 64 | def escaped_title(title) do 65 | {:safe, Plug.HTML.html_escape(title)} 66 | end 67 | end 68 | 69 | defmodule MyApp.Nested.User do 70 | defstruct name: "nested name" 71 | end 72 | 73 | defmodule MyApp.Nested.UserView do 74 | use Phoenix.View, root: "test/fixtures/templates", namespace: MyApp.Nested 75 | 76 | def render("show.text", %{user: user}) do 77 | "show nested user: " <> user.name 78 | end 79 | 80 | def escaped_title(title) do 81 | {:safe, Plug.HTML.html_escape(title)} 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/phoenix/exceptions.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.NotAcceptableError do 2 | @moduledoc """ 3 | Raised when one of the `accept*` headers is not accepted by the server. 4 | 5 | This exception is commonly raised by `Phoenix.Controller.accepts/2` 6 | which negotiates the media types the server is able to serve with 7 | the contents the client is able to render. 8 | 9 | If you are seeing this error, you should check if you are listing 10 | the desired formats in your `:accepts` plug or if you are setting 11 | the proper accept header in the client. The exception contains the 12 | acceptable mime types in the `accepts` field. 13 | """ 14 | 15 | defexception message: nil, accepts: [], plug_status: 406 16 | end 17 | 18 | defmodule Phoenix.MissingParamError do 19 | @moduledoc """ 20 | Raised when a key is expected to be present in the request parameters, 21 | but is not. 22 | 23 | This exception is raised by `Phoenix.Controller.scrub_params/2` which: 24 | 25 | * Checks to see if the required_key is present (can be empty) 26 | * Changes all empty parameters to nils ("" -> nil). 27 | 28 | If you are seeing this error, you should handle the error and surface it 29 | to the end user. It means that there is a parameter missing from the request. 30 | """ 31 | 32 | defexception [:message, plug_status: 400] 33 | 34 | def exception([key: value]) do 35 | msg = "expected key #{inspect value} to be present in params, " <> 36 | "please send the expected key or adapt your scrub_params/2 call" 37 | %Phoenix.MissingParamError{message: msg} 38 | end 39 | end 40 | 41 | defmodule Phoenix.ActionClauseError do 42 | defexception [message: nil, plug_status: 400] 43 | 44 | def exception(opts) do 45 | controller = Keyword.fetch!(opts, :controller) 46 | action = Keyword.fetch!(opts, :action) 47 | params = Keyword.fetch!(opts, :params) 48 | msg = """ 49 | could not find a matching #{inspect controller}.#{action} clause 50 | to process request. This typically happens when there is a 51 | parameter mismatch but may also happen when any of the other 52 | action arguments do not match. The request parameters are: 53 | 54 | #{inspect params} 55 | """ 56 | %Phoenix.ActionClauseError{message: msg} 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/phoenix/endpoint/watcher.ex: -------------------------------------------------------------------------------- 1 | defmodule Phoenix.Endpoint.Watcher do 2 | @moduledoc false 3 | require Logger 4 | 5 | def start_link(cmd, args, opts) do 6 | Task.start_link(__MODULE__, :watch, [to_string(cmd), args, opts]) 7 | end 8 | 9 | def watch(cmd, args, opts) do 10 | merged_opts = Keyword.merge( 11 | [into: IO.stream(:stdio, :line), stderr_to_stdout: true], opts) 12 | :ok = validate(cmd, args, merged_opts) 13 | 14 | try do 15 | System.cmd(cmd, args, merged_opts) 16 | catch 17 | :error, :enoent -> 18 | relative = Path.relative_to_cwd(cmd) 19 | Logger.error "Could not start watcher #{inspect relative} from #{inspect cd(merged_opts)}, executable does not exist" 20 | exit(:shutdown) 21 | else 22 | {_, 0} -> 23 | :ok 24 | {_, _} -> 25 | # System.cmd returned a non-zero exit code 26 | # sleep for a couple seconds before exiting to ensure this doesn't 27 | # hit the supervisor's max_restarts / max_seconds limit 28 | Process.sleep(2000) 29 | exit(:watcher_command_error) 30 | end 31 | end 32 | 33 | # We specially handle node to make sure we 34 | # provide a good getting started experience. 35 | defp validate("node", [script|_], merged_opts) do 36 | script_path = Path.expand(script, cd(merged_opts)) 37 | 38 | cond do 39 | !System.find_executable("node") -> 40 | Logger.error "Could not start watcher because \"node\" is not available. Your Phoenix " <> 41 | "application is still running, however assets won't be compiled. " <> 42 | "You may fix this by installing \"node\" and then running \"cd assets && npm install\"." 43 | exit(:shutdown) 44 | 45 | not File.exists?(script_path) -> 46 | Logger.error "Could not start node watcher because script #{inspect script_path} does not " <> 47 | "exist. Your Phoenix application is still running, however assets " <> 48 | "won't be compiled. You may fix this by running \"cd assets && npm install\"." 49 | exit(:shutdown) 50 | 51 | true -> :ok 52 | end 53 | end 54 | 55 | defp validate(_cmd, _args, _opts) do 56 | :ok 57 | end 58 | 59 | defp cd(opts), do: opts[:cd] || File.cwd!() 60 | end 61 | --------------------------------------------------------------------------------