├── .editorconfig ├── .formatter.exs ├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demo ├── .dockerignore ├── .formatter.exs ├── .gitignore ├── Dockerfile ├── README.md ├── assets │ ├── css │ │ └── app.css │ ├── js │ │ ├── app.js │ │ └── copy.js │ └── tailwind.config.js ├── config │ ├── config.exs │ ├── dev.exs │ ├── prod.exs │ ├── runtime.exs │ └── test.exs ├── lib │ ├── namor_demo.ex │ ├── namor_demo │ │ ├── application.ex │ │ └── params.ex │ ├── namor_demo_web.ex │ └── namor_demo_web │ │ ├── endpoint.ex │ │ ├── html │ │ └── error_html.ex │ │ ├── layouts.ex │ │ ├── layouts │ │ └── root.html.heex │ │ ├── live │ │ ├── index_live.ex │ │ └── index_live.html.heex │ │ ├── router.ex │ │ └── telemetry.ex ├── mix.exs ├── mix.lock ├── priv │ └── static │ │ └── robots.txt └── rel │ └── overlays │ └── bin │ ├── server │ └── server.bat ├── dict ├── default │ ├── adjectives.txt │ ├── nouns.txt │ └── verbs.txt ├── reserved.txt └── rugged │ ├── adjectives.txt │ ├── nouns.txt │ └── verbs.txt ├── lib ├── namor.ex └── namor │ ├── dictionary.ex │ └── helpers.ex ├── mix.exs ├── mix.lock └── test ├── dict ├── custom │ ├── adjectives.txt │ ├── nouns.txt │ └── verbs.txt └── reserved.txt ├── namor ├── dictionary_test.exs └── helpers_test.exs ├── namor_test.exs └── test_helper.exs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 2 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"], 3 | line_length: 120 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | permissions: 3 | contents: read 4 | on: 5 | pull_request: 6 | push: 7 | branches: [master] 8 | env: 9 | MIX_ENV: test 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | otp: ["24", "25"] 16 | elixir: ["1.13", "1.14"] 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: erlef/setup-beam@v1 20 | with: 21 | otp-version: ${{ matrix.otp }} 22 | elixir-version: ${{ matrix.elixir }} 23 | - uses: actions/cache@v3 24 | with: 25 | path: deps 26 | key: mix-deps-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} 27 | restore-keys: | 28 | mix-deps-${{ runner.os }}- 29 | - uses: actions/cache@v3 30 | with: 31 | path: _build 32 | key: mix-build-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} 33 | restore-keys: | 34 | mix-build-${{ runner.os }}- 35 | - run: mix deps.get 36 | - run: mix test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore system files 2 | .DS_Store 3 | 4 | # The directory Mix will write compiled artifacts to. 5 | /_build/ 6 | 7 | # If you run "mix test --cover", coverage assets end up here. 8 | /cover/ 9 | 10 | # The directory Mix downloads your dependencies sources to. 11 | /deps/ 12 | 13 | # Where third-party dependencies like ExDoc output generated docs. 14 | /doc/ 15 | 16 | # Ignore .fetch files in case you like to edit your project deps locally. 17 | /.fetch 18 | 19 | # If the VM crashes, it generates a dump, let's ignore it too. 20 | erl_crash.dump 21 | 22 | # Also ignore archive artifacts (built via "mix archive.build"). 23 | *.ez 24 | 25 | # Ignore package tarball (built via "mix hex.build"). 26 | namor-*.tar 27 | 28 | # Temporary files, for example, from tests. 29 | /tmp/ 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.0.3 2 | 3 | - Updated documentation things 4 | 5 | ## v1.0.2 6 | 7 | - Renamed the `:manly` dictionary to `:rugged` 8 | 9 | ## v1.0.1 10 | 11 | - Added demo phoenix app 12 | - Fixed issue where a nil seperator would cause a crash 13 | 14 | ## v1.0.0 15 | 16 | - Initial release 🎉 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Jason Maurer 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 | # Namor 2 | 3 | Namor is a name generator for Elixir that creates random, url-friendly names. This comes in handy if you need to generate unique subdomains like many PaaS/SaaS providers do, or unique names for anything else. Supports compile-time dictionary loading, subdomain validation with reserved names, custom dictionaries and reserved word lists, alternate dictionaries, and more. 4 | 5 | [See a demo here.](https://namor.jsonmaur.com) Also available for [Javascript](https://github.com/jsonmaur/namor.js). 6 | 7 | > _Please Note: Generated names are not always guaranteed to be unique. To reduce the chances of collision, you can increase the length of the trailing number ([see here for collision stats](#collision-stats)). Always be sure to check your database before assuming a generated value is unique._ 8 | 9 | - [Installation](#installation) 10 | - [Getting Started](#getting-started) 11 | - [Collision Stats](#collision-stats) 12 | - [Custom Dictionaries](#custom-dictionaries) 13 | 14 | ## Installation 15 | 16 | ```elixir 17 | def deps do 18 | [ 19 | {:namor, "~> 1.0"} 20 | ] 21 | end 22 | ``` 23 | 24 | ## Getting Started 25 | 26 | ```elixir 27 | iex> require Namor 28 | 29 | iex> Namor.generate() 30 | {:ok, "sandwich-invent"} 31 | 32 | iex> Namor.generate(salt: 5) 33 | {:ok, "sandwich-invent-s86uo"} 34 | 35 | iex> Namor.generate(words: 3, dictionary: :rugged) 36 | {:ok, "savage-whiskey-stain"} 37 | ``` 38 | 39 | An example module that generates subdomains for users (does not check for database uniqueness): 40 | 41 | ```elixir 42 | defmodule MyApp.Subdomains do 43 | use Namor 44 | 45 | @salt_length 5 46 | 47 | def get_new_subdomain(nil), do: Namor.generate(salt: @salt_length) 48 | 49 | def get_new_subdomain(name) do 50 | with false <- Namor.reserved?(name), 51 | subdomain <- Namor.with_salt(name, @salt_length), 52 | true <- Namor.subdomain?(subdomain) do 53 | {:ok, subdomain} 54 | else 55 | _ -> {:error, :invalid_subdomain} 56 | end 57 | end 58 | end 59 | ``` 60 | 61 | ## Collision Stats 62 | 63 | The following stats give you the total number of permutations based on the word count (without a salt), and can help you make a decision on how long to make your salt. This data is based on the number of words we currently have in our [dictionary files](https://github.com/jsonmaur/namor/tree/master/dict). 64 | 65 | ##### `:default` dictionary 66 | 67 | - 1-word combinations: 7,948 68 | - 2-word combinations: 11,386,875 69 | - 3-word combinations: 12,382,548,750 70 | - 4-word combinations: 23,217,278,906,250 71 | 72 | ##### `:rugged` dictionary 73 | 74 | - 1-word combinations: 735 75 | - 2-word combinations: 127,400 76 | - 3-word combinations: 14,138,880 77 | - 4-word combinations: 3,958,886,400 78 | 79 | ## Custom Dictionaries 80 | 81 | In order for our dictionary files to be loaded into your application during compilation, [`generate/1`](https://hexdocs.pm/namor/Namor.html#generate/1) and [`reserved?/1`](https://hexdocs.pm/namor/Namor.html#reserved?/1) are defined as a macros. This means they can only be used after calling `use Namor` or `require Namor`, which should be done during compilation (and not inside a function). If you want to use your own dictionary, consider calling [`Namor.Helpers.get_dict!/2`](https://hexdocs.pm/namor/Namor.Helpers.html#get_dict!/2) in a place that executes during compilation and **not** runtime. For example: 82 | 83 | ``` 84 | ┌── dictionaries/ 85 | │ ┌── foobar/ 86 | │ │ ┌── adjectives.txt 87 | │ │ ├── nouns.txt 88 | │ │ └── verbs.txt 89 | │ └── reserved.txt 90 | ``` 91 | 92 | ```elixir 93 | defmodule MyApp.Subdomains do 94 | use Namor 95 | 96 | @salt_length 5 97 | @base_path Path.expand("./dictionaries", __DIR__) 98 | 99 | @reserved Namor.Helpers.get_dict!("reserved.txt", @base_path) 100 | @dictionary Namor.Helpers.get_dict!(:foobar, @base_path) 101 | 102 | defp reserved, do: @reserved 103 | defp dictionary, do: @dictionary 104 | 105 | def get_new_subdomain(nil), do: Namor.generate([salt: @salt_length], dictionary()) 106 | 107 | def get_new_subdomain(name) do 108 | with false <- Namor.reserved?(name, reserved()), 109 | subdomain <- Namor.with_salt(name, @salt_length), 110 | true <- Namor.subdomain?(subdomain) do 111 | {:ok, subdomain} 112 | else 113 | _ -> {:error, :invalid_subdomain} 114 | end 115 | end 116 | end 117 | ``` 118 | -------------------------------------------------------------------------------- /demo/.dockerignore: -------------------------------------------------------------------------------- 1 | # This file excludes paths from the Docker build context. 2 | # 3 | # By default, Docker's build context includes all files (and folders) in the 4 | # current directory. Even if a file isn't copied into the container it is still sent to 5 | # the Docker daemon. 6 | # 7 | # There are multiple reasons to exclude files from the build context: 8 | # 9 | # 1. Prevent nested folders from being copied into the container (ex: exclude 10 | # /assets/node_modules when copying /assets) 11 | # 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc) 12 | # 3. Avoid sending files containing sensitive information 13 | # 14 | # More information on using .dockerignore is available here: 15 | # https://docs.docker.com/engine/reference/builder/#dockerignore-file 16 | 17 | .dockerignore 18 | 19 | # Ignore git, but keep git HEAD and refs to access current commit hash if needed: 20 | # 21 | # $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat 22 | # d0b8727759e1e0e7aa3d41707d12376e373d5ecc 23 | .git 24 | !.git/HEAD 25 | !.git/refs 26 | 27 | # Common development/test artifacts 28 | /cover/ 29 | /doc/ 30 | /test/ 31 | /tmp/ 32 | .elixir_ls 33 | 34 | # Mix artifacts 35 | /_build/ 36 | /deps/ 37 | *.ez 38 | 39 | # Generated on crash by the VM 40 | erl_crash.dump 41 | 42 | # Static artifacts - These should be fetched and built inside the Docker image 43 | /assets/node_modules/ 44 | /priv/static/assets/ 45 | /priv/static/cache_manifest.json 46 | -------------------------------------------------------------------------------- /demo/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | plugins: [Phoenix.LiveView.HTMLFormatter], 4 | inputs: ["*.{heex,ex,exs}", "{config,lib}/**/*.{heex,ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /demo/.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 | namor_demo-*.tar 24 | 25 | # Ignore assets that are produced by build tools. 26 | /priv/static/assets/ 27 | 28 | # Ignore digested assets cache. 29 | /priv/static/cache_manifest.json 30 | 31 | # In case you use Node.js/npm, you want to ignore these. 32 | /assets/node_modules/ 33 | -------------------------------------------------------------------------------- /demo/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELIXIR_VERSION=1.14.3 2 | ARG OTP_VERSION=25.2.1 3 | ARG DEBIAN_VERSION=bullseye-20230109-slim 4 | 5 | ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" 6 | ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" 7 | 8 | FROM ${BUILDER_IMAGE} as builder 9 | 10 | # install build dependencies 11 | RUN apt-get update -y && apt-get install -y build-essential git && \ 12 | apt-get clean && rm -f /var/lib/apt/lists/*_* 13 | 14 | # prepare build dir 15 | WORKDIR /app 16 | 17 | # install hex + rebar 18 | RUN mix local.hex --force && \ 19 | mix local.rebar --force 20 | 21 | # set build ENV 22 | ENV MIX_ENV="prod" 23 | 24 | # install mix dependencies 25 | COPY mix.exs mix.lock ./ 26 | RUN mix deps.get --only $MIX_ENV 27 | RUN mkdir config 28 | 29 | # copy compile-time config files before we compile dependencies 30 | # to ensure any relevant config change will trigger the dependencies 31 | # to be re-compiled. 32 | COPY config/config.exs config/${MIX_ENV}.exs config/ 33 | RUN mix deps.compile 34 | 35 | COPY assets assets 36 | COPY lib lib 37 | COPY priv priv 38 | 39 | RUN mix assets 40 | RUN mix compile 41 | 42 | # Changes to config/runtime.exs don't require recompiling the code 43 | COPY config/runtime.exs config/ 44 | 45 | COPY rel rel 46 | RUN mix release 47 | 48 | # start a new build stage so that the final image will only contain 49 | # the compiled release and other runtime necessities 50 | FROM ${RUNNER_IMAGE} 51 | 52 | RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales && \ 53 | apt-get clean && rm -f /var/lib/apt/lists/*_* 54 | 55 | # Set the locale 56 | RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen 57 | 58 | ENV LANG en_US.UTF-8 59 | ENV LANGUAGE en_US:en 60 | ENV LC_ALL en_US.UTF-8 61 | 62 | WORKDIR "/app" 63 | RUN chown nobody /app 64 | 65 | # set runner ENV 66 | ENV MIX_ENV="prod" 67 | 68 | # Only copy the final release from the build stage 69 | COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/namor_demo ./ 70 | 71 | USER nobody 72 | 73 | CMD ["/app/bin/server"] 74 | 75 | # Appended by flyctl 76 | ENV ECTO_IPV6 true 77 | ENV ERL_AFLAGS "-proto_dist inet6_tcp" 78 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # NamorDemo 2 | 3 | To start your Phoenix server: 4 | 5 | * Run `mix setup` to install and setup dependencies 6 | * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` 7 | 8 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 9 | 10 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). 11 | 12 | ## Learn more 13 | 14 | * Official website: https://www.phoenixframework.org/ 15 | * Guides: https://hexdocs.pm/phoenix/overview.html 16 | * Docs: https://hexdocs.pm/phoenix 17 | * Forum: https://elixirforum.com/c/phoenix-forum 18 | * Source: https://github.com/phoenixframework/phoenix 19 | -------------------------------------------------------------------------------- /demo/assets/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .select-wrapper select { 6 | @apply text-sm border-gray-300 rounded-md shadow-sm disabled:bg-gray-100 disabled:cursor-not-allowed focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:focus:border-primary-500 dark:bg-gray-800 dark:text-gray-300 focus:outline-none; 7 | } 8 | 9 | label.has-error:not(.phx-no-feedback) { 10 | @apply !text-red-900 dark:!text-red-200; 11 | } 12 | 13 | textarea.has-error:not(.phx-no-feedback), input.has-error:not(.phx-no-feedback), select.has-error:not(.phx-no-feedback) { 14 | @apply !border-red-500 focus:!border-red-500 !text-red-900 !placeholder-red-700 !bg-red-50 dark:!text-red-100 dark:!placeholder-red-300 dark:!bg-red-900 focus:!ring-red-500; 15 | } 16 | 17 | input[type=file_input].has-error:not(.phx-no-feedback) { 18 | @apply !border-red-500 !rounded-md focus:!border-red-500 !text-red-900 !placeholder-red-700 !bg-red-50 file:!border-none dark:!border-none dark:!bg-[#160B0B] dark:text-red-400; 19 | } 20 | 21 | input[type=checkbox].has-error:not(.phx-no-feedback) { 22 | @apply !border-red-500 !text-red-900 dark:!text-red-200; 23 | } 24 | 25 | input[type=radio].has-error:not(.phx-no-feedback) { 26 | @apply !border-red-500; 27 | } 28 | 29 | /* Modal animation */ 30 | .animate-fade-in-scale { 31 | animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys; 32 | } 33 | 34 | .animate-fade-in { 35 | animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys; 36 | } 37 | 38 | @keyframes fade-in-scale-keys{ 39 | 0% { scale: 0.95; opacity: 0; } 40 | 100% { scale: 1.0; opacity: 1; } 41 | } 42 | 43 | @keyframes fade-in-keys{ 44 | 0% { opacity: 0; } 45 | 100% { opacity: 1; } 46 | } 47 | -------------------------------------------------------------------------------- /demo/assets/js/app.js: -------------------------------------------------------------------------------- 1 | import "phoenix_html" 2 | import { Socket } from "phoenix" 3 | import { LiveSocket } from "phoenix_live_view" 4 | import { CopyHook } from "./copy" 5 | 6 | let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") 7 | let liveSocket = new LiveSocket("/live", Socket, { 8 | params: {_csrf_token: csrfToken}, 9 | hooks: {Copy: CopyHook} 10 | }) 11 | 12 | // connect if there are any LiveViews on the page 13 | liveSocket.connect() 14 | 15 | // expose liveSocket on window for web console debug logs and latency simulation: 16 | // >> liveSocket.enableDebug() 17 | // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session 18 | // >> liveSocket.disableLatencySim() 19 | window.liveSocket = liveSocket 20 | -------------------------------------------------------------------------------- /demo/assets/js/copy.js: -------------------------------------------------------------------------------- 1 | export const CopyHook = { 2 | mounted() { 3 | this.el.addEventListener("click", async evt => { 4 | evt.preventDefault() 5 | 6 | const text = document.querySelector(this.el.dataset.to).innerText 7 | await navigator.clipboard.writeText(text) 8 | 9 | this.el.classList.add("hidden") 10 | this.el.nextElementSibling.classList.remove("hidden") 11 | 12 | if (this.hideTimeout) { 13 | clearTimeout(this.hideTimeout) 14 | } 15 | 16 | this.hideTimeout = setTimeout(() => { 17 | this.el.classList.remove("hidden") 18 | this.el.nextElementSibling.classList.add("hidden") 19 | }, 1500) 20 | }) 21 | }, 22 | destroyed() { 23 | clearTimeout(this.hideTimeout) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/assets/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const plugin = require("tailwindcss/plugin") 2 | const colors = require("tailwindcss/colors"); 3 | 4 | module.exports = { 5 | content: [ 6 | "../lib/*_web.ex", 7 | "../lib/*_web/**/*.*ex", 8 | "../deps/petal_components/**/*.*ex", 9 | ], 10 | darkMode: "class", 11 | theme: { 12 | extend: { 13 | colors: { 14 | primary: colors.blue, 15 | secondary: colors.pink, 16 | }, 17 | } 18 | }, 19 | plugins: [ 20 | require("@tailwindcss/forms"), 21 | plugin(({ addVariant }) => addVariant("phx-no-feedback", ["&.phx-no-feedback", ".phx-no-feedback &"])), 22 | plugin(({ addVariant }) => addVariant("phx-click-loading", ["&.phx-click-loading", ".phx-click-loading &"])), 23 | plugin(({ addVariant }) => addVariant("phx-submit-loading", ["&.phx-submit-loading", ".phx-submit-loading &"])), 24 | plugin(({ addVariant }) => addVariant("phx-change-loading", ["&.phx-change-loading", ".phx-change-loading &"])) 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /demo/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the 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 | import Config 9 | 10 | # Configures the endpoint 11 | config :namor_demo, NamorDemoWeb.Endpoint, 12 | url: [host: "localhost"], 13 | pubsub_server: NamorDemo.PubSub, 14 | live_view: [signing_salt: "lHGGHnrT"], 15 | render_errors: [ 16 | layout: false, 17 | formats: [html: NamorDemoWeb.ErrorHTML] 18 | ] 19 | 20 | # Configure esbuild (the version is required) 21 | config :esbuild, 22 | version: "0.15.18", 23 | default: [ 24 | cd: Path.expand("../assets", __DIR__), 25 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}, 26 | args: ~w( 27 | --bundle 28 | --target=es2017 29 | --outdir=../priv/static/assets 30 | js/app.js 31 | ) 32 | ] 33 | 34 | # Configure tailwind (the version is required) 35 | config :tailwind, 36 | version: "3.2.4", 37 | default: [ 38 | cd: Path.expand("../assets", __DIR__), 39 | args: ~w( 40 | --config=tailwind.config.js 41 | --input=css/app.css 42 | --output=../priv/static/assets/app.css 43 | ) 44 | ] 45 | 46 | # Configures Elixir's Logger 47 | config :logger, :console, 48 | format: "$time $metadata[$level] $message\n", 49 | metadata: [:request_id] 50 | 51 | # Use Jason for JSON parsing in Phoenix 52 | config :phoenix, :json_library, Jason 53 | 54 | # Import environment specific config. This must remain at the bottom 55 | # of this file so it overrides the configuration defined above. 56 | import_config "#{config_env()}.exs" 57 | -------------------------------------------------------------------------------- /demo/config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Enable dev routes for dashboard and mailbox 4 | config :namor_demo, dev_routes: true 5 | 6 | # For development, we disable any cache and enable 7 | # debugging and code reloading. The watchers configuration 8 | # can be used to run external watchers to your application. 9 | config :namor_demo, NamorDemoWeb.Endpoint, 10 | http: [ip: {0, 0, 0, 0}, port: 4000], 11 | check_origin: false, 12 | code_reloader: true, 13 | debug_errors: true, 14 | secret_key_base: "ZS05nY0jJwUW+fCjDPjYhnP0A9QEia9+hIIbt8L9vjIV62BrJluM6KX3PXKnJo07", 15 | watchers: [ 16 | esbuild: {Esbuild, :install_and_run, [:default, ~w(--watch)]}, 17 | tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} 18 | ] 19 | 20 | # Watch static and templates for browser reloading. 21 | config :namor_demo, NamorDemoWeb.Endpoint, 22 | live_reload: [ 23 | patterns: [ 24 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", 25 | ~r"lib/namor_demo_web/(html|layouts|live)/.*(ex|heex)$" 26 | ] 27 | ] 28 | 29 | # Do not include metadata nor timestamps in development logs 30 | config :logger, :console, format: "[$level] $message\n" 31 | 32 | # Set a higher stacktrace during development. Avoid configuring such 33 | # in production as building large stacktraces may be expensive. 34 | config :phoenix, :stacktrace_depth, 20 35 | 36 | # Initialize plugs at runtime for faster development compilation 37 | config :phoenix, :plug_init_mode, :runtime 38 | -------------------------------------------------------------------------------- /demo/config/prod.exs: -------------------------------------------------------------------------------- 1 | import 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 :namor_demo, NamorDemoWeb.Endpoint, 13 | cache_static_manifest: "priv/static/cache_manifest.json" 14 | 15 | # Do not print debug messages in production 16 | config :logger, level: :info 17 | 18 | # Runtime production configuration, including reading 19 | # of environment variables, is done on config/runtime.exs. 20 | -------------------------------------------------------------------------------- /demo/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # config/runtime.exs is executed for all environments, including 4 | # during releases. It is executed after compilation and before the 5 | # system starts, so it is typically used to load production configuration 6 | # and secrets from environment variables or elsewhere. Do not define 7 | # any compile-time configuration in here, as it won't be applied. 8 | # The block below contains prod specific runtime configuration. 9 | 10 | # ## Using releases 11 | # 12 | # If you use `mix release`, you need to explicitly enable the server 13 | # by passing the PHX_SERVER=true when you start it: 14 | # 15 | # PHX_SERVER=true bin/namor_demo start 16 | # 17 | # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 | # script that automatically sets the env var above. 19 | if System.get_env("PHX_SERVER") do 20 | config :namor_demo, NamorDemoWeb.Endpoint, server: true 21 | end 22 | 23 | if config_env() == :prod do 24 | # The secret key base is used to sign/encrypt cookies and other secrets. 25 | # A default value is used in config/dev.exs and config/test.exs but you 26 | # want to use a different value for prod and you most likely don't want 27 | # to check this value into version control, so we use an environment 28 | # variable instead. 29 | secret_key_base = 30 | System.get_env("SECRET_KEY_BASE") || 31 | raise """ 32 | environment variable SECRET_KEY_BASE is missing. 33 | You can generate one by calling: mix phx.gen.secret 34 | """ 35 | 36 | host = System.get_env("PHX_HOST") || "example.com" 37 | port = String.to_integer(System.get_env("PORT") || "4000") 38 | 39 | config :namor_demo, NamorDemoWeb.Endpoint, 40 | url: [host: host, port: 443, scheme: "https"], 41 | http: [ 42 | # Enable IPv6 and bind on all interfaces. 43 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 44 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html 45 | # for details about using IPv6 vs IPv4 and loopback vs public addresses. 46 | ip: {0, 0, 0, 0, 0, 0, 0, 0}, 47 | port: port 48 | ], 49 | secret_key_base: secret_key_base 50 | 51 | # ## SSL Support 52 | # 53 | # To get SSL working, you will need to add the `https` key 54 | # to your endpoint configuration: 55 | # 56 | # config :namor_demo, NamorDemoWeb.Endpoint, 57 | # https: [ 58 | # ..., 59 | # port: 443, 60 | # cipher_suite: :strong, 61 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 62 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 63 | # ] 64 | # 65 | # The `cipher_suite` is set to `:strong` to support only the 66 | # latest and more secure SSL ciphers. This means old browsers 67 | # and clients may not be supported. You can set it to 68 | # `:compatible` for wider support. 69 | # 70 | # `:keyfile` and `:certfile` expect an absolute path to the key 71 | # and cert in disk or a relative path inside priv, for example 72 | # "priv/ssl/server.key". For all supported SSL configuration 73 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 74 | # 75 | # We also recommend setting `force_ssl` in your endpoint, ensuring 76 | # no data is ever sent via http, always redirecting to https: 77 | # 78 | # config :namor_demo, NamorDemoWeb.Endpoint, 79 | # force_ssl: [hsts: true] 80 | # 81 | # Check `Plug.SSL` for all available options in `force_ssl`. 82 | end 83 | -------------------------------------------------------------------------------- /demo/config/test.exs: -------------------------------------------------------------------------------- 1 | import 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 :namor_demo, NamorDemoWeb.Endpoint, 6 | http: [ip: {127, 0, 0, 1}, port: 4002], 7 | secret_key_base: "yVAApZKJvTm55/9YHypmkBBOWEIffe5qSYhbE6envx52JLXtyXy1oNuDNq25s53f", 8 | server: false 9 | 10 | # Print only warnings and errors during test 11 | config :logger, level: :warning 12 | 13 | # Initialize plugs at runtime for faster test compilation 14 | config :phoenix, :plug_init_mode, :runtime 15 | -------------------------------------------------------------------------------- /demo/lib/namor_demo.ex: -------------------------------------------------------------------------------- 1 | defmodule NamorDemo do 2 | @moduledoc """ 3 | NamorDemo 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 | -------------------------------------------------------------------------------- /demo/lib/namor_demo/application.ex: -------------------------------------------------------------------------------- 1 | defmodule NamorDemo.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 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | # Start the Telemetry supervisor 12 | NamorDemoWeb.Telemetry, 13 | # Start the PubSub system 14 | {Phoenix.PubSub, name: NamorDemo.PubSub}, 15 | # Start the Endpoint (http/https) 16 | NamorDemoWeb.Endpoint 17 | # Start a worker by calling: NamorDemo.Worker.start_link(arg) 18 | # {NamorDemo.Worker, arg} 19 | ] 20 | 21 | # See https://hexdocs.pm/elixir/Supervisor.html 22 | # for other strategies and supported options 23 | opts = [strategy: :one_for_one, name: NamorDemo.Supervisor] 24 | Supervisor.start_link(children, opts) 25 | end 26 | 27 | # Tell Phoenix to update the endpoint configuration 28 | # whenever the application is updated. 29 | @impl true 30 | def config_change(changed, _new, removed) do 31 | NamorDemoWeb.Endpoint.config_change(changed, removed) 32 | :ok 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /demo/lib/namor_demo/params.ex: -------------------------------------------------------------------------------- 1 | defmodule NamorDemo.Params do 2 | import Ecto.Changeset 3 | 4 | alias __MODULE__ 5 | 6 | defstruct [:words, :salt, :salt_type, :separator, :dictionary] 7 | 8 | @types %{ 9 | words: :integer, 10 | salt: :integer, 11 | salt_type: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: [:mixed, :letters, :numbers])}, 12 | separator: :string, 13 | dictionary: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: [:default, :rugged])} 14 | } 15 | 16 | def changeset(%Params{} = params, attrs \\ %{}) do 17 | {params, @types} 18 | |> cast(attrs, Map.keys(@types), empty_values: []) 19 | |> validate_number(:words, greater_than: 0, less_than_or_equal_to: 4) 20 | |> validate_number(:salt, greater_than_or_equal_to: 0) 21 | |> validate_length(:separator, min: 0, max: 10) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /demo/lib/namor_demo_web.ex: -------------------------------------------------------------------------------- 1 | defmodule NamorDemoWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, components, channels, and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use NamorDemoWeb, :controller 9 | use NamorDemoWeb, :html 10 | 11 | The definitions below will be executed for every controller, 12 | component, 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 additional modules and import 17 | those modules here. 18 | """ 19 | 20 | def static_paths, do: ~w(assets robots.txt) 21 | 22 | def router do 23 | quote do 24 | use Phoenix.Router 25 | 26 | # Import common connection and controller functions to use in pipelines 27 | import Plug.Conn 28 | import Phoenix.Controller 29 | import Phoenix.LiveView.Router 30 | end 31 | end 32 | 33 | def channel do 34 | quote do 35 | use Phoenix.Channel 36 | end 37 | end 38 | 39 | def controller do 40 | quote do 41 | use Phoenix.Controller, 42 | formats: [:html], 43 | layouts: [html: NamorDemoWeb.Layouts] 44 | 45 | import Plug.Conn 46 | 47 | unquote(verified_routes()) 48 | end 49 | end 50 | 51 | def live_view do 52 | quote do 53 | use Phoenix.LiveView 54 | 55 | unquote(html_helpers()) 56 | end 57 | end 58 | 59 | def live_component do 60 | quote do 61 | use Phoenix.LiveComponent 62 | 63 | unquote(html_helpers()) 64 | end 65 | end 66 | 67 | def html do 68 | quote do 69 | use Phoenix.Component 70 | 71 | # Import convenience functions from controllers 72 | import Phoenix.Controller, 73 | only: [get_csrf_token: 0, view_module: 1, view_template: 1] 74 | 75 | # Include general helpers for rendering HTML 76 | unquote(html_helpers()) 77 | end 78 | end 79 | 80 | defp html_helpers do 81 | quote do 82 | use PetalComponents 83 | use Phoenix.HTML 84 | 85 | alias NamorDemoWeb.Router.Helpers, as: Routes 86 | alias Phoenix.LiveView.JS 87 | 88 | # Routes generation with the ~p sigil 89 | unquote(verified_routes()) 90 | end 91 | end 92 | 93 | def verified_routes do 94 | quote do 95 | use Phoenix.VerifiedRoutes, 96 | endpoint: NamorDemoWeb.Endpoint, 97 | router: NamorDemoWeb.Router, 98 | statics: NamorDemoWeb.static_paths() 99 | end 100 | end 101 | 102 | @doc """ 103 | When used, dispatch to the appropriate controller/view/etc. 104 | """ 105 | defmacro __using__(which) when is_atom(which) do 106 | apply(__MODULE__, which, []) 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /demo/lib/namor_demo_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule NamorDemoWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :namor_demo 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: "_namor_demo_session", 10 | signing_salt: "EUT9OBsN", 11 | same_site: "Lax" 12 | ] 13 | 14 | socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 15 | 16 | # Serve at "/" the static files from "priv/static" directory. 17 | # 18 | # You should set gzip to true if you are running phx.digest 19 | # when deploying your static files in production. 20 | plug Plug.Static, 21 | at: "/", 22 | from: :namor_demo, 23 | gzip: false, 24 | only: NamorDemoWeb.static_paths() 25 | 26 | # Code reloading can be explicitly enabled under the 27 | # :code_reloader configuration of your endpoint. 28 | if code_reloading? do 29 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 30 | plug Phoenix.LiveReloader 31 | plug Phoenix.CodeReloader 32 | end 33 | 34 | plug Plug.RequestId 35 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 36 | 37 | plug Plug.Parsers, 38 | parsers: [:urlencoded, :multipart, :json], 39 | pass: ["*/*"], 40 | json_decoder: Phoenix.json_library() 41 | 42 | plug Plug.MethodOverride 43 | plug Plug.Head 44 | plug Plug.Session, @session_options 45 | plug NamorDemoWeb.Router 46 | end 47 | -------------------------------------------------------------------------------- /demo/lib/namor_demo_web/html/error_html.ex: -------------------------------------------------------------------------------- 1 | defmodule NamorDemoWeb.ErrorHTML do 2 | use NamorDemoWeb, :html 3 | 4 | # If you want to customize your error pages, 5 | # uncomment the embed_templates/1 call below 6 | # and add pages to the error directory: 7 | # 8 | # * lib/namor_demo_web/controllers/error_html/404.html.heex 9 | # * lib/namor_demo_web/controllers/error_html/500.html.heex 10 | # 11 | # embed_templates "error_html/*" 12 | 13 | # The default is to render a plain text page based on 14 | # the template name. For example, "404.html" becomes 15 | # "Not Found". 16 | def render(template, _assigns) do 17 | Phoenix.Controller.status_message_from_template(template) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /demo/lib/namor_demo_web/layouts.ex: -------------------------------------------------------------------------------- 1 | defmodule NamorDemoWeb.Layouts do 2 | use NamorDemoWeb, :html 3 | 4 | embed_templates "layouts/*" 5 | end 6 | -------------------------------------------------------------------------------- /demo/lib/namor_demo_web/layouts/root.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Namor Demo 10 | 11 | 12 |