├── .credo.exs ├── .formatter.exs ├── .gitignore ├── .tool-versions ├── LICENSE ├── README.md ├── assets ├── .babelrc ├── css │ └── app.css ├── js │ ├── app.js │ └── socket.js ├── package-lock.json ├── package.json ├── static │ ├── favicon.ico │ ├── images │ │ └── phoenix.png │ └── robots.txt └── webpack.config.js ├── config ├── config.exs ├── dev.exs ├── prod.exs ├── prod.secret.exs └── test.exs ├── lib ├── speakr.ex ├── speakr │ ├── application.ex │ ├── auth │ │ └── user.ex │ └── repo.ex ├── speakr_web.ex └── speakr_web │ ├── channels │ └── user_socket.ex │ ├── controllers │ └── page_controller.ex │ ├── endpoint.ex │ ├── gettext.ex │ ├── pow_mailer.ex │ ├── router.ex │ ├── templates │ ├── layout │ │ └── app.html.eex │ ├── page │ │ └── index.html.eex │ ├── pow │ │ ├── registration │ │ │ ├── edit.html.eex │ │ │ └── new.html.eex │ │ └── session │ │ │ └── new.html.eex │ ├── pow_email_confirmation │ │ └── mailer │ │ │ ├── email_confirmation.html.eex │ │ │ └── email_confirmation.text.eex │ └── pow_reset_password │ │ └── mailer │ │ ├── reset_password.html.eex │ │ └── reset_password.text.eex │ └── views │ ├── error_helpers.ex │ ├── error_view.ex │ ├── layout_view.ex │ ├── page_view.ex │ ├── pow │ ├── registration_view.ex │ └── session_view.ex │ ├── pow_email_confirmation │ └── mailer_view.ex │ └── pow_reset_password │ └── mailer_view.ex ├── mix.exs ├── mix.lock ├── priv ├── gettext │ ├── en │ │ └── LC_MESSAGES │ │ │ └── errors.po │ └── errors.pot └── repo │ ├── migrations │ ├── .formatter.exs │ ├── 20200125051017_create_users.exs │ └── 20200125054005_add_pow_email_confirmation_to_users.exs │ └── seeds.exs └── test ├── speakr_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 └── test_helper.exs /.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 exec using `mix credo -C `. If no exec name is given 14 | # "default" is used. 15 | # 16 | name: "default", 17 | # 18 | # These are the files included in the analysis: 19 | files: %{ 20 | # 21 | # You can give explicit globs or simply directories. 22 | # In the latter case `**/*.{ex,exs}` will be used. 23 | # 24 | included: ["lib/", "src/", "test/", "web/", "apps/"], 25 | excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] 26 | }, 27 | # 28 | # Load and configure plugins here: 29 | # 30 | plugins: [], 31 | # 32 | # If you create your own checks, you must specify the source files for 33 | # them here, so they can be loaded by Credo before running the analysis. 34 | # 35 | requires: [], 36 | # 37 | # If you want to enforce a style guide and need a more traditional linting 38 | # experience, you can change `strict` to `true` below: 39 | # 40 | strict: false, 41 | # 42 | # If you want to use uncolored output by default, you can change `color` 43 | # to `false` below: 44 | # 45 | color: true, 46 | # 47 | # You can customize the parameters of any check by adding a second element 48 | # to the tuple. 49 | # 50 | # To disable a check put `false` as second element: 51 | # 52 | # {Credo.Check.Design.DuplicatedCode, false} 53 | # 54 | checks: [ 55 | # 56 | ## Consistency Checks 57 | # 58 | {Credo.Check.Consistency.ExceptionNames, []}, 59 | {Credo.Check.Consistency.LineEndings, []}, 60 | {Credo.Check.Consistency.ParameterPatternMatching, []}, 61 | {Credo.Check.Consistency.SpaceAroundOperators, []}, 62 | {Credo.Check.Consistency.SpaceInParentheses, []}, 63 | {Credo.Check.Consistency.TabsOrSpaces, []}, 64 | 65 | # 66 | ## Design Checks 67 | # 68 | # You can customize the priority of any check 69 | # Priority values are: `low, normal, high, higher` 70 | # 71 | {Credo.Check.Design.AliasUsage, 72 | [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, 73 | # You can also customize the exit_status of each check. 74 | # If you don't want TODO comments to cause `mix credo` to fail, just 75 | # set this value to 0 (zero). 76 | # 77 | {Credo.Check.Design.TagTODO, [exit_status: 2]}, 78 | {Credo.Check.Design.TagFIXME, []}, 79 | 80 | # 81 | ## Readability Checks 82 | # 83 | {Credo.Check.Readability.AliasOrder, []}, 84 | {Credo.Check.Readability.FunctionNames, []}, 85 | {Credo.Check.Readability.LargeNumbers, []}, 86 | {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, 87 | {Credo.Check.Readability.ModuleAttributeNames, []}, 88 | {Credo.Check.Readability.ModuleDoc, []}, 89 | {Credo.Check.Readability.ModuleNames, []}, 90 | {Credo.Check.Readability.ParenthesesInCondition, []}, 91 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, 92 | {Credo.Check.Readability.PredicateFunctionNames, []}, 93 | {Credo.Check.Readability.PreferImplicitTry, []}, 94 | {Credo.Check.Readability.RedundantBlankLines, []}, 95 | {Credo.Check.Readability.Semicolons, []}, 96 | {Credo.Check.Readability.SpaceAfterCommas, []}, 97 | {Credo.Check.Readability.StringSigils, []}, 98 | {Credo.Check.Readability.TrailingBlankLine, []}, 99 | {Credo.Check.Readability.TrailingWhiteSpace, []}, 100 | # TODO: enable by default in Credo 1.1 101 | {Credo.Check.Readability.UnnecessaryAliasExpansion, false}, 102 | {Credo.Check.Readability.VariableNames, []}, 103 | 104 | # 105 | ## Refactoring Opportunities 106 | # 107 | {Credo.Check.Refactor.CondStatements, []}, 108 | {Credo.Check.Refactor.CyclomaticComplexity, []}, 109 | {Credo.Check.Refactor.FunctionArity, []}, 110 | {Credo.Check.Refactor.LongQuoteBlocks, []}, 111 | {Credo.Check.Refactor.MapInto, false}, 112 | {Credo.Check.Refactor.MatchInCondition, []}, 113 | {Credo.Check.Refactor.NegatedConditionsInUnless, []}, 114 | {Credo.Check.Refactor.NegatedConditionsWithElse, []}, 115 | {Credo.Check.Refactor.Nesting, []}, 116 | {Credo.Check.Refactor.UnlessWithElse, []}, 117 | {Credo.Check.Refactor.WithClauses, []}, 118 | 119 | # 120 | ## Warnings 121 | # 122 | {Credo.Check.Warning.BoolOperationOnSameValues, []}, 123 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, 124 | {Credo.Check.Warning.IExPry, []}, 125 | {Credo.Check.Warning.IoInspect, []}, 126 | {Credo.Check.Warning.LazyLogging, false}, 127 | {Credo.Check.Warning.OperationOnSameValues, []}, 128 | {Credo.Check.Warning.OperationWithConstantResult, []}, 129 | {Credo.Check.Warning.RaiseInsideRescue, []}, 130 | {Credo.Check.Warning.UnusedEnumOperation, []}, 131 | {Credo.Check.Warning.UnusedFileOperation, []}, 132 | {Credo.Check.Warning.UnusedKeywordOperation, []}, 133 | {Credo.Check.Warning.UnusedListOperation, []}, 134 | {Credo.Check.Warning.UnusedPathOperation, []}, 135 | {Credo.Check.Warning.UnusedRegexOperation, []}, 136 | {Credo.Check.Warning.UnusedStringOperation, []}, 137 | {Credo.Check.Warning.UnusedTupleOperation, []}, 138 | 139 | # 140 | # Controversial and experimental checks (opt-in, just replace `false` with `[]`) 141 | # 142 | {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, 143 | {Credo.Check.Consistency.UnusedVariableNames, false}, 144 | {Credo.Check.Design.DuplicatedCode, false}, 145 | {Credo.Check.Readability.AliasAs, false}, 146 | {Credo.Check.Readability.MultiAlias, false}, 147 | {Credo.Check.Readability.Specs, false}, 148 | {Credo.Check.Readability.SinglePipe, false}, 149 | {Credo.Check.Refactor.ABCSize, false}, 150 | {Credo.Check.Refactor.AppendSingleItem, false}, 151 | {Credo.Check.Refactor.DoubleBooleanNegation, false}, 152 | {Credo.Check.Refactor.ModuleDependencies, false}, 153 | {Credo.Check.Refactor.PipeChainStart, false}, 154 | {Credo.Check.Refactor.VariableRebinding, false}, 155 | {Credo.Check.Warning.MapGetUnsafePass, false}, 156 | {Credo.Check.Warning.UnsafeToAtom, false} 157 | 158 | # 159 | # Custom checks can be created using `mix credo.gen.check`. 160 | # 161 | ] 162 | } 163 | ] 164 | } 165 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto, :phoenix], 3 | inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | subdirectories: ["priv/*/migrations"] 5 | ] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | speakr-*.tar 24 | 25 | # If NPM crashes, it generates a log, let's ignore it too. 26 | npm-debug.log 27 | 28 | # The directory NPM downloads your dependencies sources to. 29 | /assets/node_modules/ 30 | 31 | # Since we are building assets from assets/, 32 | # we ignore priv/static. You may want to comment 33 | # this depending on your deployment strategy. 34 | /priv/static/ 35 | 36 | # Others 37 | .elixir_ls/ 38 | .vscode/ 39 | logfile 40 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 22.0 2 | elixir 1.10.0 3 | nodejs 12.13.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 mafinar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Speakr 2 | 3 | To start your Phoenix server: 4 | 5 | * Install dependencies with `mix deps.get` 6 | * Create and migrate your database with `mix ecto.setup` 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](https://hexdocs.pm/phoenix/deployment.html). 13 | 14 | ## Learn more 15 | 16 | * Official website: https://www.phoenixframework.org/ 17 | * Guides: https://hexdocs.pm/phoenix/overview.html 18 | * Docs: https://hexdocs.pm/phoenix 19 | * Forum: https://elixirforum.com/c/phoenix-forum 20 | * Source: https://github.com/phoenixframework/phoenix 21 | -------------------------------------------------------------------------------- /assets/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /assets/css/app.css: -------------------------------------------------------------------------------- 1 | @import "../node_modules/uikit/dist/css/uikit.min.css"; -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | // We need to import the CSS so that webpack will load it. 2 | // The MiniCssExtractPlugin is used to separate it out into 3 | // its own CSS file. 4 | import css from "../css/app.css" 5 | 6 | // webpack automatically bundles all modules in your 7 | // entry points. Those entry points can be configured 8 | // in "webpack.config.js". 9 | // 10 | // Import dependencies 11 | // 12 | import "phoenix_html" 13 | 14 | // Import local files 15 | // 16 | // Local files can be imported directly using relative paths, for example: 17 | // import socket from "./socket" 18 | -------------------------------------------------------------------------------- /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 | // 7 | // Pass the token on params as below. Or remove it 8 | // from the params if you are not using authentication. 9 | import {Socket} from "phoenix" 10 | 11 | let socket = new Socket("/socket", {params: {token: window.userToken}}) 12 | 13 | // When you connect, you'll often need to authenticate the client. 14 | // For example, imagine you have an authentication plug, `MyAuth`, 15 | // which authenticates the session and assigns a `:current_user`. 16 | // If the current user exists you can assign the user's token in 17 | // the connection for use in the layout. 18 | // 19 | // In your "lib/web/router.ex": 20 | // 21 | // pipeline :browser do 22 | // ... 23 | // plug MyAuth 24 | // plug :put_user_token 25 | // end 26 | // 27 | // defp put_user_token(conn, _) do 28 | // if current_user = conn.assigns[:current_user] do 29 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id) 30 | // assign(conn, :user_token, token) 31 | // else 32 | // conn 33 | // end 34 | // end 35 | // 36 | // Now you need to pass this token to JavaScript. You can do so 37 | // inside a script tag in "lib/web/templates/layout/app.html.eex": 38 | // 39 | // 40 | // 41 | // You will need to verify the user token in the "connect/3" function 42 | // in "lib/web/channels/user_socket.ex": 43 | // 44 | // def connect(%{"token" => token}, socket, _connect_info) do 45 | // # max_age: 1209600 is equivalent to two weeks in seconds 46 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do 47 | // {:ok, user_id} -> 48 | // {:ok, assign(socket, :user, user_id)} 49 | // {:error, reason} -> 50 | // :error 51 | // end 52 | // end 53 | // 54 | // Finally, connect to the socket: 55 | socket.connect() 56 | 57 | // Now that you are connected, you can join channels with a topic: 58 | let channel = socket.channel("topic:subtopic", {}) 59 | channel.join() 60 | .receive("ok", resp => { console.log("Joined successfully", resp) }) 61 | .receive("error", resp => { console.log("Unable to join", resp) }) 62 | 63 | export default socket 64 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": {}, 3 | "license": "MIT", 4 | "scripts": { 5 | "deploy": "webpack --mode production", 6 | "watch": "webpack --mode development --watch" 7 | }, 8 | "dependencies": { 9 | "phoenix": "file:../deps/phoenix", 10 | "phoenix_html": "file:../deps/phoenix_html", 11 | "uikit": "^3.3.0" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.0.0", 15 | "@babel/preset-env": "^7.0.0", 16 | "babel-loader": "^8.0.0", 17 | "copy-webpack-plugin": "^5.1.1", 18 | "css-loader": "^3.4.2", 19 | "mini-css-extract-plugin": "^0.9.0", 20 | "optimize-css-assets-webpack-plugin": "^5.0.1", 21 | "terser-webpack-plugin": "^2.3.2", 22 | "webpack": "4.41.5", 23 | "webpack-cli": "^3.3.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /assets/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-shoily/speakr/4d84c747fba12a53ae16fb674b8293b5ad083ac0/assets/static/favicon.ico -------------------------------------------------------------------------------- /assets/static/images/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-shoily/speakr/4d84c747fba12a53ae16fb674b8293b5ad083ac0/assets/static/images/phoenix.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const glob = require('glob'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const TerserPlugin = require('terser-webpack-plugin'); 5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 6 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 7 | 8 | module.exports = (env, options) => ({ 9 | optimization: { 10 | minimizer: [ 11 | new TerserPlugin({ cache: true, parallel: true, sourceMap: false }), 12 | new OptimizeCSSAssetsPlugin({}) 13 | ] 14 | }, 15 | entry: { 16 | './js/app.js': glob.sync('./vendor/**/*.js').concat(['./js/app.js']) 17 | }, 18 | output: { 19 | filename: 'app.js', 20 | path: path.resolve(__dirname, '../priv/static/js') 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.js$/, 26 | exclude: /node_modules/, 27 | use: { 28 | loader: 'babel-loader' 29 | } 30 | }, 31 | { 32 | test: /\.s?css$/, 33 | use: [MiniCssExtractPlugin.loader, 'css-loader'] 34 | } 35 | ] 36 | }, 37 | plugins: [ 38 | new MiniCssExtractPlugin({ filename: '../css/app.css' }), 39 | new CopyWebpackPlugin([{ from: 'static/', to: '../' }]) 40 | ] 41 | }); 42 | -------------------------------------------------------------------------------- /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 | 7 | # General application configuration 8 | use Mix.Config 9 | 10 | config :speakr, 11 | ecto_repos: [Speakr.Repo], 12 | generators: [binary_id: true] 13 | 14 | config :speakr, :pow, 15 | user: Speakr.Auth.User, 16 | repo: Speakr.Repo, 17 | web_module: SpeakrWeb, 18 | extensions: [PowResetPassword, PowEmailConfirmation], 19 | mailer_backend: SpeakrWeb.PowMailer, 20 | controller_callbacks: Pow.Extension.Phoenix.ControllerCallbacks 21 | 22 | # Configures the endpoint 23 | config :speakr, SpeakrWeb.Endpoint, 24 | url: [host: "localhost"], 25 | secret_key_base: "3ZMcdBo0bze/rycj10ZamgkBzJm5KtrznbTldp1lZ8qAgvx00vKMEbL6g+aK1PPH", 26 | render_errors: [view: SpeakrWeb.ErrorView, accepts: ~w(html json)], 27 | pubsub: [name: Speakr.PubSub, adapter: Phoenix.PubSub.PG2] 28 | 29 | # Configures Elixir's Logger 30 | config :logger, :console, 31 | format: "$time $metadata[$level] $message\n", 32 | metadata: [:request_id] 33 | 34 | # Use Jason for JSON parsing in Phoenix 35 | config :phoenix, :json_library, Jason 36 | 37 | # Import environment specific config. This must remain at the bottom 38 | # of this file so it overrides the configuration defined above. 39 | import_config "#{Mix.env()}.exs" 40 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # Configure your database 4 | config :speakr, Speakr.Repo, 5 | username: "postgres", 6 | password: "postgres", 7 | database: "speakr_dev", 8 | hostname: "localhost", 9 | show_sensitive_data_on_connection_error: true, 10 | pool_size: 10 11 | 12 | # For development, we disable any cache and enable 13 | # debugging and code reloading. 14 | # 15 | # The watchers configuration can be used to run external 16 | # watchers to your application. For example, we use it 17 | # with webpack to recompile .js and .css sources. 18 | config :speakr, SpeakrWeb.Endpoint, 19 | http: [port: 4000], 20 | debug_errors: true, 21 | code_reloader: true, 22 | check_origin: false, 23 | watchers: [ 24 | node: [ 25 | "node_modules/webpack/bin/webpack.js", 26 | "--mode", 27 | "development", 28 | "--watch-stdin", 29 | cd: Path.expand("../assets", __DIR__) 30 | ] 31 | ] 32 | 33 | # ## SSL Support 34 | # 35 | # In order to use HTTPS in development, a self-signed 36 | # certificate can be generated by running the following 37 | # Mix task: 38 | # 39 | # mix phx.gen.cert 40 | # 41 | # Note that this task requires Erlang/OTP 20 or later. 42 | # Run `mix help phx.gen.cert` for more information. 43 | # 44 | # The `http:` config above can be replaced with: 45 | # 46 | # https: [ 47 | # port: 4001, 48 | # cipher_suite: :strong, 49 | # keyfile: "priv/cert/selfsigned_key.pem", 50 | # certfile: "priv/cert/selfsigned.pem" 51 | # ], 52 | # 53 | # If desired, both `http:` and `https:` keys can be 54 | # configured to run both http and https servers on 55 | # different ports. 56 | 57 | # Watch static and templates for browser reloading. 58 | config :speakr, SpeakrWeb.Endpoint, 59 | live_reload: [ 60 | patterns: [ 61 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", 62 | ~r"priv/gettext/.*(po)$", 63 | ~r"lib/speakr_web/(live|views)/.*(ex)$", 64 | ~r"lib/speakr_web/templates/.*(eex)$" 65 | ] 66 | ] 67 | 68 | # Do not include metadata nor timestamps in development logs 69 | config :logger, :console, format: "[$level] $message\n" 70 | 71 | # Set a higher stacktrace during development. Avoid configuring such 72 | # in production as building large stacktraces may be expensive. 73 | config :phoenix, :stacktrace_depth, 20 74 | 75 | # Initialize plugs at runtime for faster development compilation 76 | config :phoenix, :plug_init_mode, :runtime 77 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | config :speakr, SpeakrWeb.Endpoint, 13 | url: [host: "example.com", port: 80], 14 | cache_static_manifest: "priv/static/cache_manifest.json" 15 | 16 | # Do not print debug messages in production 17 | config :logger, level: :info 18 | 19 | # ## SSL Support 20 | # 21 | # To get SSL working, you will need to add the `https` key 22 | # to the previous section and set your `:url` port to 443: 23 | # 24 | # config :speakr, SpeakrWeb.Endpoint, 25 | # ... 26 | # url: [host: "example.com", port: 443], 27 | # https: [ 28 | # port: 443, 29 | # cipher_suite: :strong, 30 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 31 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH"), 32 | # transport_options: [socket_opts: [:inet6]] 33 | # ] 34 | # 35 | # The `cipher_suite` is set to `:strong` to support only the 36 | # latest and more secure SSL ciphers. This means old browsers 37 | # and clients may not be supported. You can set it to 38 | # `:compatible` for wider support. 39 | # 40 | # `:keyfile` and `:certfile` expect an absolute path to the key 41 | # and cert in disk or a relative path inside priv, for example 42 | # "priv/ssl/server.key". For all supported SSL configuration 43 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 44 | # 45 | # We also recommend setting `force_ssl` in your endpoint, ensuring 46 | # no data is ever sent via http, always redirecting to https: 47 | # 48 | # config :speakr, SpeakrWeb.Endpoint, 49 | # force_ssl: [hsts: true] 50 | # 51 | # Check `Plug.SSL` for all available options in `force_ssl`. 52 | 53 | # Finally import the config/prod.secret.exs which loads secrets 54 | # and configuration from environment variables. 55 | import_config "prod.secret.exs" 56 | -------------------------------------------------------------------------------- /config/prod.secret.exs: -------------------------------------------------------------------------------- 1 | # In this file, we load production configuration and secrets 2 | # from environment variables. You can also hardcode secrets, 3 | # although such is generally not recommended and you have to 4 | # remember to add this file to your .gitignore. 5 | use Mix.Config 6 | 7 | database_url = 8 | System.get_env("DATABASE_URL") || 9 | raise """ 10 | environment variable DATABASE_URL is missing. 11 | For example: ecto://USER:PASS@HOST/DATABASE 12 | """ 13 | 14 | config :speakr, Speakr.Repo, 15 | # ssl: true, 16 | url: database_url, 17 | pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") 18 | 19 | secret_key_base = 20 | System.get_env("SECRET_KEY_BASE") || 21 | raise """ 22 | environment variable SECRET_KEY_BASE is missing. 23 | You can generate one by calling: mix phx.gen.secret 24 | """ 25 | 26 | config :speakr, SpeakrWeb.Endpoint, 27 | http: [ 28 | port: String.to_integer(System.get_env("PORT") || "4000"), 29 | transport_options: [socket_opts: [:inet6]] 30 | ], 31 | secret_key_base: secret_key_base 32 | 33 | # ## Using releases (Elixir v1.9+) 34 | # 35 | # If you are doing OTP releases, you need to instruct Phoenix 36 | # to start each relevant endpoint: 37 | # 38 | # config :speakr, SpeakrWeb.Endpoint, server: true 39 | # 40 | # Then you can assemble a release by calling `mix release`. 41 | # See `mix help release` for more information. 42 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # Configure your database 4 | config :speakr, Speakr.Repo, 5 | username: "postgres", 6 | password: "postgres", 7 | database: "speakr_test", 8 | hostname: "localhost", 9 | pool: Ecto.Adapters.SQL.Sandbox 10 | 11 | # We don't run a server during test. If one is required, 12 | # you can enable the server option below. 13 | config :speakr, SpeakrWeb.Endpoint, 14 | http: [port: 4002], 15 | server: false 16 | 17 | # Print only warnings and errors during test 18 | config :logger, level: :warn 19 | -------------------------------------------------------------------------------- /lib/speakr.ex: -------------------------------------------------------------------------------- 1 | defmodule Speakr do 2 | @moduledoc """ 3 | Speakr 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 | -------------------------------------------------------------------------------- /lib/speakr/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Speakr.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | # List all child processes to be supervised 10 | children = [ 11 | # Start the Ecto repository 12 | Speakr.Repo, 13 | # Start the endpoint when the application starts 14 | SpeakrWeb.Endpoint 15 | # Starts a worker by calling: Speakr.Worker.start_link(arg) 16 | # {Speakr.Worker, arg}, 17 | ] 18 | 19 | # See https://hexdocs.pm/elixir/Supervisor.html 20 | # for other strategies and supported options 21 | opts = [strategy: :one_for_one, name: Speakr.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 | SpeakrWeb.Endpoint.config_change(changed, removed) 29 | :ok 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/speakr/auth/user.ex: -------------------------------------------------------------------------------- 1 | defmodule Speakr.Auth.User do 2 | use Ecto.Schema 3 | use Pow.Ecto.Schema 4 | use Pow.Extension.Ecto.Schema, 5 | extensions: [PowResetPassword, PowEmailConfirmation] 6 | 7 | schema "users" do 8 | pow_user_fields() 9 | 10 | timestamps() 11 | end 12 | 13 | def changeset(user_or_changeset, attrs) do 14 | user_or_changeset 15 | |> pow_changeset(attrs) 16 | |> pow_extension_changeset(attrs) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/speakr/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Speakr.Repo do 2 | use Ecto.Repo, 3 | otp_app: :speakr, 4 | adapter: Ecto.Adapters.Postgres 5 | end 6 | -------------------------------------------------------------------------------- /lib/speakr_web.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb 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 SpeakrWeb, :controller 9 | use SpeakrWeb, :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 | def mailer_view do 20 | quote do 21 | use Phoenix.View, root: "lib/speakr_web/templates", 22 | namespace: SpeakrWeb 23 | 24 | use Phoenix.HTML 25 | end 26 | end 27 | 28 | def controller do 29 | quote do 30 | use Phoenix.Controller, namespace: SpeakrWeb 31 | 32 | import Plug.Conn 33 | import SpeakrWeb.Gettext 34 | alias SpeakrWeb.Router.Helpers, as: Routes 35 | end 36 | end 37 | 38 | def view do 39 | quote do 40 | use Phoenix.View, 41 | root: "lib/speakr_web/templates", 42 | namespace: SpeakrWeb 43 | 44 | # Import convenience functions from controllers 45 | import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1] 46 | 47 | # Use all HTML functionality (forms, tags, etc) 48 | use Phoenix.HTML 49 | 50 | import SpeakrWeb.ErrorHelpers 51 | import SpeakrWeb.Gettext 52 | alias SpeakrWeb.Router.Helpers, as: Routes 53 | end 54 | end 55 | 56 | def router do 57 | quote do 58 | use Phoenix.Router 59 | import Plug.Conn 60 | import Phoenix.Controller 61 | end 62 | end 63 | 64 | def channel do 65 | quote do 66 | use Phoenix.Channel 67 | import SpeakrWeb.Gettext 68 | end 69 | end 70 | 71 | @doc """ 72 | When used, dispatch to the appropriate controller/view/etc. 73 | """ 74 | defmacro __using__(which) when is_atom(which) do 75 | apply(__MODULE__, which, []) 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/speakr_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", SpeakrWeb.RoomChannel 6 | 7 | # Socket params are passed from the client and can 8 | # be used to verify and authenticate a user. After 9 | # verification, you can put default assigns into 10 | # the socket that will be set for all channels, ie 11 | # 12 | # {:ok, assign(socket, :user_id, verified_user_id)} 13 | # 14 | # To deny connection, return `:error`. 15 | # 16 | # See `Phoenix.Token` documentation for examples in 17 | # performing token verification on connect. 18 | def connect(_params, socket, _connect_info) do 19 | {:ok, socket} 20 | end 21 | 22 | # Socket id's are topics that allow you to identify all sockets for a given user: 23 | # 24 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}" 25 | # 26 | # Would allow you to broadcast a "disconnect" event and terminate 27 | # all active sockets and channels for a given user: 28 | # 29 | # SpeakrWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) 30 | # 31 | # Returning `nil` makes this socket anonymous. 32 | def id(_socket), do: nil 33 | end 34 | -------------------------------------------------------------------------------- /lib/speakr_web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.PageController do 2 | use SpeakrWeb, :controller 3 | 4 | def index(conn, _params) do 5 | render(conn, "index.html") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/speakr_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :speakr 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_speakr_key", 10 | signing_salt: "vvXUKowP" 11 | ] 12 | 13 | socket "/socket", SpeakrWeb.UserSocket, 14 | websocket: true, 15 | longpoll: false 16 | 17 | # Serve at "/" the static files from "priv/static" directory. 18 | # 19 | # You should set gzip to true if you are running phx.digest 20 | # when deploying your static files in production. 21 | plug Plug.Static, 22 | at: "/", 23 | from: :speakr, 24 | gzip: false, 25 | only: ~w(css fonts images js favicon.ico robots.txt) 26 | 27 | # Code reloading can be explicitly enabled under the 28 | # :code_reloader configuration of your endpoint. 29 | if code_reloading? do 30 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 31 | plug Phoenix.LiveReloader 32 | plug Phoenix.CodeReloader 33 | end 34 | 35 | plug Plug.RequestId 36 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 37 | 38 | plug Plug.Parsers, 39 | parsers: [:urlencoded, :multipart, :json], 40 | pass: ["*/*"], 41 | json_decoder: Phoenix.json_library() 42 | 43 | plug Plug.MethodOverride 44 | plug Plug.Head 45 | plug Plug.Session, @session_options 46 | 47 | plug Plug.Session, 48 | store: :cookie, 49 | key: "_speakr_key", 50 | signing_salt: "secret-change-in-production" 51 | 52 | plug Pow.Plug.Session, otp_app: :speakr 53 | plug SpeakrWeb.Router 54 | end 55 | -------------------------------------------------------------------------------- /lib/speakr_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.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 SpeakrWeb.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: :speakr 24 | end 25 | -------------------------------------------------------------------------------- /lib/speakr_web/pow_mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.PowMailer do 2 | use Pow.Phoenix.Mailer 3 | require Logger 4 | 5 | def cast(%{user: user, subject: subject, text: text, html: html, assigns: _assigns}) do 6 | # Build email struct to be used in `process/1` 7 | 8 | %{to: user.email, subject: subject, text: text, html: html} 9 | end 10 | 11 | def process(email) do 12 | # Send email 13 | 14 | Logger.debug("E-mail sent: #{inspect email}") 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/speakr_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.Router do 2 | use SpeakrWeb, :router 3 | use Pow.Phoenix.Router 4 | use Pow.Extension.Phoenix.Router, 5 | extensions: [PowResetPassword, PowEmailConfirmation] 6 | 7 | pipeline :protected do 8 | plug Pow.Plug.RequireAuthenticated, 9 | error_handler: Pow.Phoenix.PlugErrorHandler 10 | end 11 | 12 | pipeline :browser do 13 | plug :accepts, ["html"] 14 | plug :fetch_session 15 | plug :fetch_flash 16 | plug :protect_from_forgery 17 | plug :put_secure_browser_headers 18 | end 19 | 20 | pipeline :api do 21 | plug :accepts, ["json"] 22 | end 23 | 24 | scope "/" do 25 | pipe_through :browser 26 | 27 | pow_routes() 28 | pow_extension_routes() 29 | end 30 | 31 | scope "/", SpeakrWeb do 32 | pipe_through [:browser, :protected] 33 | 34 | get "/", PageController, :index 35 | end 36 | 37 | # Other scopes may use custom stacks. 38 | # scope "/api", SpeakrWeb do 39 | # pipe_through :api 40 | # end 41 | end 42 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/layout/app.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Speakr · Phoenix Framework 8 | "/> 9 | <%= csrf_meta_tag() %> 10 | 11 | 12 | <%= render @view_module, @view_template, assigns %> 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/page/index.html.eex: -------------------------------------------------------------------------------- 1 |

2 | Welcome to Speakr 3 |

4 |

5 | Conference Management System created with Phoenix/Elixir 6 |

7 | 8 | <%= if Pow.Plug.current_user(@conn) do %> 9 | <%= link "Sign out", to: Routes.pow_session_path(@conn, :delete), method: :delete %> 10 | <% else %> 11 | <%= link "Register", to: Routes.pow_registration_path(@conn, :new) %> 12 | <%= link "Sign in", to: Routes.pow_session_path(@conn, :new) %> 13 | <% end %> -------------------------------------------------------------------------------- /lib/speakr_web/templates/pow/registration/edit.html.eex: -------------------------------------------------------------------------------- 1 |

Edit profile

2 | 3 | <%= form_for @changeset, @action, [as: :user], fn f -> %> 4 | <%= if @changeset.action do %> 5 |
6 |

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

7 |
8 | <% end %> 9 | 10 | <%= label f, :current_password %> 11 | <%= password_input f, :current_password %> 12 | <%= error_tag f, :current_password %> 13 | 14 | <%= label f, Pow.Ecto.Schema.user_id_field(@changeset) %> 15 | <%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset) %> 16 | <%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %> 17 | 18 | <%= label f, :password %> 19 | <%= password_input f, :password %> 20 | <%= error_tag f, :password %> 21 | 22 | <%= label f, :confirm_password %> 23 | <%= password_input f, :confirm_password %> 24 | <%= error_tag f, :confirm_password %> 25 | 26 |
27 | <%= submit "Update" %> 28 |
29 | <% end %> 30 | 31 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/pow/registration/new.html.eex: -------------------------------------------------------------------------------- 1 |

Register

2 | 3 | <%= form_for @changeset, @action, [as: :user], fn f -> %> 4 | <%= if @changeset.action do %> 5 |
6 |

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

7 |
8 | <% end %> 9 | 10 | <%= label f, Pow.Ecto.Schema.user_id_field(@changeset) %> 11 | <%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset) %> 12 | <%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %> 13 | 14 | <%= label f, :password %> 15 | <%= password_input f, :password %> 16 | <%= error_tag f, :password %> 17 | 18 | <%= label f, :confirm_password %> 19 | <%= password_input f, :confirm_password %> 20 | <%= error_tag f, :confirm_password %> 21 | 22 |
23 | <%= submit "Register" %> 24 |
25 | <% end %> 26 | 27 | 28 | <%= link "Sign in", to: Routes.pow_session_path(@conn, :new) %> 29 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/pow/session/new.html.eex: -------------------------------------------------------------------------------- 1 |

Sign in

2 | 3 | <%= form_for @changeset, @action, [as: :user], fn f -> %> 4 | <%= if @changeset.action do %> 5 |
6 |

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

7 |
8 | <% end %> 9 | 10 | <%= label f, Pow.Ecto.Schema.user_id_field(@changeset) %> 11 | <%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset) %> 12 | <%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %> 13 | 14 | <%= label f, :password %> 15 | <%= password_input f, :password %> 16 | <%= error_tag f, :password %> 17 | 18 |
19 | <%= submit "Sign in" %> 20 |
21 | <% end %> 22 | 23 | 24 | <%= link "Register", to: Routes.pow_registration_path(@conn, :new) %> 25 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/pow_email_confirmation/mailer/email_confirmation.html.eex: -------------------------------------------------------------------------------- 1 | <%= content_tag(:h3, "Hi,") %> 2 | <%= content_tag(:p, "Please use the following link to confirm your e-mail address:") %> 3 | <%= content_tag(:p, link(@url, to: @url)) %> 4 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/pow_email_confirmation/mailer/email_confirmation.text.eex: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Please use the following link to confirm your e-mail address: 4 | 5 | <%= @url %> 6 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/pow_reset_password/mailer/reset_password.html.eex: -------------------------------------------------------------------------------- 1 | <%= content_tag(:h3, "Hi,") %> 2 | <%= content_tag(:p, "Please use the following link to reset your password:") %> 3 | <%= content_tag(:p, link(@url, to: @url)) %> 4 | <%= content_tag(:p, "You can disregard this email if you didn't request a password reset.") %> 5 | -------------------------------------------------------------------------------- /lib/speakr_web/templates/pow_reset_password/mailer/reset_password.text.eex: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Please use the following link to reset your password: 4 | 5 | <%= @url %> 6 | 7 | You can disregard this email if you didn't request a password reset. 8 | -------------------------------------------------------------------------------- /lib/speakr_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.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 | Enum.map(Keyword.get_values(form.errors, field), fn error -> 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 | # When using gettext, we typically pass the strings we want 22 | # to translate as a static argument: 23 | # 24 | # # Translate "is invalid" in the "errors" domain 25 | # dgettext("errors", "is invalid") 26 | # 27 | # # Translate the number of files with plural rules 28 | # dngettext("errors", "1 file", "%{count} files", count) 29 | # 30 | # Because the error messages we show in our forms and APIs 31 | # are defined inside Ecto, we need to translate them dynamically. 32 | # This requires us to call the Gettext module passing our gettext 33 | # backend as first argument. 34 | # 35 | # Note we use the "errors" domain, which means translations 36 | # should be written to the errors.po file. The :count option is 37 | # set by Ecto and indicates we should also apply plural rules. 38 | if count = opts[:count] do 39 | Gettext.dngettext(SpeakrWeb.Gettext, "errors", msg, msg, count, opts) 40 | else 41 | Gettext.dgettext(SpeakrWeb.Gettext, "errors", msg, opts) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/speakr_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.ErrorView do 2 | use SpeakrWeb, :view 3 | 4 | # If you want to customize a particular status code 5 | # for a certain format, you may uncomment below. 6 | # def render("500.html", _assigns) do 7 | # "Internal Server Error" 8 | # end 9 | 10 | # By default, Phoenix returns the status message from 11 | # the template name. For example, "404.html" becomes 12 | # "Not Found". 13 | def template_not_found(template, _assigns) do 14 | Phoenix.Controller.status_message_from_template(template) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/speakr_web/views/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.LayoutView do 2 | use SpeakrWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/speakr_web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.PageView do 2 | use SpeakrWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/speakr_web/views/pow/registration_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.Pow.RegistrationView do 2 | use SpeakrWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/speakr_web/views/pow/session_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.Pow.SessionView do 2 | use SpeakrWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/speakr_web/views/pow_email_confirmation/mailer_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.PowEmailConfirmation.MailerView do 2 | use SpeakrWeb, :mailer_view 3 | 4 | def subject(:email_confirmation, _assigns), do: "Confirm your email address" 5 | end 6 | -------------------------------------------------------------------------------- /lib/speakr_web/views/pow_reset_password/mailer_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.PowResetPassword.MailerView do 2 | use SpeakrWeb, :mailer_view 3 | 4 | def subject(:reset_password, _assigns), do: "Reset password link" 5 | end 6 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Speakr.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :speakr, 7 | version: "0.1.0", 8 | elixir: "~> 1.5", 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: {Speakr.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 | {:phoenix, "~> 1.4.12"}, 37 | {:phoenix_pubsub, "~> 1.1"}, 38 | {:phoenix_ecto, "~> 4.0"}, 39 | {:ecto_sql, "~> 3.1"}, 40 | {:postgrex, ">= 0.0.0"}, 41 | {:phoenix_html, "~> 2.11"}, 42 | {:phoenix_live_reload, "~> 1.2", only: :dev}, 43 | {:gettext, "~> 0.11"}, 44 | {:jason, "~> 1.0"}, 45 | {:plug_cowboy, "~> 2.0"}, 46 | {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}, 47 | {:pow, "~> 1.0.16"} 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 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, 3 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, 4 | "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"}, 6 | "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, 7 | "db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, 8 | "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm"}, 9 | "ecto": {:hex, :ecto, "3.3.1", "82ab74298065bf0c64ca299f6c6785e68ea5d6b980883ee80b044499df35aba1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, 10 | "ecto_sql": {:hex, :ecto_sql, "3.3.2", "92804e0de69bb63e621273c3492252cb08a29475c05d40eeb6f41ad2d483cfd3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, 11 | "file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"}, 12 | "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm"}, 13 | "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, 14 | "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, 15 | "phoenix": {:hex, :phoenix, "1.4.12", "b86fa85a2ba336f5de068549de5ccceec356fd413264a9637e7733395d6cc4ea", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, 16 | "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 17 | "phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 18 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.1", "274a4b07c4adbdd7785d45a8b0bb57634d0b4f45b18d2c508b26c0344bd59b8f", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, 19 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"}, 20 | "plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, 21 | "plug_cowboy": {:hex, :plug_cowboy, "2.1.1", "a196e4f428d7f5d6dba5ded314cc55cd0fbddf1110af620f75c0190e77844b33", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 22 | "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, 23 | "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, 24 | "pow": {:hex, :pow, "1.0.16", "f4d3de2f423962f08740b80d57fee193fa1f4ed2bbe3e94ba4355cb066314d70", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3.0 or ~> 1.4.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 2.0.0 and <= 3.0.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, ">= 1.5.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, 25 | "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, 26 | "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"}, 27 | } 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /priv/gettext/errors.pot: -------------------------------------------------------------------------------- 1 | ## This 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 has 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 | -------------------------------------------------------------------------------- /priv/repo/migrations/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto_sql], 3 | inputs: ["*.exs"] 4 | ] 5 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200125051017_create_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Speakr.Repo.Migrations.CreateUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:users) do 6 | add :email, :string, null: false 7 | add :password_hash, :string 8 | 9 | timestamps() 10 | end 11 | 12 | create unique_index(:users, [:email]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200125054005_add_pow_email_confirmation_to_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Speakr.Repo.Migrations.AddPowEmailConfirmationToUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:users) do 6 | add :email_confirmation_token, :string 7 | add :email_confirmed_at, :utc_datetime 8 | add :unconfirmed_email, :string 9 | end 10 | 11 | create unique_index(:users, [:email_confirmation_token]) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /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 | # Speakr.Repo.insert!(%Speakr.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /test/speakr_web/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.PageControllerTest do 2 | use SpeakrWeb.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 | -------------------------------------------------------------------------------- /test/speakr_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.ErrorViewTest do 2 | use SpeakrWeb.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(SpeakrWeb.ErrorView, "404.html", []) == "Not Found" 9 | end 10 | 11 | test "renders 500.html" do 12 | assert render_to_string(SpeakrWeb.ErrorView, "500.html", []) == "Internal Server Error" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/speakr_web/views/layout_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.LayoutViewTest do 2 | use SpeakrWeb.ConnCase, async: true 3 | 4 | # When testing helpers, you may want to import Phoenix.HTML and 5 | # use functions such as safe_to_string() to convert the helper 6 | # result into an HTML string. 7 | # import Phoenix.HTML 8 | end 9 | -------------------------------------------------------------------------------- /test/speakr_web/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.PageViewTest do 2 | use SpeakrWeb.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.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 data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use SpeakrWeb.ChannelCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # Import conveniences for testing with channels 23 | use Phoenix.ChannelTest 24 | 25 | # The default endpoint for testing 26 | @endpoint SpeakrWeb.Endpoint 27 | end 28 | end 29 | 30 | setup tags do 31 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(Speakr.Repo) 32 | 33 | unless tags[:async] do 34 | Ecto.Adapters.SQL.Sandbox.mode(Speakr.Repo, {:shared, self()}) 35 | end 36 | 37 | :ok 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule SpeakrWeb.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 data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use SpeakrWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # Import conveniences for testing with connections 23 | use Phoenix.ConnTest 24 | alias SpeakrWeb.Router.Helpers, as: Routes 25 | 26 | # The default endpoint for testing 27 | @endpoint SpeakrWeb.Endpoint 28 | end 29 | end 30 | 31 | setup tags do 32 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(Speakr.Repo) 33 | 34 | unless tags[:async] do 35 | Ecto.Adapters.SQL.Sandbox.mode(Speakr.Repo, {:shared, self()}) 36 | end 37 | 38 | {:ok, conn: Phoenix.ConnTest.build_conn()} 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Speakr.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 | we enable the SQL sandbox, so changes done to the database 11 | are reverted at the end of every test. If you are using 12 | PostgreSQL, you can even run database tests asynchronously 13 | by setting `use Speakr.DataCase, async: true`, although 14 | this option is not recommended for other databases. 15 | """ 16 | 17 | use ExUnit.CaseTemplate 18 | 19 | using do 20 | quote do 21 | alias Speakr.Repo 22 | 23 | import Ecto 24 | import Ecto.Changeset 25 | import Ecto.Query 26 | import Speakr.DataCase 27 | end 28 | end 29 | 30 | setup tags do 31 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(Speakr.Repo) 32 | 33 | unless tags[:async] do 34 | Ecto.Adapters.SQL.Sandbox.mode(Speakr.Repo, {:shared, self()}) 35 | end 36 | 37 | :ok 38 | end 39 | 40 | @doc """ 41 | A helper that transforms changeset errors into a map of messages. 42 | 43 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 44 | assert "password is too short" in errors_on(changeset).password 45 | assert %{password: ["password is too short"]} = errors_on(changeset) 46 | 47 | """ 48 | def errors_on(changeset) do 49 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 50 | Regex.replace(~r"%{(\w+)}", message, fn _, key -> 51 | opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() 52 | end) 53 | end) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | Ecto.Adapters.SQL.Sandbox.mode(Speakr.Repo, :manual) 3 | --------------------------------------------------------------------------------