├── .gitignore ├── .travis.yml ├── README.md ├── lib └── firebird.ex ├── mix.exs └── template └── $PROJECT_NAME$ ├── .gitignore ├── Procfile ├── README.md ├── assets ├── css │ └── app.sass ├── js │ ├── app.js │ └── socket.js ├── package.json ├── static │ ├── images │ │ └── .gitkeep │ └── robots.txt └── webpack.config.js ├── bin ├── ci ├── heroku_compile_assets ├── setup └── update ├── config ├── .credo.exs ├── config.exs ├── dev.exs ├── prod.exs └── test.exs ├── elixir_buildpack.config ├── lib ├── $PROJECT_NAME$.ex ├── $PROJECT_NAME$ │ ├── application.ex │ ├── mailer.ex │ └── repo.ex ├── $PROJECT_NAME$_web.ex └── $PROJECT_NAME$_web │ ├── channels │ └── user_socket.ex │ ├── controllers │ └── page_controller.ex │ ├── endpoint.ex │ ├── gettext.ex │ ├── router.ex │ ├── templates │ ├── layout │ │ └── app.html.slim │ └── page │ │ └── index.html.slim │ └── views │ ├── error_helpers.ex │ ├── error_view.ex │ ├── layout_view.ex │ └── page_view.ex ├── mix.exs ├── phoenix_static_buildpack.config ├── priv ├── gettext │ ├── en │ │ └── LC_MESSAGES │ │ │ └── errors.po │ └── errors.pot └── repo │ ├── migrations │ └── .gitkeep │ └── seeds.exs └── test ├── $PROJECT_NAME$_web ├── controllers │ └── page_controller_test.exs └── views │ ├── error_view_test.exs │ ├── layout_view_test.exs │ └── page_view_test.exs ├── support ├── channel_case.ex ├── conn_case.ex ├── data_case.ex └── factory.ex └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.4.2 4 | otp_release: 5 | - 19.2 6 | sudo: false 7 | before_install: 8 | - nvm install 7 9 | - nvm use 7 10 | - npm i --global yarn 11 | cache: 12 | yarn: true 13 | before_script: 14 | - mix local.hex --force 15 | - mix archive.install hex mix_generator --force 16 | - mix archive.install hex mix_templates --force 17 | - mix template.install . 18 | - mix gen firebird travis 19 | script: 20 | - cd travis && bin/setup 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebird 2 | [![Build Status](https://travis-ci.org/infinitered/firebird.svg?branch=master)](https://travis-ci.org/infinitered/firebird) 3 | 4 | [Infinite Red's](https://infinite.red) boilerplate for new Phoenix projects, using Dave Thomas's excellent [Elixir project generator](https://pragdave.me/blog/2017/04/18/elixir-project-generator.html). 5 | 6 | ## Why Firebird? 7 | 8 | While Phoenix comes with some good defaults out of the box, if you're building a non-trivial application you'll need to make some different choices. Infinite Red ships Elixir/Phoenix web applications and websites regularly and we've found that using Yarn, Webpack, and some other tooling makes our lives easier. 9 | 10 | We plan to keep Firebird up to date and a leading boilerplate for Phoenix applications. Star (and watch, if you want) this repo to show support and keep tabs on our progress! 11 | 12 | ## Highlights 13 | 14 | - Phoenix 1.3 15 | - [Yarn](https://github.com/yarnpkg/yarn) (not npm) 16 | - [Webpack 2](https://github.com/webpack/webpack) 17 | - [Swoosh](https://github.com/swoosh/swoosh) for email 18 | - [Sass](http://sass-lang.com/guide) with [Bourbon](http://bourbon.io/) and [Neat](http://neat.bourbon.io/) 19 | - [Slim](https://github.com/slime-lang/phoenix_slime) templates 20 | - [Heroku](https://heroku.com) Procfile, buildpack configs 21 | - Batteries included (scripts) 22 | 23 | ## Install 24 | 25 | _Note that Firebird requires Elixir 1.4.0+._ 26 | 27 | ``` 28 | $ mix archive.install hex mix_generator 29 | $ mix archive.install hex mix_templates 30 | $ mix template.install github infinitered/firebird 31 | ``` 32 | 33 | ## Use 34 | 35 | ``` 36 | $ mix gen firebird 37 | $ cd 38 | $ bin/setup 39 | ``` 40 | 41 | ## License 42 | 43 | The MIT License (MIT), copyright (c) 2017 Infinite Red, Inc. 44 | -------------------------------------------------------------------------------- /lib/firebird.ex: -------------------------------------------------------------------------------- 1 | defmodule Firebird do 2 | @moduledoc false # File.read!(Path.join([__DIR__, "../README.md"])) 3 | 4 | use MixTemplates, 5 | name: :firebird, 6 | short_desc: "Infinite Red boilerplate for Phoenix 1.3 apps", 7 | source_dir: "../template" 8 | end 9 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Firebird.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :firebird, 7 | version: "0.1.0", 8 | deps: deps(), 9 | elixir: "~> 1.4", 10 | package: package(), 11 | description: "Infinite Red boilerplate for Phoenix 1.3 apps", 12 | build_embedded: Mix.env == :prod, 13 | start_permanent: Mix.env == :prod, 14 | ] 15 | end 16 | 17 | defp package do 18 | [ 19 | name: :firebird, 20 | files: ["lib", "mix.exs", "README.md", "LICENSE.md", "template"], 21 | maintainers: ["Daniel Berkompas "], 22 | licenses: ["MIT"], 23 | links: %{ 24 | "GitHub" => "https://github.com/infinitered/firebird", 25 | } 26 | ] 27 | end 28 | 29 | defp deps do 30 | [ 31 | {:mix_templates, "> 0.0.0", app: false}, 32 | {:ex_doc, "> 0.0.0", only: [:dev, :test]}, 33 | ] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/.gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /cover 4 | /db 5 | /deps 6 | /doc 7 | *.beam 8 | *.env 9 | *.ez 10 | 11 | # Generate on crash by the VM 12 | erl_crash.dump 13 | 14 | # Static artifacts 15 | /assets/node_modules 16 | 17 | # Since we are building assets from assets/ 18 | # we ignore priv/static. You may want to comment 19 | # this depending on your deployment strategy. 20 | /priv/static/ 21 | 22 | *.DS_Store -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/Procfile: -------------------------------------------------------------------------------- 1 | web: mix phx.server 2 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/README.md: -------------------------------------------------------------------------------- 1 | # <%= @project_name_camel_case %> 2 | 3 | TODO: Describe your project 4 | 5 | ## Setup 6 | 7 | ``` 8 | $ cd <%= @project_name %> 9 | $ bin/setup 10 | ``` 11 | 12 | ## Scripts 13 | 14 | - `bin/setup`: Install all dependencies and run tests. Use this on your CI server. 15 | - `bin/update`: Update all dependencies, after pulling or merging. 16 | - `bin/ci`: Run this locally to run all commands run by CI. 17 | - `bin/heroku_compile_assets`: Compile assets on Heroku for production. 18 | - `mix phx.server`: Start Phoenix server. 19 | - `mix ecto.reset`: Drop and reseed the database. 20 | - `mix test`: Run the Elixir tests. 21 | - `cd assets/ && yarn run test`: Run Javascript tests. 22 | 23 | ## Deploy 24 | 25 | ``` 26 | $ heroku create 27 | $ heroku buildpacks:set https://github.com/HashNuke/heroku-buildpack-elixir 28 | $ heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static 29 | $ git push heroku master 30 | ``` 31 | 32 | Generated using the [Firebird](https://github.com/infinitered/firebird) template. 33 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/assets/css/app.sass: -------------------------------------------------------------------------------- 1 | /* This file is for your main application css. */ 2 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/assets/js/app.js: -------------------------------------------------------------------------------- 1 | // Brunch automatically concatenates all files in your 2 | // watched paths. Those paths can be configured at 3 | // config.paths.watched in 'brunch-config.js'. 4 | // 5 | // However, those files will only be executed if 6 | // explicitly imported. The only exception are files 7 | // in vendor, which are never wrapped in imports and 8 | // therefore are always executed. 9 | 10 | // Import dependencies 11 | // 12 | // If you no longer want to use a dependency, remember 13 | // to also remove its path from 'config.paths.watched'. 14 | import 'phoenix_html' 15 | 16 | // Import local files 17 | // 18 | // Local files can be imported directly using relative 19 | // paths './socket' or full ones 'web/static/js/socket'. 20 | 21 | // import socket from './socket' 22 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/assets/js/socket.js: -------------------------------------------------------------------------------- 1 | // NOTE: The contents of this file will only be executed if 2 | // you uncomment its entry in 'assets/js/app.js'. 3 | 4 | // To use Phoenix channels, the first step is to import Socket 5 | // and connect at the socket path in 'lib/web/endpoint.ex': 6 | import {Socket} from 'phoenix' 7 | 8 | let socket = new Socket('/socket', {params: {token: window.userToken}}) 9 | 10 | // When you connect, you'll often need to authenticate the client. 11 | // For example, imagine you have an authentication plug, `MyAuth`, 12 | // which authenticates the session and assigns a `:current_user`. 13 | // If the current user exists you can assign the user's token in 14 | // the connection for use in the layout. 15 | // 16 | // In your 'lib/web/router.ex': 17 | // 18 | // pipeline :browser do 19 | // ... 20 | // plug MyAuth 21 | // plug :put_user_token 22 | // end 23 | // 24 | // defp put_user_token(conn, _) do 25 | // if current_user = conn.assigns[:current_user] do 26 | // token = Phoenix.Token.sign(conn, 'user socket', current_user.id) 27 | // assign(conn, :user_token, token) 28 | // else 29 | // conn 30 | // end 31 | // end 32 | // 33 | // Now you need to pass this token to JavaScript. You can do so 34 | // inside a script tag in 'lib/web/templates/layout/app.html.eex': 35 | // 36 | // 37 | // 38 | // You will need to verify the user token in the 'connect/2' function 39 | // in 'lib/web/channels/user_socket.ex': 40 | // 41 | // def connect(%{'token' => token}, socket) do 42 | // # max_age: 1209600 is equivalent to two weeks in seconds 43 | // case Phoenix.Token.verify(socket, 'user socket', token, max_age: 1209600) do 44 | // {:ok, user_id} -> 45 | // {:ok, assign(socket, :user, user_id)} 46 | // {:error, reason} -> 47 | // :error 48 | // end 49 | // end 50 | // 51 | // Finally, pass the token on connect as below. Or remove it 52 | // from connect if you don't care about authentication. 53 | 54 | socket.connect() 55 | 56 | // Now that you are connected, you can join channels with a topic: 57 | let channel = socket.channel('topic:subtopic', {}) 58 | channel.join() 59 | .receive('ok', resp => { console.log('Joined successfully', resp) }) 60 | .receive('error', resp => { console.log('Unable to join', resp) }) 61 | 62 | export default socket 63 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "phoenix": "file:../deps/phoenix", 4 | "phoenix_html": "file:../deps/phoenix_html" 5 | }, 6 | "devDependencies": { 7 | "bourbon": "^4.3.4", 8 | "bourbon-neat": "^2.0.0", 9 | "babel-core": "^6.1.2", 10 | "babel-loader": "^7.0.0", 11 | "babel-plugin-transform-object-rest-spread": "^6.3.13", 12 | "babel-preset-es2015": "^6.1.2", 13 | "copy-webpack-plugin": "^4.0.1", 14 | "css-loader": "^0.28.0", 15 | "extract-text-webpack-plugin": "^2.1.0", 16 | "import-glob-loader": "^1.1.0", 17 | "node-sass": "^4.5.2", 18 | "sass-loader": "^6.0.3", 19 | "standard": "^10.0.2", 20 | "style-loader": "^0.16.1", 21 | "webpack": "^2.4.1", 22 | "phantomjs-prebuilt": "^2.1.7" 23 | }, 24 | "license": "UNLICENSED", 25 | "private": true, 26 | "repository": {}, 27 | "scripts": { 28 | "test": "standard && ./node_modules/webpack/bin/webpack.js", 29 | "compile": "webpack -p" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/assets/static/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitered/firebird/679ca2f21eb0a5dc4bfc15c90f2c3c2abd2fe49b/template/$PROJECT_NAME$/assets/static/images/.gitkeep -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/assets/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 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/assets/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | var CopyWebpackPlugin = require('copy-webpack-plugin') 4 | var env = process.env.MIX_ENV || 'dev' 5 | var isProduction = (env === 'prod') 6 | 7 | module.exports = { 8 | entry: { 9 | 'app': ['./js/app.js', './css/app.sass'] 10 | }, 11 | 12 | output: { 13 | path: path.resolve(__dirname, '../priv/static/'), 14 | filename: 'js/[name].js' 15 | }, 16 | 17 | devtool: 'source-map', 18 | 19 | module: { 20 | rules: [{ 21 | test: /\.(sass|scss)$/, 22 | include: /css/, 23 | use: ExtractTextPlugin.extract({ 24 | fallback: 'style-loader', 25 | use: [ 26 | {loader: 'css-loader'}, 27 | { 28 | loader: 'sass-loader', 29 | options: { 30 | includePaths: [ 31 | require('bourbon').includePaths, 32 | require('bourbon-neat').includePaths 33 | ], 34 | sourceComments: !isProduction 35 | } 36 | } 37 | ] 38 | }) 39 | }, { 40 | test: /\.js?$/, 41 | include: /js/, 42 | use: [{ 43 | loader: 'babel-loader', 44 | query: { 45 | presets: ['es2015'], 46 | plugins: [], 47 | cacheDirectory: true 48 | } 49 | }] 50 | }] 51 | }, 52 | 53 | plugins: [ 54 | new CopyWebpackPlugin([{ from: './static' }]), 55 | new ExtractTextPlugin('css/app.css') 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/bin/ci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Continuous Integration Script 4 | # 5 | # This script contains all the test commands for this app, to be run on CI. 6 | # This way, developers can run all the same commands that CI uses to determine 7 | # if the build passes. 8 | 9 | cd assets/ && { 10 | yarn run test || { echo 'Javascript tests failed!'; exit 1; }; 11 | cd -; 12 | } 13 | 14 | MIX_ENV=test mix compile --warnings-as-errors --force || { echo 'Please fix all compiler warnings.'; exit 1; } 15 | MIX_ENV=test mix credo --strict --ignore design,consistency || { echo 'Elixir code failed Credo linting. See warnings above.'; exit 1; } 16 | MIX_ENV=test mix docs || { echo 'Elixir HTML docs were not generated!'; exit 1; } 17 | mix test || { echo 'Elixir tests failed!'; exit 1; } -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/bin/heroku_compile_assets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script is run on Heroku to compile the assets. 4 | # It is necessary because we need the sass gem installed in order 5 | # to compile sass files. Otherwise, we could use the default 6 | # buildpack script. 7 | # 8 | # See phoenix_static_buildpack.config for a little more context. 9 | 10 | cd $assets_dir 11 | MIX_ENV=prod ./node_modules/webpack/bin/webpack.js --progress --color; 12 | 13 | cd ../ 14 | mix "${phoenix_ex}.digest" 15 | 16 | # We don't need the asset files after compile. This helps keep 17 | # the heroku slug size down. 18 | rm -rf $assets_dir -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Setup Script 4 | # 5 | # Runs all the needed commands to set up a developer's system to run this app. 6 | # Customize this as your app grows. 7 | 8 | # Ensure Elixir is installed 9 | command -v elixir >/dev/null 2>&1 || { 10 | echo "This app requires Elixir, but it was not found on your system." 11 | echo "Install it using the instructions at http://elixir-lang.org" 12 | exit 1 13 | } 14 | 15 | # Ensure Node.js is installed 16 | command -v npm >/dev/null 2>&1 || { 17 | echo "This app requires Node.js to build assets, but it was not found on your system." 18 | echo "Install it using the instructions at http://nodejs.org" 19 | exit 1 20 | } 21 | 22 | # Ensure Yarn is installed 23 | command -v yarn >/dev/null 2>&1 || { 24 | echo "This app requires Yarn package manager, but it was not found on your system." 25 | echo "Install it using the instructions at https://yarnpkg.com/en/" 26 | exit 1 27 | } 28 | 29 | echo "----------------------------------------------------------" 30 | echo "Ensuring Hex is installed..." 31 | echo "----------------------------------------------------------" 32 | mix local.hex --force 33 | mix local.rebar --force 34 | echo "Done!" 35 | 36 | echo "----------------------------------------------------------" 37 | echo "Installing Mix dependencies..." 38 | echo "----------------------------------------------------------" 39 | 40 | mix deps.get || { echo "Mix dependencies could not be installed!"; exit 1; } 41 | 42 | echo "----------------------------------------------------------" 43 | echo "Installing Node.js dependencies (for assets)" 44 | echo "----------------------------------------------------------" 45 | 46 | cd assets/ && { 47 | yarn install || { echo "Node dependencies could not be installed!"; exit 1; }; 48 | cd -; 49 | } 50 | 51 | if [ ! $CI ]; then 52 | echo "----------------------------------------------------------" 53 | echo "Setting up Postgres with seed data..." 54 | echo "----------------------------------------------------------" 55 | mix ecto.setup || { echo "Database could not be set up!"; exit 1; } 56 | fi 57 | 58 | echo "----------------------------------------------------------" 59 | echo "Running tests to verify setup is complete..." 60 | echo "----------------------------------------------------------" 61 | 62 | bin/ci || { exit 1; } 63 | 64 | echo "----------------------------------------------------------" 65 | echo "Setup complete!" 66 | echo "----------------------------------------------------------" 67 | 68 | echo "Run the web server with mix phx.server, and view localhost:4000" -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Update Script 4 | # 5 | # This script is intended to help keep your local dependencies up to date after 6 | # merging branches or pulling down new changes. This is particularly useful for 7 | # front-end developers working on the app, who may not understand all the 8 | # dependency management on the backend. 9 | 10 | echo "Ensuring your dependencies and DB schema are up to date..." 11 | 12 | # Get any new Mix dependencies 13 | mix deps.get 14 | 15 | # Get any new Node.js dependencies 16 | cd assets/ && { 17 | yarn install; 18 | cd -; 19 | } 20 | 21 | # Run any new database migrations 22 | mix ecto.migrate 23 | 24 | echo "Finished! You should be up to date now." -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/config/.credo.exs: -------------------------------------------------------------------------------- 1 | # This file contains the configuration for Credo and you are probably reading 2 | # this after creating it with `mix credo.gen.config`. 3 | # 4 | # If you find anything wrong or unclear in this file, please report an 5 | # issue on GitHub: https://github.com/rrrene/credo/issues 6 | # 7 | %{ 8 | # 9 | # You can have as many configs as you like in the `configs:` field. 10 | configs: [ 11 | %{ 12 | # 13 | # Run any config using `mix credo -C `. If no config name is given 14 | # "default" is used. 15 | name: "default", 16 | # 17 | # these are the files included in the analysis 18 | files: %{ 19 | # 20 | # you can give explicit globs or simply directories 21 | # in the latter case `**/*.{ex,exs}` will be used 22 | included: ["lib", "web"], 23 | excluded: [~r"/_build/", ~r"/deps/"] 24 | }, 25 | # 26 | # If you create your own checks, you must specify the source files for 27 | # them here, so they can be loaded by Credo before running the analysis. 28 | requires: [], 29 | # 30 | # Credo automatically checks for updates, like e.g. Hex does. 31 | # You can disable this behaviour below: 32 | check_for_updates: true, 33 | # 34 | # You can customize the parameters of any check by adding a second element 35 | # to the tuple. 36 | # 37 | # To disable a check put `false` as second element: 38 | # 39 | # {Credo.Check.Design.DuplicatedCode, false} 40 | # 41 | checks: [ 42 | {Credo.Check.Consistency.ExceptionNames}, 43 | {Credo.Check.Consistency.LineEndings}, 44 | {Credo.Check.Consistency.SpaceAroundOperators}, 45 | {Credo.Check.Consistency.SpaceInParentheses}, 46 | {Credo.Check.Consistency.TabsOrSpaces}, 47 | 48 | # For some checks, like AliasUsage, you can only customize the priority 49 | # Priority values are: `low, normal, high, higher` 50 | {Credo.Check.Design.AliasUsage, false}, 51 | 52 | # For others you can set parameters 53 | 54 | # If you don't want the `setup` and `test` macro calls in ExUnit tests 55 | # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just 56 | # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. 57 | {Credo.Check.Design.DuplicatedCode, excluded_macros: []}, 58 | 59 | # You can also customize the exit_status of each check. 60 | # If you don't want TODO comments to cause `mix credo` to fail, just 61 | # set this value to 0 (zero). 62 | {Credo.Check.Design.TagTODO, exit_status: 2}, 63 | {Credo.Check.Design.TagFIXME}, 64 | 65 | {Credo.Check.Readability.FunctionNames}, 66 | {Credo.Check.Readability.LargeNumbers, false}, 67 | {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 120}, 68 | {Credo.Check.Readability.ModuleAttributeNames}, 69 | {Credo.Check.Readability.ModuleDoc}, 70 | {Credo.Check.Readability.ModuleNames}, 71 | {Credo.Check.Readability.ParenthesesInCondition}, 72 | {Credo.Check.Readability.PredicateFunctionNames}, 73 | {Credo.Check.Readability.TrailingBlankLine, false}, 74 | {Credo.Check.Readability.TrailingWhiteSpace, false}, 75 | {Credo.Check.Readability.VariableNames}, 76 | {Credo.Check.Readability.Specs, false}, 77 | {Credo.Check.Readability.StringSigils, false}, 78 | {Credo.Check.Readability.PreferImplicitTry, false}, 79 | 80 | {Credo.Check.Refactor.ABCSize, max_size: 40}, 81 | {Credo.Check.Refactor.CondStatements}, 82 | {Credo.Check.Refactor.FunctionArity}, 83 | {Credo.Check.Refactor.MatchInCondition}, 84 | {Credo.Check.Refactor.PipeChainStart}, 85 | {Credo.Check.Refactor.CyclomaticComplexity}, 86 | {Credo.Check.Refactor.NegatedConditionsInUnless}, 87 | {Credo.Check.Refactor.NegatedConditionsWithElse}, 88 | {Credo.Check.Refactor.Nesting}, 89 | {Credo.Check.Refactor.UnlessWithElse}, 90 | {Credo.Check.Refactor.VariableRebinding, false}, 91 | 92 | {Credo.Check.Warning.IExPry}, 93 | {Credo.Check.Warning.IoInspect}, 94 | {Credo.Check.Warning.NameRedeclarationByAssignment}, 95 | {Credo.Check.Warning.NameRedeclarationByCase}, 96 | {Credo.Check.Warning.NameRedeclarationByDef}, 97 | {Credo.Check.Warning.NameRedeclarationByFn}, 98 | {Credo.Check.Warning.OperationOnSameValues}, 99 | {Credo.Check.Warning.BoolOperationOnSameValues}, 100 | {Credo.Check.Warning.UnusedEnumOperation}, 101 | {Credo.Check.Warning.UnusedKeywordOperation}, 102 | {Credo.Check.Warning.UnusedListOperation}, 103 | {Credo.Check.Warning.UnusedStringOperation}, 104 | {Credo.Check.Warning.UnusedTupleOperation}, 105 | {Credo.Check.Warning.OperationWithConstantResult}, 106 | 107 | # Custom checks can be created using `mix credo.gen.check`. 108 | # 109 | ] 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/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 | # Configure Phoenix to support Slim templates 9 | config :phoenix, :template_engines, 10 | slim: PhoenixSlime.Engine, 11 | slime: PhoenixSlime.Engine 12 | 13 | # General application configuration 14 | config :<%= @project_name %>, 15 | ecto_repos: [<%= @project_name_camel_case %>.Repo] 16 | 17 | # Configures the endpoint 18 | config :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, 19 | url: [host: "localhost"], 20 | secret_key_base: "/XS66yr6BbUsl0+u0pjJIxa0lK5whxGGWGZLuWuBSCbnmNcsRLz+gvyRathkiAM8", 21 | render_errors: [view: <%= @project_name_camel_case %>Web.ErrorView, accepts: ~w(html json)], 22 | pubsub: [name: <%= @project_name_camel_case %>.PubSub, 23 | adapter: Phoenix.PubSub.PG2] 24 | 25 | # Configures Elixir's Logger 26 | config :logger, :console, 27 | format: "$time $metadata[$level] $message\n", 28 | metadata: [:request_id] 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 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/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 :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, 10 | http: [port: 4000], 11 | debug_errors: true, 12 | code_reloader: true, 13 | check_origin: false, 14 | watchers: [node: ["node_modules/webpack/bin/webpack.js", "-p", "-w", 15 | cd: Path.expand("../assets", __DIR__)]] 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 | # Watch static and templates for browser reloading. 34 | config :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, 35 | live_reload: [ 36 | patterns: [ 37 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, 38 | ~r{priv/gettext/.*(po)$}, 39 | ~r{lib/<%= @project_name %>_web/views/.*(ex)$}, 40 | ~r{lib/<%= @project_name %>_web/templates/.*(eex|slim|slime)$} 41 | ] 42 | ] 43 | 44 | # 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 | 51 | # Configure your database 52 | config :<%= @project_name %>, <%= @project_name_camel_case %>.Repo, 53 | adapter: Ecto.Adapters.Postgres, 54 | database: "<%= @project_name %>_dev", 55 | hostname: "localhost", 56 | pool_size: 10 57 | 58 | # Configure mailer for local previews 59 | config :<%= @project_name %>, <%= @project_name_camel_case %>.Mailer, 60 | adapter: Swoosh.Adapters.Local 61 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, we often load configuration from external 4 | # sources, such as your system environment. For this reason, 5 | # you won't find the :http configuration below, but set inside 6 | # <%= @project_name_camel_case %>Web.Endpoint.load_from_system_env/1 dynamically. 7 | # Any dynamic configuration should be moved to such function. 8 | # 9 | # Don't forget to configure the url host to something meaningful, 10 | # Phoenix uses this information when generating URLs. 11 | # 12 | # Finally, we also include the path to a cache manifest 13 | # containing the digested version of static files. This 14 | # manifest is generated by the mix phoenix.digest task 15 | # which you typically run after static files are built. 16 | config :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, 17 | load_from_system_env: true, 18 | cache_static_manifest: "priv/static/cache_manifest.json", 19 | force_ssl: [rewrite_on: [:x_forwarded_proto]] 20 | 21 | # Do not print debug messages in production 22 | config :logger, level: :info 23 | 24 | # TODO: Configure mailer for production. 25 | # 26 | # config :<%= @project_name %>, <%= @project_name_camel_case %>.Mailer, 27 | # adapter: Swoosh.Adapters.Sendgrid, 28 | # api_key: {:system, "SENDGRID_API_KEY"} 29 | 30 | # TODO: Configure the database 31 | # 32 | # config :<%= @project_name %>, <%= @project_name_camel_case %>.Repo, 33 | # adapter: Ecto.Adapters.Postgres, 34 | # url: System.get_env("DATABASE_URL"), 35 | # pool_size: String.to_integer(System.get_env("POOL_SIZE") || "20"), 36 | # ssl: true 37 | 38 | # ## SSL Support 39 | # 40 | # To get SSL working, you will need to add the `https` key 41 | # to the previous section and set your `:url` port to 443: 42 | # 43 | # config :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, 44 | # ... 45 | # url: [host: "example.com", port: 443], 46 | # https: [:inet6, 47 | # port: 443, 48 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 49 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] 50 | # 51 | # Where those two env variables return an absolute path to 52 | # the key and cert in disk or a relative path inside priv, 53 | # for example "priv/ssl/server.key". 54 | # 55 | # We also recommend setting `force_ssl`, ensuring no data is 56 | # ever sent via http, always redirecting to https: 57 | # 58 | # config :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, 59 | # force_ssl: [hsts: true] 60 | # 61 | # Check `Plug.SSL` for all available options in `force_ssl`. 62 | 63 | # ## Using releases 64 | # 65 | # If you are doing OTP releases, you need to instruct Phoenix 66 | # to start the server for all endpoints: 67 | # 68 | # config :phoenix, :serve_endpoints, true 69 | # 70 | # Alternatively, you can configure exactly which server to 71 | # start per endpoint: 72 | # 73 | # config :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, server: true 74 | # 75 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/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 :<%= @project_name %>, <%= @project_name_camel_case %>Web.Endpoint, 6 | http: [port: 4001], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | 12 | # Configure mailer for test mode 13 | config :<%= @project_name %>, <%= @project_name_camel_case %>.Mailer, 14 | adapter: Swoosh.Adapters.Test 15 | 16 | # Configure your database 17 | {whoami, _} = System.cmd("whoami", []) 18 | whoami = String.replace(whoami, "\n", "") 19 | 20 | config :<%= @project_name %>, <%= @project_name_camel_case %>.Repo, 21 | adapter: Ecto.Adapters.Postgres, 22 | database: "<%= @project_name %>_test", 23 | hostname: "localhost", 24 | username: System.get_env("DATABASE_POSTGRESQL_USERNAME") || whoami, 25 | password: System.get_env("DATABASE_POSTGRESQL_PASSWORD") || nil, 26 | pool: Ecto.Adapters.SQL.Sandbox, 27 | ownership_timeout: 60_000 28 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/elixir_buildpack.config: -------------------------------------------------------------------------------- 1 | # Buildpack: https://github.com/HashNuke/heroku-buildpack-elixir 2 | erlang_version=19.2 3 | elixir_version=1.4.2 4 | always_rebuild=true -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %> do 2 | @moduledoc """ 3 | <%= @project_name_camel_case %> keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$/application.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>.Application do 2 | @moduledoc false 3 | 4 | use Application 5 | 6 | # See http://elixir-lang.org/docs/stable/elixir/Application.html 7 | # for more information on OTP Applications 8 | def start(_type, _args) do 9 | import Supervisor.Spec 10 | 11 | # Define workers and child supervisors to be supervised 12 | children = [ 13 | # Start the Ecto repository 14 | supervisor(<%= @project_name_camel_case %>.Repo, []), 15 | # Start the endpoint when the application starts 16 | supervisor(<%= @project_name_camel_case %>Web.Endpoint, []), 17 | # Start your own worker by calling: <%= @project_name_camel_case %>.Worker.start_link(arg1, arg2, arg3) 18 | # worker(<%= @project_name_camel_case %>.Worker, [arg1, arg2, arg3]), 19 | ] 20 | 21 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 22 | # for other strategies and supported options 23 | opts = [strategy: :one_for_one, name: <%= @project_name_camel_case %>.Supervisor] 24 | Supervisor.start_link(children, opts) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$/mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>.Mailer do 2 | @moduledoc """ 3 | Swoosh mailer for <%= @project_name_camel_case %>. 4 | """ 5 | 6 | use Swoosh.Mailer, otp_app: :<%= @project_name %> 7 | end 8 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>.Repo do 2 | use Ecto.Repo, otp_app: :<%= @project_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 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, views, channels and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use <%= @project_name_camel_case %>Web, :controller 9 | use <%= @project_name_camel_case %>Web, :view 10 | 11 | The definitions below will be executed for every view, 12 | controller, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define any helper function in modules 17 | and import those modules here. 18 | """ 19 | 20 | def controller do 21 | quote do 22 | use Phoenix.Controller, namespace: <%= @project_name_camel_case %>Web 23 | import Plug.Conn 24 | import <%= @project_name_camel_case %>Web.Router.Helpers 25 | import <%= @project_name_camel_case %>Web.Gettext 26 | end 27 | end 28 | 29 | def view do 30 | quote do 31 | use Phoenix.View, root: "lib/<%= @project_name %>_web/templates", 32 | namespace: <%= @project_name_camel_case %>Web 33 | 34 | # Import convenience functions from controllers 35 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] 36 | 37 | # Use all HTML functionality (forms, tags, etc) 38 | use Phoenix.HTML 39 | 40 | import <%= @project_name_camel_case %>Web.Router.Helpers 41 | import <%= @project_name_camel_case %>Web.ErrorHelpers 42 | import <%= @project_name_camel_case %>Web.Gettext 43 | end 44 | end 45 | 46 | def router do 47 | quote do 48 | use Phoenix.Router 49 | import Plug.Conn 50 | import Phoenix.Controller 51 | end 52 | end 53 | 54 | def channel do 55 | quote do 56 | use Phoenix.Channel 57 | import <%= @project_name_camel_case %>Web.Gettext 58 | end 59 | end 60 | 61 | @doc """ 62 | When used, dispatch to the appropriate controller/view/etc. 63 | """ 64 | defmacro __using__(which) when is_atom(which) do 65 | apply(__MODULE__, which, []) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", <%= @project_name_camel_case %>Web.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 | # <%= @project_name_camel_case %>Web.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) 34 | # 35 | # Returning `nil` makes this socket anonymous. 36 | def id(_socket), do: nil 37 | end 38 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.PageController do 2 | use <%= @project_name_camel_case %>Web, :controller 3 | 4 | def index(conn, _params) do 5 | render conn, "index.html" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :<%= @project_name %> 3 | 4 | socket "/socket", <%= @project_name_camel_case %>Web.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: :<%= @project_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 17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 18 | plug Phoenix.LiveReloader 19 | plug Phoenix.CodeReloader 20 | end 21 | 22 | plug Plug.RequestId 23 | plug Plug.Logger 24 | 25 | plug Plug.Parsers, 26 | parsers: [:urlencoded, :multipart, :json], 27 | pass: ["*/*"], 28 | json_decoder: Poison 29 | 30 | plug Plug.MethodOverride 31 | plug Plug.Head 32 | 33 | # 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: "_<%= @project_name %>_key", 39 | signing_salt: "EKKB12hr" 40 | 41 | plug <%= @project_name_camel_case %>Web.Router 42 | 43 | @doc """ 44 | Callback invoked for dynamically configuring the endpoint. 45 | 46 | It receives the endpoint configuration and checks if 47 | configuration should be loaded from the system environment. 48 | """ 49 | def init(_key, config) do 50 | if config[:load_from_system_env] do 51 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" 52 | url_port = System.get_env("URL_PORT") || raise "expected the URL_PORT environment variable to be set" 53 | host = System.get_env("URL_HOST") || config.url[:host] || raise "expected the HOST environment variable to be set" 54 | scheme = System.get_env("URL_SCHEME") || raise "exepceted URL_SCHEME environment variable to be set" 55 | secret_key_base = System.get_env("SECRET_KEY_BASE") || config.secret_key_base || raise "expected the SECRET_KEY_BASE environment variable to be set" 56 | 57 | config = 58 | config 59 | |> Keyword.put(:http, [port: port, protocol_options: [compress: true]]) 60 | |> Keyword.put(:secret_key_base, secret_key_base) 61 | |> Keyword.put(:url, [scheme: scheme, host: host, port: url_port, path: "/"]) 62 | 63 | {:ok, config} 64 | else 65 | {:ok, config} 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.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 <%= @project_name_camel_case %>Web.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: :<%= @project_name %> 24 | end 25 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.Router do 2 | use <%= @project_name_camel_case %>Web, :router 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 11 | 12 | pipeline :api do 13 | plug :accepts, ["json"] 14 | end 15 | 16 | scope "/", <%= @project_name_camel_case %>Web 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", <%= @project_name_camel_case %>Web do 24 | # pipe_through :api 25 | # end 26 | 27 | if Mix.env == :dev do 28 | scope "/dev" do 29 | pipe_through [:browser] 30 | 31 | forward "/mailbox", Plug.Swoosh.MailboxPreview, [base_path: "/dev/mailbox"] 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/templates/layout/app.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta charset="utf-8" 5 | meta content="IE=edge" http-equiv="X-UA-Compatible" 6 | meta content="width=device-width, initial-scale=1" name="viewport" 7 | 8 | title <%= @project_name_camel_case %> 9 | 10 | link rel="stylesheet" href="#{static_url(@conn, "/css/app.css")}" 11 | 12 | body 13 | main role="main" 14 | = render @view_module, @view_template, assigns 15 | 16 | script src="#{static_path(@conn, "/js/app.js")}" 17 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/templates/page/index.html.slim: -------------------------------------------------------------------------------- 1 | p Hello <%= @project_name_camel_case %> 2 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | 6 | use Phoenix.HTML 7 | 8 | @doc """ 9 | Generates tag for inlined form input errors. 10 | """ 11 | def error_tag(form, field) do 12 | if error = form.errors[field] do 13 | content_tag :span, translate_error(error), class: "help-block" 14 | end 15 | end 16 | 17 | @doc """ 18 | Translates an error message using gettext. 19 | """ 20 | def translate_error({msg, opts}) do 21 | # Because error messages were defined within Ecto, we must 22 | # call the Gettext module passing our Gettext backend. We 23 | # also use the "errors" domain as translations are placed 24 | # in the errors.po file. 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(<%= @project_name_camel_case %>Web.Gettext, "errors", msg, msg, count, opts) 36 | else 37 | Gettext.dgettext(<%= @project_name_camel_case %>Web.Gettext, "errors", msg, opts) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.ErrorView do 2 | use <%= @project_name_camel_case %>Web, :view 3 | 4 | def render("404.html", _assigns) do 5 | "Page not found" 6 | end 7 | 8 | def render("500.html", _assigns) do 9 | "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 17 | end 18 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/views/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.LayoutView do 2 | use <%= @project_name_camel_case %>Web, :view 3 | end 4 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/lib/$PROJECT_NAME$_web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.PageView do 2 | use <%= @project_name_camel_case %>Web, :view 3 | end 4 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :<%= @project_name %>, 7 | version: "0.0.1", 8 | elixir: "~> 1.4", 9 | elixirc_paths: elixirc_paths(Mix.env), 10 | compilers: [:phoenix, :gettext] ++ Mix.compilers, 11 | start_permanent: Mix.env == :prod, 12 | aliases: aliases(), 13 | deps: deps() 14 | ] 15 | end 16 | 17 | # Configuration for the OTP application. 18 | # 19 | # Type `mix help compile.app` for more information. 20 | def application do 21 | [ 22 | mod: {<%= @project_name_camel_case %>.Application, []}, 23 | extra_applications: [:logger, :runtime_tools] 24 | ] 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 | [ 36 | {:cowboy, "~> 1.0"}, 37 | {:credo, "~> 0.7", only: [:dev, :test]}, 38 | {:ex_doc, "~> 0.15", only: [:dev, :test]}, 39 | {:gettext, "~> 0.11"}, 40 | {:phoenix, "~> 1.3.0"}, 41 | {:phoenix_ecto, "~> 3.2"}, 42 | {:phoenix_html, "~> 2.10"}, 43 | {:phoenix_live_reload, "~> 1.0", only: :dev}, 44 | {:phoenix_pubsub, "~> 1.0"}, 45 | {:phoenix_slime, "~> 0.8.0"}, 46 | {:postgrex, ">= 0.0.0"}, 47 | {:swoosh, "~> 0.7"} 48 | ] 49 | end 50 | 51 | # Aliases are shortcuts or tasks specific to the current project. 52 | # For example, to create, migrate and run the seeds file at once: 53 | # 54 | # $ mix ecto.setup 55 | # 56 | # See the documentation for `Mix` for more info on aliases. 57 | defp aliases do 58 | [ 59 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 60 | "ecto.reset": ["ecto.drop", "ecto.setup"], 61 | "test": ["ecto.create --quiet", "ecto.migrate", "test"] 62 | ] 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/phoenix_static_buildpack.config: -------------------------------------------------------------------------------- 1 | # Buildpack: https://github.com/gjaldon/heroku-buildpack-phoenix-static 2 | node_version=7.4.0 3 | assets_path=assets 4 | compile="bin/heroku_compile_assets" 5 | phoenix_ex=phx -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/priv/gettext/en/LC_MESSAGES/errors.po: -------------------------------------------------------------------------------- 1 | ## `msgid`s in this file come from POT (.pot) files. 2 | ## 3 | ## Do not add, change, or remove `msgid`s manually here as 4 | ## they're tied to the ones in the corresponding POT file 5 | ## (with the same domain). 6 | ## 7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge` 8 | ## to merge POT files into PO files. 9 | msgid "" 10 | msgstr "" 11 | "Language: en\n" 12 | 13 | ## From Ecto.Changeset.cast/4 14 | msgid "can't be blank" 15 | msgstr "" 16 | 17 | ## From Ecto.Changeset.unique_constraint/3 18 | msgid "has already been taken" 19 | msgstr "" 20 | 21 | ## From Ecto.Changeset.put_change/3 22 | msgid "is invalid" 23 | msgstr "" 24 | 25 | ## From Ecto.Changeset.validate_acceptance/3 26 | msgid "must be accepted" 27 | msgstr "" 28 | 29 | ## From Ecto.Changeset.validate_format/3 30 | msgid "has invalid format" 31 | msgstr "" 32 | 33 | ## From Ecto.Changeset.validate_subset/3 34 | msgid "has an invalid entry" 35 | msgstr "" 36 | 37 | ## From Ecto.Changeset.validate_exclusion/3 38 | msgid "is reserved" 39 | msgstr "" 40 | 41 | ## From Ecto.Changeset.validate_confirmation/3 42 | msgid "does not match confirmation" 43 | msgstr "" 44 | 45 | ## From Ecto.Changeset.no_assoc_constraint/3 46 | msgid "is still associated with this entry" 47 | msgstr "" 48 | 49 | msgid "are still associated with this entry" 50 | msgstr "" 51 | 52 | ## From Ecto.Changeset.validate_length/3 53 | msgid "should be %{count} character(s)" 54 | msgid_plural "should be %{count} character(s)" 55 | msgstr[0] "" 56 | msgstr[1] "" 57 | 58 | msgid "should have %{count} item(s)" 59 | msgid_plural "should have %{count} item(s)" 60 | msgstr[0] "" 61 | msgstr[1] "" 62 | 63 | msgid "should be at least %{count} character(s)" 64 | msgid_plural "should be at least %{count} character(s)" 65 | msgstr[0] "" 66 | msgstr[1] "" 67 | 68 | msgid "should have at least %{count} item(s)" 69 | msgid_plural "should have at least %{count} item(s)" 70 | msgstr[0] "" 71 | msgstr[1] "" 72 | 73 | msgid "should be at most %{count} character(s)" 74 | msgid_plural "should be at most %{count} character(s)" 75 | msgstr[0] "" 76 | msgstr[1] "" 77 | 78 | msgid "should have at most %{count} item(s)" 79 | msgid_plural "should have at most %{count} item(s)" 80 | msgstr[0] "" 81 | msgstr[1] "" 82 | 83 | ## From Ecto.Changeset.validate_number/3 84 | msgid "must be less than %{number}" 85 | msgstr "" 86 | 87 | msgid "must be greater than %{number}" 88 | msgstr "" 89 | 90 | msgid "must be less than or equal to %{number}" 91 | msgstr "" 92 | 93 | msgid "must be greater than or equal to %{number}" 94 | msgstr "" 95 | 96 | msgid "must be equal to %{number}" 97 | msgstr "" 98 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/priv/gettext/errors.pot: -------------------------------------------------------------------------------- 1 | ## This file is a PO Template file. 2 | ## 3 | ## `msgid`s here are often extracted from source code. 4 | ## Add new translations manually only if they're dynamic 5 | ## translations that can't be statically extracted. 6 | ## 7 | ## Run `mix gettext.extract` to bring this file up to 8 | ## date. Leave `msgstr`s empty as changing them here as no 9 | ## effect: edit them in PO (`.po`) files instead. 10 | 11 | ## From Ecto.Changeset.cast/4 12 | msgid "can't be blank" 13 | msgstr "" 14 | 15 | ## From Ecto.Changeset.unique_constraint/3 16 | msgid "has already been taken" 17 | msgstr "" 18 | 19 | ## From Ecto.Changeset.put_change/3 20 | msgid "is invalid" 21 | msgstr "" 22 | 23 | ## From Ecto.Changeset.validate_acceptance/3 24 | msgid "must be accepted" 25 | msgstr "" 26 | 27 | ## From Ecto.Changeset.validate_format/3 28 | msgid "has invalid format" 29 | msgstr "" 30 | 31 | ## From Ecto.Changeset.validate_subset/3 32 | msgid "has an invalid entry" 33 | msgstr "" 34 | 35 | ## From Ecto.Changeset.validate_exclusion/3 36 | msgid "is reserved" 37 | msgstr "" 38 | 39 | ## From Ecto.Changeset.validate_confirmation/3 40 | msgid "does not match confirmation" 41 | msgstr "" 42 | 43 | ## From Ecto.Changeset.no_assoc_constraint/3 44 | msgid "is still associated with this entry" 45 | msgstr "" 46 | 47 | msgid "are still associated with this entry" 48 | msgstr "" 49 | 50 | ## From Ecto.Changeset.validate_length/3 51 | msgid "should be %{count} character(s)" 52 | msgid_plural "should be %{count} character(s)" 53 | msgstr[0] "" 54 | msgstr[1] "" 55 | 56 | msgid "should have %{count} item(s)" 57 | msgid_plural "should have %{count} item(s)" 58 | msgstr[0] "" 59 | msgstr[1] "" 60 | 61 | msgid "should be at least %{count} character(s)" 62 | msgid_plural "should be at least %{count} character(s)" 63 | msgstr[0] "" 64 | msgstr[1] "" 65 | 66 | msgid "should have at least %{count} item(s)" 67 | msgid_plural "should have at least %{count} item(s)" 68 | msgstr[0] "" 69 | msgstr[1] "" 70 | 71 | msgid "should be at most %{count} character(s)" 72 | msgid_plural "should be at most %{count} character(s)" 73 | msgstr[0] "" 74 | msgstr[1] "" 75 | 76 | msgid "should have at most %{count} item(s)" 77 | msgid_plural "should have at most %{count} item(s)" 78 | msgstr[0] "" 79 | msgstr[1] "" 80 | 81 | ## From Ecto.Changeset.validate_number/3 82 | msgid "must be less than %{number}" 83 | msgstr "" 84 | 85 | msgid "must be greater than %{number}" 86 | msgstr "" 87 | 88 | msgid "must be less than or equal to %{number}" 89 | msgstr "" 90 | 91 | msgid "must be greater than or equal to %{number}" 92 | msgstr "" 93 | 94 | msgid "must be equal to %{number}" 95 | msgstr "" 96 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/priv/repo/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitered/firebird/679ca2f21eb0a5dc4bfc15c90f2c3c2abd2fe49b/template/$PROJECT_NAME$/priv/repo/migrations/.gitkeep -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # <%= @project_name_camel_case %>.Factory.insert!(:some_factory) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/$PROJECT_NAME$_web/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.PageControllerTest do 2 | use <%= @project_name_camel_case %>Web.ConnCase 3 | 4 | test "GET /", %{conn: conn} do 5 | conn = get conn, "/" 6 | assert html_response(conn, 200) =~ "Hello <%= @project_name_camel_case %>" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/$PROJECT_NAME$_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.ErrorViewTest do 2 | use <%= @project_name_camel_case %>Web.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(<%= @project_name_camel_case %>Web.ErrorView, "404.html", []) == 9 | "Page not found" 10 | end 11 | 12 | test "render 500.html" do 13 | assert render_to_string(<%= @project_name_camel_case %>Web.ErrorView, "500.html", []) == 14 | "Internal server error" 15 | end 16 | 17 | test "render any other" do 18 | assert render_to_string(<%= @project_name_camel_case %>Web.ErrorView, "505.html", []) == 19 | "Internal server error" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/$PROJECT_NAME$_web/views/layout_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.LayoutViewTest do 2 | use <%= @project_name_camel_case %>Web.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/$PROJECT_NAME$_web/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.PageViewTest do 2 | use <%= @project_name_camel_case %>Web.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.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 <%= @project_name_camel_case %>Web.Endpoint 25 | end 26 | end 27 | 28 | 29 | setup tags do 30 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(<%= @project_name_camel_case %>.Repo) 31 | unless tags[:async] do 32 | Ecto.Adapters.SQL.Sandbox.mode(<%= @project_name_camel_case %>.Repo, {:shared, self()}) 33 | end 34 | :ok 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>Web.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 <%= @project_name_camel_case %>Web.Router.Helpers 23 | 24 | # The default endpoint for testing 25 | @endpoint <%= @project_name_camel_case %>Web.Endpoint 26 | end 27 | end 28 | 29 | 30 | setup tags do 31 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(<%= @project_name_camel_case %>.Repo) 32 | unless tags[:async] do 33 | Ecto.Adapters.SQL.Sandbox.mode(<%= @project_name_camel_case %>.Repo, {:shared, self()}) 34 | end 35 | {:ok, conn: Phoenix.ConnTest.build_conn()} 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>.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 <%= @project_name_camel_case %>.Factory 20 | 21 | import Ecto 22 | import Ecto.Changeset 23 | import Ecto.Query 24 | import <%= @project_name_camel_case %>.DataCase 25 | end 26 | end 27 | 28 | setup tags do 29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(<%= @project_name_camel_case %>.Repo) 30 | 31 | unless tags[:async] do 32 | Ecto.Adapters.SQL.Sandbox.mode(<%= @project_name_camel_case %>.Repo, {:shared, self()}) 33 | end 34 | 35 | :ok 36 | end 37 | 38 | @doc """ 39 | A helper that transform changeset errors to a map of messages. 40 | 41 | 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 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/support/factory.ex: -------------------------------------------------------------------------------- 1 | defmodule <%= @project_name_camel_case %>.Factory do 2 | @moduledoc """ 3 | Build and insert test data. 4 | 5 | ## Examples 6 | 7 | Factory.build(:user) 8 | # => %<%= @project_name_camel_case %>.User{name: "John Smith"} 9 | 10 | Factory.build(:user, name: "Jane Smith") 11 | # => %<%= @project_name_camel_case %>.User{name: "Jane Smith"} 12 | 13 | Factory.insert!(:user, name: "Jane Smith") 14 | # => %<%= @project_name_camel_case %>.User{name: "Jane Smith"} 15 | """ 16 | 17 | alias <%= @project_name_camel_case %>.Repo 18 | 19 | # Define your factories like this: 20 | # 21 | # def build(:user) do 22 | # %<%= @project_name_camel_case %>.User{name: "John Smith"} 23 | # end 24 | def build(_factory_name) do 25 | # return struct 26 | end 27 | 28 | @doc """ 29 | Build a schema struct with custom attributes. 30 | 31 | ## Example 32 | 33 | Suppose you had a `build/1` factory for users: 34 | 35 | def build(:user) do 36 | %<%= @project_name_camel_case %>.User{name: "John Smith"} 37 | end 38 | 39 | You could call `build/2` to customize the name: 40 | 41 | Factory.build(:user, name: "Custom Name") 42 | # => %<%= @project_name_camel_case %>.User{name: "Custom Name"} 43 | """ 44 | def build(factory_name, attributes) do 45 | factory_name 46 | |> build() 47 | |> struct(attributes) 48 | end 49 | 50 | @doc """ 51 | Builds and inserts a factory. 52 | 53 | ## Example 54 | 55 | Suppose you had a `build/1` factory for users: 56 | 57 | def build(:user) do 58 | %<%= @project_name_camel_case %>.User{name: "John Smith"} 59 | end 60 | 61 | You can customize and insert the factory in one call to `insert!/2`: 62 | 63 | Factory.insert!(:user, name: "Custom Name") 64 | # => <%= @project_name_camel_case %>.User{name: "Custom Name"} 65 | """ 66 | def insert!(factory_name, attributes \\ []) do 67 | factory_name 68 | |> build(attributes) 69 | |> Repo.insert! 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /template/$PROJECT_NAME$/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | 3 | Ecto.Adapters.SQL.Sandbox.mode(<%= @project_name_camel_case %>.Repo, :manual) 4 | --------------------------------------------------------------------------------