├── .formatter.exs
├── .gitignore
├── .semaphore
└── semaphore.yml
├── .tool-versions
├── LICENSE
├── README.md
├── apps
├── firestorm
│ ├── .formatter.exs
│ ├── .gitignore
│ ├── README.md
│ ├── config
│ │ ├── config.exs
│ │ ├── dev.exs
│ │ ├── prod.exs
│ │ └── test.exs
│ ├── lib
│ │ ├── firestorm.ex
│ │ ├── firestorm
│ │ │ ├── application.ex
│ │ │ └── firestorm_admin
│ │ │ │ └── firestorm_admin.ex
│ │ ├── firestorm_web.ex
│ │ └── firestorm_web
│ │ │ ├── channels
│ │ │ └── user_socket.ex
│ │ │ ├── context.ex
│ │ │ ├── controllers
│ │ │ ├── admin
│ │ │ │ ├── category_controller.ex
│ │ │ │ ├── post_controller.ex
│ │ │ │ ├── thread_controller.ex
│ │ │ │ └── user_controller.ex
│ │ │ ├── auth_controller.ex
│ │ │ └── page_controller.ex
│ │ │ ├── endpoint.ex
│ │ │ ├── gettext.ex
│ │ │ ├── loaders
│ │ │ ├── posts.ex
│ │ │ └── threads.ex
│ │ │ ├── plug
│ │ │ └── ensure_admin.ex
│ │ │ ├── resolvers
│ │ │ ├── categories.ex
│ │ │ ├── posts.ex
│ │ │ ├── threads.ex
│ │ │ └── users.ex
│ │ │ ├── router.ex
│ │ │ ├── schema.ex
│ │ │ ├── schema
│ │ │ ├── categories_types.ex
│ │ │ ├── pagination_types.ex
│ │ │ ├── posts_types.ex
│ │ │ ├── threads_types.ex
│ │ │ └── users_types.ex
│ │ │ ├── templates
│ │ │ ├── admin
│ │ │ │ ├── category
│ │ │ │ │ ├── edit.html.eex
│ │ │ │ │ ├── form.html.eex
│ │ │ │ │ ├── index.html.eex
│ │ │ │ │ ├── new.html.eex
│ │ │ │ │ └── show.html.eex
│ │ │ │ ├── post
│ │ │ │ │ ├── edit.html.eex
│ │ │ │ │ ├── form.html.eex
│ │ │ │ │ ├── index.html.eex
│ │ │ │ │ ├── new.html.eex
│ │ │ │ │ └── show.html.eex
│ │ │ │ ├── thread
│ │ │ │ │ ├── edit.html.eex
│ │ │ │ │ ├── form.html.eex
│ │ │ │ │ ├── index.html.eex
│ │ │ │ │ ├── new.html.eex
│ │ │ │ │ └── show.html.eex
│ │ │ │ └── user
│ │ │ │ │ ├── edit.html.eex
│ │ │ │ │ ├── form.html.eex
│ │ │ │ │ ├── index.html.eex
│ │ │ │ │ ├── new.html.eex
│ │ │ │ │ └── show.html.eex
│ │ │ ├── auth
│ │ │ │ ├── forgot_password_verification.html.eex
│ │ │ │ └── login.html.eex
│ │ │ ├── layout
│ │ │ │ ├── app.html.eex
│ │ │ │ └── torch.html.eex
│ │ │ └── page
│ │ │ │ └── index.html.eex
│ │ │ └── views
│ │ │ ├── admin
│ │ │ ├── category_view.ex
│ │ │ ├── post_view.ex
│ │ │ ├── thread_view.ex
│ │ │ └── user_view.ex
│ │ │ ├── auth_view.ex
│ │ │ ├── error_helpers.ex
│ │ │ ├── error_view.ex
│ │ │ ├── layout_view.ex
│ │ │ └── page_view.ex
│ ├── mix.exs
│ ├── priv
│ │ ├── gettext
│ │ │ ├── en
│ │ │ │ └── LC_MESSAGES
│ │ │ │ │ └── errors.po
│ │ │ └── errors.pot
│ │ ├── static
│ │ │ ├── css
│ │ │ │ ├── app.css
│ │ │ │ └── base_admin.css
│ │ │ └── images
│ │ │ │ └── firestorm-logo.png
│ │ └── templates
│ │ │ ├── phx.gen.context
│ │ │ ├── access_no_schema.ex
│ │ │ ├── context.ex
│ │ │ ├── context_test.exs
│ │ │ ├── schema_access.ex
│ │ │ └── test_cases.exs
│ │ │ └── phx.gen.html
│ │ │ ├── controller.ex
│ │ │ ├── controller_test.exs
│ │ │ ├── edit.html.eex
│ │ │ ├── form.html.eex
│ │ │ ├── index.html.eex
│ │ │ ├── new.html.eex
│ │ │ ├── show.html.eex
│ │ │ └── view.ex
│ └── test
│ │ ├── absinthe
│ │ ├── mutations
│ │ │ ├── categories_test.exs
│ │ │ ├── posts_test.exs
│ │ │ ├── threads_test.exs
│ │ │ └── users_test.exs
│ │ ├── queries
│ │ │ ├── categories_test.exs
│ │ │ └── threads_test.exs
│ │ └── subscriptions
│ │ │ ├── category_added_test.exs
│ │ │ ├── post_added_test.exs
│ │ │ └── thread_added_test.exs
│ │ ├── firestorm
│ │ └── firestorm_admin
│ │ │ └── firestorm_admin_test.exs
│ │ ├── firestorm_web
│ │ ├── controllers
│ │ │ ├── category_controller_test.exs
│ │ │ ├── page_controller_test.exs
│ │ │ ├── post_controller_test.exs
│ │ │ ├── thread_controller_test.exs
│ │ │ └── user_controller_test.exs
│ │ └── views
│ │ │ ├── error_view_test.exs
│ │ │ ├── layout_view_test.exs
│ │ │ └── page_view_test.exs
│ │ ├── support
│ │ ├── channel_case.ex
│ │ ├── conn_case.ex
│ │ ├── factories.ex
│ │ └── subscription_case.ex
│ │ └── test_helper.exs
└── firestorm_data
│ ├── .formatter.exs
│ ├── .gitignore
│ ├── README.md
│ ├── config
│ ├── config.exs
│ ├── dev.exs
│ ├── prod.exs
│ └── test.exs
│ ├── lib
│ ├── firestorm_data.ex
│ ├── firestorm_data
│ │ ├── application.ex
│ │ ├── categories
│ │ │ ├── categories.ex
│ │ │ ├── category.ex
│ │ │ └── category_index.ex
│ │ ├── data.ex
│ │ ├── posts
│ │ │ ├── post.ex
│ │ │ └── posts.ex
│ │ ├── repo.ex
│ │ ├── threads
│ │ │ ├── thread.ex
│ │ │ └── threads.ex
│ │ └── users
│ │ │ ├── user.ex
│ │ │ └── users.ex
│ └── seed.ex
│ ├── mix.exs
│ ├── priv
│ └── repo
│ │ ├── migrations
│ │ ├── 20181114181247_create_categories.exs
│ │ ├── 20181114192933_create_threads.exs
│ │ ├── 20181115164920_create_posts.exs
│ │ ├── 20181116024055_create_users.exs
│ │ ├── 20181116031928_add_user_id_to_posts.exs
│ │ ├── 20191004172429_add_slug_to_threads_and_categories.exs
│ │ └── 20191004172431_add_admin_to_users.exs
│ │ └── seeds.exs
│ └── test
│ ├── categories_test.exs
│ ├── firestorm_data_test.exs
│ ├── posts_test.exs
│ ├── support
│ └── data_case.ex
│ ├── test_helper.exs
│ ├── threads_test.exs
│ └── users_test.exs
├── assets
└── images
│ └── firestorm-logo.png
├── config
└── config.exs
├── mix.exs
└── mix.lock
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["mix.exs", "config/*.exs"],
4 | subdirectories: ["apps/*"]
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 | .elixir_ls
23 |
--------------------------------------------------------------------------------
/.semaphore/semaphore.yml:
--------------------------------------------------------------------------------
1 | version: v1.0
2 | name: First pipeline
3 | agent:
4 | machine:
5 | type: e1-standard-2
6 | os_image: ubuntu1804
7 | blocks:
8 | - name: Unit tests
9 | task:
10 | env_vars:
11 | - name: DATABASE_POSTGRESQL_USERNAME
12 | value: postgres
13 | - name: DATABASE_POSTGRESQL_PASSWORD
14 | value: ""
15 | - name: MIX_ENV
16 | value: test
17 | - name: MIX_ENV
18 | value: test
19 | prologue:
20 | commands:
21 | - sem-service start postgres
22 | - checkout
23 | jobs:
24 | - name: compile
25 | commands:
26 | - sem-version elixir 1.9.1
27 | - elixir --version
28 | - mix local.hex --force
29 | - mix local.rebar --force
30 | - createdb firestorm_data_repo_test -h 0.0.0.0 -U $DATABASE_POSTGRESQL_USERNAME
31 | - mix deps.get
32 | - mix ecto.create
33 | - mix ecto.migrate
34 | - mix compile --warnings-as-errors
35 | - mix test
36 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | elixir 1.9.1
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 DailyDrip
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
4 | associated documentation files (the "Software"), to deal in the Software without restriction,
5 | including without limitation the rights to use, copy, modify, merge, publish, distribute,
6 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
7 | furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
15 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ### An open-source forum engine, with an Elixir+Phoenix backend and an Elm frontend.
4 |
5 | #### A community-funded project from [SmoothTerminal](https://www.smoothterminal.com).
6 |
7 | This is the Elixir backend for the [Firestorm Forum
8 | project](https://github.com/dailydrip/firestorm). This is a bit of a rewrite to make it a bit better
9 | factored long term. It's presently at version 2.0-alpha-1. It needs a bit of work to consider it
10 | proper 2.0, but it's presently supporting the comments on
11 | [SmoothTerminal](https://www.smoothterminal.com) in production.
12 |
13 | There were two primary motivations for a rewrite.
14 |
15 | - To properly decouple the data layer from the web interface. This was a feature of early releases
16 | of Firestorm 1.0, but in the process of building it for tutorials it was dropped for ease of
17 | explanation. Sadly, this led to it being improperly coupled and this was never fixed. Now the data
18 | layer may be used standalone to bring a forum into an existing application trivially.
19 | - To switch to GraphQL for the API. When building the first version, GraphQL had a problematic
20 | license. Facebook has since rectified that situation, so we rebuilt it focusing on providing a
21 | GraphQL API.
22 |
23 | In the process, there were plenty of features that were lost. Some of these features were
24 | unfortunately coupled to the provided frontend. We will re-implement those features, but didn't want
25 | to wait to get the new release out since it's been far too long since we provided updates.
26 |
27 | ## Patrons
28 |
29 | This project was funded by [a
30 | Kickstarter](https://www.kickstarter.com/projects/1003377429/firestorm-an-open-source-forum-in-phoenix-from-eli).
31 |
32 | All of the patrons that made it possible are listed in [the PATRONS
33 | file](https://github.com/dailydrip/firestorm/blob/master/PATRONS.md) in the main project.
34 |
35 | ## License
36 |
37 | Firestorm is [MIT Licensed](./LICENSE).
38 |
--------------------------------------------------------------------------------
/apps/firestorm/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:phoenix],
3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/apps/firestorm/.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 | firestorm-*.tar
24 |
25 | # Files matching config/*.secret.exs pattern contain sensitive
26 | # data and you should not commit them into version control.
27 | #
28 | # Alternatively, you may comment the line below and commit the
29 | # secrets files as long as you replace their contents by environment
30 | # variables.
31 | /config/*.secret.exs
32 |
--------------------------------------------------------------------------------
/apps/firestorm/README.md:
--------------------------------------------------------------------------------
1 | # Firestorm
2 |
3 | To start your Phoenix server:
4 |
5 | * Install dependencies with `mix deps.get`
6 | * Start Phoenix endpoint with `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: http://www.phoenixframework.org/
15 | * Guides: https://hexdocs.pm/phoenix/overview.html
16 | * Docs: https://hexdocs.pm/phoenix
17 | * Mailing list: http://groups.google.com/group/phoenix-talk
18 | * Source: https://github.com/phoenixframework/phoenix
19 |
--------------------------------------------------------------------------------
/apps/firestorm/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 :firestorm,
11 | ecto_repos: [FirestormData.Repo]
12 |
13 | # Configures the endpoint
14 | config :firestorm, FirestormWeb.Endpoint,
15 | url: [host: "localhost"],
16 | secret_key_base: "HkZ1ZUIQPixKcotFBEaFCCmQeBlKCM937FExYO78wJ/toM+A0fcfRi+SzwlZzzmm",
17 | render_errors: [view: FirestormWeb.ErrorView, accepts: ~w(html json)],
18 | pubsub: [name: Firestorm.PubSub, adapter: Phoenix.PubSub.PG2]
19 |
20 | # Configures Elixir's Logger
21 | config :logger, :console,
22 | format: "$time $metadata[$level] $message\n",
23 | metadata: [:request_id]
24 |
25 | # Use Jason for JSON parsing in Phoenix
26 | config :phoenix, :json_library, Jason
27 |
28 | # Import environment specific config. This must remain at the bottom
29 | # of this file so it overrides the configuration defined above.
30 | import_config "#{Mix.env()}.exs"
31 |
--------------------------------------------------------------------------------
/apps/firestorm/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with webpack to recompile .js and .css sources.
9 | config :firestorm, FirestormWeb.Endpoint,
10 | http: [port: 4000],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: []
15 |
16 | # ## SSL Support
17 | #
18 | # In order to use HTTPS in development, a self-signed
19 | # certificate can be generated by running the following
20 | # Mix task:
21 | #
22 | # mix phx.gen.cert
23 | #
24 | # Note that this task requires Erlang/OTP 20 or later.
25 | # Run `mix help phx.gen.cert` for more information.
26 | #
27 | # The `http:` config above can be replaced with:
28 | #
29 | # https: [
30 | # port: 4001,
31 | # cipher_suite: :strong,
32 | # keyfile: "priv/cert/selfsigned_key.pem",
33 | # certfile: "priv/cert/selfsigned.pem"
34 | # ],
35 | #
36 | # If desired, both `http:` and `https:` keys can be
37 | # configured to run both http and https servers on
38 | # different ports.
39 |
40 | # Watch static and templates for browser reloading.
41 | config :firestorm, FirestormWeb.Endpoint,
42 | live_reload: [
43 | patterns: [
44 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
45 | ~r{priv/gettext/.*(po)$},
46 | ~r{lib/firestorm_web/views/.*(ex)$},
47 | ~r{lib/firestorm_web/templates/.*(eex)$}
48 | ]
49 | ]
50 |
51 | # Do not include metadata nor timestamps in development logs
52 | config :logger, :console, format: "[$level] $message\n"
53 |
54 | # Set a higher stacktrace during development. Avoid configuring such
55 | # in production as building large stacktraces may be expensive.
56 | config :phoenix, :stacktrace_depth, 20
57 |
58 | # Initialize plugs at runtime for faster development compilation
59 | config :phoenix, :plug_init_mode, :runtime
60 |
--------------------------------------------------------------------------------
/apps/firestorm/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 |
13 | config :firestorm, FirestormWeb.Endpoint,
14 | http: [port: {:system, "PORT"}],
15 | url: [host: System.get_env("APP_NAME") <> ".gigalixirapp.com", port: 80],
16 | secret_key_base: Map.fetch!(System.get_env(), "SECRET_KEY_BASE"),
17 | check_origin: false,
18 | server: true
19 |
20 | # Do not print debug messages in production
21 | config :logger, level: :info
22 |
23 | config :firestorm_data, FirestormData.Repo,
24 | adapter: Ecto.Adapters.Postgres,
25 | url: System.get_env("DATABASE_URL"),
26 | ssl: true,
27 | pool_size: 2
28 |
29 | # ## SSL Support
30 | #
31 | # To get SSL working, you will need to add the `https` key
32 | # to the previous section and set your `:url` port to 443:
33 | #
34 | # config :firestorm, FirestormWeb.Endpoint,
35 | # ...
36 | # url: [host: "example.com", port: 443],
37 | # https: [
38 | # :inet6,
39 | # port: 443,
40 | # cipher_suite: :strong,
41 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
42 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
43 | # ]
44 | #
45 | # The `cipher_suite` is set to `:strong` to support only the
46 | # latest and more secure SSL ciphers. This means old browsers
47 | # and clients may not be supported. You can set it to
48 | # `:compatible` for wider support.
49 | #
50 | # `:keyfile` and `:certfile` expect an absolute path to the key
51 | # and cert in disk or a relative path inside priv, for example
52 | # "priv/ssl/server.key". For all supported SSL configuration
53 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
54 | #
55 | # We also recommend setting `force_ssl` in your endpoint, ensuring
56 | # no data is ever sent via http, always redirecting to https:
57 | #
58 | # config :firestorm, FirestormWeb.Endpoint,
59 | # force_ssl: [hsts: true]
60 | #
61 | # Check `Plug.SSL` for all available options in `force_ssl`.
62 |
63 | # ## Using releases (distillery)
64 | #
65 | # If you are doing OTP releases, you need to instruct Phoenix
66 | # to start the server for all endpoints:
67 | #
68 | # config :phoenix, :serve_endpoints, true
69 | #
70 | # Alternatively, you can configure exactly which server to
71 | # start per endpoint:
72 | #
73 | # config :firestorm, FirestormWeb.Endpoint, server: true
74 | #
75 | # Note you can't rely on `System.get_env/1` when using releases.
76 | # See the releases documentation accordingly.
77 |
78 | # Finally import the config/prod.secret.exs which should be versioned
79 | # separately.
80 | # import_config "prod.secret.exs"
81 |
--------------------------------------------------------------------------------
/apps/firestorm/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :firestorm, FirestormWeb.Endpoint,
6 | http: [port: 4002],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm.ex:
--------------------------------------------------------------------------------
1 | defmodule Firestorm do
2 | @moduledoc """
3 | Firestorm 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 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm/application.ex:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.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 | import Supervisor.Spec
10 |
11 | # List all child processes to be supervised
12 | children = [
13 | # Start the endpoint when the application starts
14 | FirestormWeb.Endpoint,
15 | supervisor(Absinthe.Subscription, [FirestormWeb.Endpoint])
16 | # Starts a worker by calling: Firestorm.Worker.start_link(arg)
17 | # {Firestorm.Worker, arg},
18 | ]
19 |
20 | # See https://hexdocs.pm/elixir/Supervisor.html
21 | # for other strategies and supported options
22 | opts = [strategy: :one_for_one, name: Firestorm.Supervisor]
23 | Supervisor.start_link(children, opts)
24 | end
25 |
26 | # Tell Phoenix to update the endpoint configuration
27 | # whenever the application is updated.
28 | def config_change(changed, _new, removed) do
29 | FirestormWeb.Endpoint.config_change(changed, removed)
30 | :ok
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb 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 FirestormWeb, :controller
9 | use FirestormWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: FirestormWeb
23 |
24 | import Plug.Conn
25 | import FirestormWeb.Gettext
26 | alias FirestormWeb.Router.Helpers, as: Routes
27 | end
28 | end
29 |
30 | def view do
31 | quote do
32 | use Phoenix.View,
33 | root: "lib/firestorm_web/templates",
34 | namespace: FirestormWeb
35 |
36 | # Import convenience functions from controllers
37 | import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
38 |
39 | # Use all HTML functionality (forms, tags, etc)
40 | use Phoenix.HTML
41 |
42 | import FirestormWeb.ErrorHelpers
43 | import FirestormWeb.Gettext
44 | alias FirestormWeb.Router.Helpers, as: Routes
45 | end
46 | end
47 |
48 | def router do
49 | quote do
50 | use Phoenix.Router
51 | import Plug.Conn
52 | import Phoenix.Controller
53 | end
54 | end
55 |
56 | def channel do
57 | quote do
58 | use Phoenix.Channel
59 | import FirestormWeb.Gettext
60 | end
61 | end
62 |
63 | @doc """
64 | When used, dispatch to the appropriate controller/view/etc.
65 | """
66 | defmacro __using__(which) when is_atom(which) do
67 | apply(__MODULE__, which, [])
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.UserSocket do
2 | @moduledoc false
3 | @one_day 86_400
4 |
5 | use Phoenix.Socket
6 | use Absinthe.Phoenix.Socket, schema: FirestormWeb.Schema
7 |
8 | alias Absinthe.Phoenix.Socket
9 |
10 | def connect(%{"token" => token}, socket) do
11 | with {:ok, user_id} <-
12 | Phoenix.Token.verify(FirestormWeb.Endpoint, "user auth", token, max_age: @one_day),
13 | {:ok, user} <- FirestormData.Users.get_user(user_id) do
14 | socket = Socket.put_options(socket, context: %{current_user: user})
15 | {:ok, socket}
16 | else
17 | _ -> :error
18 | end
19 | end
20 |
21 | def connect(_, socket) do
22 | {:ok, socket}
23 | end
24 |
25 | def id(%{assigns: %{absinthe: %{opts: [context: %{current_user: user}]}}}) do
26 | "user_socket:#{user.id}"
27 | end
28 |
29 | def id(_), do: nil
30 | end
31 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/context.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Context do
2 | import Plug.Conn
3 | @behaviour Plug
4 | @one_day 86_400
5 |
6 | def init(opts), do: opts
7 |
8 | def call(conn, _) do
9 | context = build_context(conn)
10 | Absinthe.Plug.put_options(conn, context: context)
11 | end
12 |
13 | @doc """
14 | Return the current user context based on the authorization header
15 | """
16 | def build_context(conn) do
17 | with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
18 | {:ok, current_user} <- authorize(token) do
19 | %{current_user: current_user}
20 | else
21 | _ -> %{}
22 | end
23 | end
24 |
25 | defp authorize(token) do
26 | with {:ok, user_id} <-
27 | Phoenix.Token.verify(FirestormWeb.Endpoint, "user auth", token, max_age: @one_day) do
28 | FirestormData.Users.get_user(user_id)
29 | else
30 | _ -> {:error, "invalid authorization token"}
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/controllers/admin/category_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.CategoryController do
2 | use FirestormWeb, :controller
3 |
4 | alias Firestorm.FirestormAdmin
5 | alias FirestormData.Categories.Category
6 |
7 | plug(:put_layout, {FirestormWeb.LayoutView, "torch.html"})
8 |
9 | def index(conn, params) do
10 | case FirestormAdmin.paginate_categories(params) do
11 | {:ok, assigns} ->
12 | render(conn, "index.html", assigns)
13 |
14 | error ->
15 | conn
16 | |> put_flash(:error, "There was an error rendering Categories. #{inspect(error)}")
17 | |> redirect(to: Routes.category_path(conn, :index))
18 | end
19 | end
20 |
21 | def new(conn, _params) do
22 | changeset = FirestormAdmin.new_category_changeset(%Category{})
23 | render(conn, "new.html", changeset: changeset)
24 | end
25 |
26 | def create(conn, %{"category" => category_params}) do
27 | case FirestormAdmin.create_category(category_params) do
28 | {:ok, category} ->
29 | conn
30 | |> put_flash(:info, "Category created successfully.")
31 | |> redirect(to: Routes.category_path(conn, :show, category))
32 |
33 | {:error, %Ecto.Changeset{} = changeset} ->
34 | render(conn, "new.html", changeset: changeset)
35 | end
36 | end
37 |
38 | def show(conn, %{"id" => id}) do
39 | category = FirestormAdmin.get_category!(id)
40 | render(conn, "show.html", category: category)
41 | end
42 |
43 | def edit(conn, %{"id" => id}) do
44 | category = FirestormAdmin.get_category!(id)
45 | changeset = FirestormAdmin.change_category(category)
46 | render(conn, "edit.html", category: category, changeset: changeset)
47 | end
48 |
49 | def update(conn, %{"id" => id, "category" => category_params}) do
50 | category = FirestormAdmin.get_category!(id)
51 |
52 | case FirestormAdmin.update_category(category, category_params) do
53 | {:ok, category} ->
54 | conn
55 | |> put_flash(:info, "Category updated successfully.")
56 | |> redirect(to: Routes.category_path(conn, :show, category))
57 |
58 | {:error, %Ecto.Changeset{} = changeset} ->
59 | render(conn, "edit.html", category: category, changeset: changeset)
60 | end
61 | end
62 |
63 | def delete(conn, %{"id" => id}) do
64 | category = FirestormAdmin.get_category!(id)
65 | {:ok, _category} = FirestormAdmin.delete_category(category)
66 |
67 | conn
68 | |> put_flash(:info, "Category deleted successfully.")
69 | |> redirect(to: Routes.category_path(conn, :index))
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/controllers/admin/post_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.PostController do
2 | use FirestormWeb, :controller
3 |
4 | alias Firestorm.FirestormAdmin
5 | alias FirestormData.Posts.Post
6 |
7 | plug(:put_layout, {FirestormWeb.LayoutView, "torch.html"})
8 |
9 | def index(conn, params) do
10 | case FirestormAdmin.paginate_posts(params) do
11 | {:ok, assigns} ->
12 | render(conn, "index.html", assigns)
13 |
14 | error ->
15 | conn
16 | |> put_flash(:error, "There was an error rendering Posts. #{inspect(error)}")
17 | |> redirect(to: Routes.post_path(conn, :index))
18 | end
19 | end
20 |
21 | def new(conn, _params) do
22 | changeset = FirestormAdmin.change_post(%Post{})
23 | render(conn, "new.html", changeset: changeset)
24 | end
25 |
26 | def create(conn, %{"post" => post_params}) do
27 | case FirestormAdmin.create_post(post_params) do
28 | {:ok, post} ->
29 | conn
30 | |> put_flash(:info, "Post created successfully.")
31 | |> redirect(to: Routes.post_path(conn, :show, post))
32 |
33 | {:error, %Ecto.Changeset{} = changeset} ->
34 | render(conn, "new.html", changeset: changeset)
35 | end
36 | end
37 |
38 | def show(conn, %{"id" => id}) do
39 | post = FirestormAdmin.get_post!(id)
40 | render(conn, "show.html", post: post)
41 | end
42 |
43 | def edit(conn, %{"id" => id}) do
44 | post = FirestormAdmin.get_post!(id)
45 | changeset = FirestormAdmin.change_post(post)
46 | render(conn, "edit.html", post: post, changeset: changeset)
47 | end
48 |
49 | def update(conn, %{"id" => id, "post" => post_params}) do
50 | post = FirestormAdmin.get_post!(id)
51 |
52 | case FirestormAdmin.update_post(post, post_params) do
53 | {:ok, post} ->
54 | conn
55 | |> put_flash(:info, "Post updated successfully.")
56 | |> redirect(to: Routes.post_path(conn, :show, post))
57 |
58 | {:error, %Ecto.Changeset{} = changeset} ->
59 | render(conn, "edit.html", post: post, changeset: changeset)
60 | end
61 | end
62 |
63 | def delete(conn, %{"id" => id}) do
64 | post = FirestormAdmin.get_post!(id)
65 | {:ok, _post} = FirestormAdmin.delete_post(post)
66 |
67 | conn
68 | |> put_flash(:info, "Post deleted successfully.")
69 | |> redirect(to: Routes.post_path(conn, :index))
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/controllers/admin/thread_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.ThreadController do
2 | use FirestormWeb, :controller
3 |
4 | alias Firestorm.FirestormAdmin
5 | alias FirestormData.Threads.Thread
6 |
7 | plug(:put_layout, {FirestormWeb.LayoutView, "torch.html"})
8 |
9 | def index(conn, params) do
10 | case FirestormAdmin.paginate_threads(params) do
11 | {:ok, assigns} ->
12 | render(conn, "index.html", assigns)
13 |
14 | error ->
15 | conn
16 | |> put_flash(:error, "There was an error rendering Threads. #{inspect(error)}")
17 | |> redirect(to: Routes.thread_path(conn, :index))
18 | end
19 | end
20 |
21 | def new(conn, _params) do
22 | changeset = FirestormAdmin.change_thread(%Thread{})
23 | render(conn, "new.html", changeset: changeset)
24 | end
25 |
26 | def create(conn, %{"thread" => thread_params}) do
27 | case FirestormAdmin.create_thread(thread_params) do
28 | {:ok, thread} ->
29 | conn
30 | |> put_flash(:info, "Thread created successfully.")
31 | |> redirect(to: Routes.thread_path(conn, :show, thread))
32 |
33 | {:error, %Ecto.Changeset{} = changeset} ->
34 | render(conn, "new.html", changeset: changeset)
35 | end
36 | end
37 |
38 | def show(conn, %{"id" => id}) do
39 | thread = FirestormAdmin.get_thread!(id)
40 | render(conn, "show.html", thread: thread)
41 | end
42 |
43 | def edit(conn, %{"id" => id}) do
44 | thread = FirestormAdmin.get_thread!(id)
45 | changeset = FirestormAdmin.change_thread(thread)
46 | render(conn, "edit.html", thread: thread, changeset: changeset)
47 | end
48 |
49 | def update(conn, %{"id" => id, "thread" => thread_params}) do
50 | thread = FirestormAdmin.get_thread!(id)
51 |
52 | case FirestormAdmin.update_thread(thread, thread_params) do
53 | {:ok, thread} ->
54 | conn
55 | |> put_flash(:info, "Thread updated successfully.")
56 | |> redirect(to: Routes.thread_path(conn, :show, thread))
57 |
58 | {:error, %Ecto.Changeset{} = changeset} ->
59 | render(conn, "edit.html", thread: thread, changeset: changeset)
60 | end
61 | end
62 |
63 | def delete(conn, %{"id" => id}) do
64 | thread = FirestormAdmin.get_thread!(id)
65 | {:ok, _thread} = FirestormAdmin.delete_thread(thread)
66 |
67 | conn
68 | |> put_flash(:info, "Thread deleted successfully.")
69 | |> redirect(to: Routes.thread_path(conn, :index))
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/controllers/admin/user_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.UserController do
2 | use FirestormWeb, :controller
3 |
4 | alias Firestorm.FirestormAdmin
5 | alias FirestormData.Users.User
6 |
7 | plug(:put_layout, {FirestormWeb.LayoutView, "torch.html"})
8 |
9 | def index(conn, params) do
10 | case FirestormAdmin.paginate_users(params) do
11 | {:ok, assigns} ->
12 | render(conn, "index.html", assigns)
13 |
14 | error ->
15 | conn
16 | |> put_flash(:error, "There was an error rendering Users. #{inspect(error)}")
17 | |> redirect(to: Routes.user_path(conn, :index))
18 | end
19 | end
20 |
21 | def new(conn, _params) do
22 | changeset = FirestormAdmin.change_user(%User{})
23 | render(conn, "new.html", changeset: changeset)
24 | end
25 |
26 | def create(conn, %{"user" => user_params}) do
27 | case FirestormAdmin.create_user(user_params) do
28 | {:ok, user} ->
29 | conn
30 | |> put_flash(:info, "User created successfully.")
31 | |> redirect(to: Routes.user_path(conn, :show, user))
32 |
33 | {:error, %Ecto.Changeset{} = changeset} ->
34 | render(conn, "new.html", changeset: changeset)
35 | end
36 | end
37 |
38 | def show(conn, %{"id" => id}) do
39 | user = FirestormAdmin.get_user!(id)
40 | render(conn, "show.html", user: user)
41 | end
42 |
43 | def edit(conn, %{"id" => id}) do
44 | user = FirestormAdmin.get_user!(id)
45 | changeset = FirestormAdmin.change_user(user)
46 | render(conn, "edit.html", user: user, changeset: changeset)
47 | end
48 |
49 | def update(conn, %{"id" => id, "user" => user_params}) do
50 | user = FirestormAdmin.get_user!(id)
51 |
52 | case FirestormAdmin.update_user(user, user_params) do
53 | {:ok, user} ->
54 | conn
55 | |> put_flash(:info, "User updated successfully.")
56 | |> redirect(to: Routes.user_path(conn, :show, user))
57 |
58 | {:error, %Ecto.Changeset{} = changeset} ->
59 | render(conn, "edit.html", user: user, changeset: changeset)
60 | end
61 | end
62 |
63 | def delete(conn, %{"id" => id}) do
64 | user = FirestormAdmin.get_user!(id)
65 | {:ok, _user} = FirestormAdmin.delete_user(user)
66 |
67 | conn
68 | |> put_flash(:info, "User deleted successfully.")
69 | |> redirect(to: Routes.user_path(conn, :index))
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/controllers/auth_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.AuthController do
2 | @one_day 86_400
3 | use FirestormWeb, :controller
4 |
5 | use Absinthe.Phoenix.Controller, schema: FirestormWeb.Schema
6 |
7 | def login(conn, _params) do
8 | conn
9 | |> render("login.html")
10 | end
11 |
12 | def logout(conn, _params) do
13 | conn
14 | |> fetch_session
15 | |> delete_session(:current_user_id)
16 | |> redirect(to: "/login")
17 | end
18 |
19 | def forgot_password_verification(conn, %{"token" => token}) do
20 | render(conn, "forgot_password_verification.html", token: token)
21 | end
22 |
23 | @graphql """
24 | mutation ($email: String, $password: String!) {
25 | authenticate(email: $email, password: $password)
26 | }
27 | """
28 | def authenticate(conn, params) do
29 | if params[:errors] do
30 | conn
31 | |> put_flash(
32 | :error,
33 | List.first(params[:errors])
34 | |> Map.get(:message)
35 | )
36 | |> redirect(to: "/login")
37 | else
38 | case find_user_by_token(params[:data]["authenticate"]) do
39 | nil ->
40 | conn
41 | |> put_flash(:error, "No luck")
42 | |> redirect(to: "/login")
43 |
44 | user ->
45 | conn
46 | |> put_session(:current_user_id, user.id)
47 | |> put_flash(:info, "Logged in successfully")
48 | |> redirect(to: "/admin/users")
49 | end
50 | end
51 | end
52 |
53 | def find_user_by_token(token) do
54 | with {:ok, user_id} <-
55 | Phoenix.Token.verify(FirestormWeb.Endpoint, "user auth", token, max_age: @one_day),
56 | {:ok, user} <- FirestormData.Users.get_user(user_id) do
57 | user
58 | else
59 | _ -> nil
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.PageController do
2 | use FirestormWeb, :controller
3 |
4 | def index(conn, _params) do
5 | render(conn, "index.html")
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :firestorm
3 | use Absinthe.Phoenix.Endpoint
4 |
5 | socket("/socket", FirestormWeb.UserSocket,
6 | websocket: true,
7 | longpoll: false
8 | )
9 |
10 | plug(Corsica,
11 | origins: "*",
12 | allow_headers: [
13 | "authorization",
14 | "content-type"
15 | ]
16 | )
17 |
18 | # Serve at "/" the static files from "priv/static" directory.
19 | #
20 | # You should set gzip to true if you are running phx.digest
21 | # when deploying your static files in production.
22 | plug(Plug.Static,
23 | at: "/",
24 | from: :firestorm,
25 | gzip: false,
26 | only: ~w(css fonts images js favicon.ico robots.txt)
27 | )
28 |
29 | plug(
30 | Plug.Static,
31 | at: "/torch",
32 | from: {:torch, "priv/static"},
33 | gzip: true,
34 | cache_control_for_etags: "public, max-age=86400",
35 | headers: [{"access-control-allow-origin", "*"}]
36 | )
37 |
38 | # Code reloading can be explicitly enabled under the
39 | # :code_reloader configuration of your endpoint.
40 | if code_reloading? do
41 | socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket)
42 | plug(Phoenix.LiveReloader)
43 | plug(Phoenix.CodeReloader)
44 | end
45 |
46 | plug(Plug.RequestId)
47 | plug(Plug.Logger)
48 |
49 | plug(Plug.Parsers,
50 | parsers: [:urlencoded, :multipart, :json],
51 | pass: ["*/*"],
52 | json_decoder: Phoenix.json_library()
53 | )
54 |
55 | plug(Plug.MethodOverride)
56 | plug(Plug.Head)
57 |
58 | # The session will be stored in the cookie and signed,
59 | # this means its contents can be read but not tampered with.
60 | # Set :encryption_salt if you would also like to encrypt it.
61 | plug(Plug.Session,
62 | store: :cookie,
63 | key: "_firestorm_key",
64 | signing_salt: "aOGGPR01"
65 | )
66 |
67 | plug(FirestormWeb.Router)
68 | end
69 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.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 FirestormWeb.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: :firestorm
24 | end
25 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/loaders/posts.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Loaders.Posts do
2 | def data do
3 | Dataloader.Ecto.new(FirestormData.Repo)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/loaders/threads.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Loaders.Threads do
2 | def data do
3 | Dataloader.Ecto.new(FirestormData.Repo)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/plug/ensure_admin.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Plug.EnsureAdmin do
2 | @moduledoc """
3 | This plug makes sure that the authenticated user is a super user,
4 | otherwise it halts the connection.
5 | """
6 | import Plug.Conn
7 | import Phoenix.Controller
8 | alias FirestormData.Repo
9 | alias FirestormData.Users.User
10 | alias FirestormWeb.Router.Helpers, as: Routes
11 |
12 | def init(opts), do: Enum.into(opts, %{})
13 |
14 | def call(conn, opts \\ []) do
15 | check_admin(conn, opts)
16 | end
17 |
18 | defp check_admin(conn, _opts) do
19 | case get_session(conn, :current_user_id) do
20 | nil ->
21 | halt_plug(conn)
22 |
23 | current_user_id ->
24 | case Repo.get(User, current_user_id) do
25 | nil ->
26 | halt_plug(conn)
27 |
28 | current_user ->
29 | case current_user.admin do
30 | false -> halt_plug(conn)
31 | true -> assign(conn, :current_user, current_user)
32 | end
33 | end
34 | end
35 | end
36 |
37 | defp halt_plug(conn) do
38 | conn
39 | |> redirect(to: Routes.auth_path(conn, :login))
40 | |> halt()
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/resolvers/categories.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Resolvers.Categories do
2 | alias FirestormData.Users.User
3 |
4 | def list_categories(_, args, _) do
5 | {:ok, FirestormData.Categories.list_categories(args)}
6 | end
7 |
8 | def find_category(_, %{id: id}, _) do
9 | FirestormData.Categories.find_category(%{id: id})
10 | end
11 |
12 | def create_category(_, args, %{context: %{current_user: %User{}}}) do
13 | FirestormData.Categories.create_category(args)
14 | end
15 |
16 | def create_category(_, _, _) do
17 | {:error, "unauthorized"}
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/resolvers/posts.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Resolvers.Posts do
2 | alias FirestormData.{
3 | Posts,
4 | Threads.Thread,
5 | Users.User
6 | }
7 |
8 | def list_posts(%Thread{} = thread, _, _) do
9 | {:ok, Posts.list_posts(thread)}
10 | end
11 |
12 | def create_post(_, %{thread_id: thread_id, body: body}, %{
13 | context: %{current_user: %User{} = current_user}
14 | }) do
15 | with {:ok, thread} <- FirestormData.Threads.get_thread(thread_id),
16 | do: FirestormData.Posts.create_post(thread, current_user, %{body: body})
17 | end
18 |
19 | def create_post(_, _, _), do: {:error, :unauthorized}
20 | end
21 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/resolvers/threads.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Resolvers.Threads do
2 | alias FirestormData.{
3 | Categories.Category,
4 | Threads,
5 | Users.User
6 | }
7 |
8 | def list_threads(%Category{} = category, _, _) do
9 | {:ok, Threads.list_threads(category)}
10 | end
11 |
12 | def find_thread(_, %{id: id}, _) do
13 | Threads.get_thread(id)
14 | end
15 |
16 | def create_thread(_, %{category_id: category_id, title: title, body: body}, %{
17 | context: %{current_user: %User{} = current_user}
18 | }) do
19 | IO.puts("create_thread")
20 |
21 | with {:ok, category} <- FirestormData.Categories.find_category(%{id: category_id}),
22 | do:
23 | FirestormData.Threads.create_thread(category, current_user, %{title: title, body: body})
24 | end
25 |
26 | def create_thread(_, _, _) do
27 | IO.puts("create_thread unauth")
28 | {:error, "unauthorized"}
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/resolvers/users.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Resolvers.Users do
2 | def gravatar(user, _, _) do
3 | {:ok, Gravity.image(user.email)}
4 | end
5 |
6 | def create_user(_, args, _) do
7 | FirestormData.Users.create_user(args)
8 | end
9 |
10 | def authenticate(_, %{email: email, password: password}, _) do
11 | with {:ok, user} <- FirestormData.Users.find_user(%{email: email}) do
12 | # FIXME:This is a bit of data leakage - why does the graphql interface know that the data layer uses Comeonin.Bcrypt?
13 | case Comeonin.Bcrypt.checkpw(password, user.password_hash) do
14 | true ->
15 | # Everything checks out, success
16 | {:ok, sign_auth_token(user.id)}
17 |
18 | _ ->
19 | # User existed, we checked the password, but no dice
20 | {:error, "No user found with that username or password"}
21 | end
22 | end
23 | end
24 |
25 | defp sign_auth_token(id), do: Phoenix.Token.sign(FirestormWeb.Endpoint, "user auth", id)
26 | end
27 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Router do
2 | use FirestormWeb, :router
3 |
4 | pipeline :browser do
5 | plug(:accepts, ["html"])
6 | plug(:fetch_session)
7 | plug(:fetch_flash)
8 | plug(:protect_from_forgery)
9 | plug(:put_secure_browser_headers)
10 | end
11 |
12 | pipeline :api do
13 | plug(:accepts, ["json"])
14 | end
15 |
16 | pipeline :graphql do
17 | plug(FirestormWeb.Context)
18 | end
19 |
20 | scope "/", FirestormWeb do
21 | pipe_through(:browser)
22 |
23 | get("/", PageController, :index)
24 | get("/login", AuthController, :login)
25 | post("/authenticate", AuthController, :authenticate)
26 | get("/logout", AuthController, :logout)
27 | get("/forgot_password/:token", AuthController, :forgot_password_verification)
28 | end
29 |
30 | scope "/graphql" do
31 | pipe_through(:graphql)
32 |
33 | forward(
34 | "/",
35 | Absinthe.Plug.GraphiQL,
36 | schema: FirestormWeb.Schema,
37 | socket: FirestormWeb.UserSocket,
38 | json_codec: Jason
39 | )
40 | end
41 |
42 | pipeline :admin do
43 | plug(:accepts, ["html"])
44 | plug(:fetch_session)
45 | plug(:fetch_flash)
46 | plug(:protect_from_forgery)
47 | plug(:put_secure_browser_headers)
48 | plug(FirestormWeb.Plug.EnsureAdmin)
49 | end
50 |
51 | scope "/admin", FirestormWeb.Admin do
52 | pipe_through(:admin)
53 | resources("/categories", CategoryController)
54 | resources("/threads", ThreadController)
55 | resources("/posts", PostController)
56 | resources("/users", UserController)
57 | end
58 |
59 | # Other scopes may use custom stacks.
60 | # scope "/api", FirestormWeb do
61 | # pipe_through :api
62 | # end
63 | end
64 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/schema.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Schema do
2 | use Absinthe.Schema
3 | import_types(FirestormWeb.Schema.CategoriesTypes)
4 | import_types(FirestormWeb.Schema.ThreadsTypes)
5 | import_types(FirestormWeb.Schema.PostsTypes)
6 | import_types(FirestormWeb.Schema.UsersTypes)
7 | import_types(FirestormWeb.Schema.PaginationTypes)
8 |
9 | alias FirestormWeb.Resolvers
10 |
11 | def plugins do
12 | [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
13 | end
14 |
15 | def context(ctx) do
16 | loader =
17 | Dataloader.new()
18 | |> Dataloader.add_source(FirestormData.Posts.Post, FirestormWeb.Loaders.Posts.data())
19 | |> Dataloader.add_source(FirestormData.Threads.Thread, FirestormWeb.Loaders.Threads.data())
20 |
21 | Map.put(ctx, :loader, loader)
22 | end
23 |
24 | @desc """
25 | The `DateTime` scalar type represents a date and time in the UTC
26 | timezone. The DateTime appears in a JSON response as an ISO8601 formatted
27 | string, including UTC timezone ("Z"). The parsed date and time string will
28 | be converted to UTC and any UTC offset other than 0 will be rejected.
29 | """
30 | scalar :datetime, name: "DateTime" do
31 | serialize(&DateTime.to_iso8601/1)
32 | parse(&parse_datetime/1)
33 | end
34 |
35 | @spec parse_datetime(Absinthe.Blueprint.Input.String.t()) :: {:ok, DateTime.t()} | :error
36 | @spec parse_datetime(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
37 | defp parse_datetime(%Absinthe.Blueprint.Input.String{value: value}) do
38 | case DateTime.from_iso8601(value) do
39 | {:ok, datetime, 0} -> {:ok, datetime}
40 | {:ok, _datetime, _offset} -> :error
41 | _error -> :error
42 | end
43 | end
44 |
45 | defp parse_datetime(%Absinthe.Blueprint.Input.Null{}) do
46 | {:ok, nil}
47 | end
48 |
49 | defp parse_datetime(_) do
50 | :error
51 | end
52 |
53 | query do
54 | @desc "Get all categories"
55 | field(:categories, non_null(:paginated_categories)) do
56 | arg(:pagination, :pagination)
57 | resolve(&Resolvers.Categories.list_categories/3)
58 | end
59 |
60 | @desc "Get a specific category"
61 | field(:category, non_null(:category)) do
62 | arg(:id, non_null(:id))
63 | resolve(&Resolvers.Categories.find_category/3)
64 | end
65 |
66 | @desc "Get a specific thread"
67 | field(:thread, non_null(:thread)) do
68 | arg(:id, non_null(:id))
69 | resolve(&Resolvers.Threads.find_thread/3)
70 | end
71 | end
72 |
73 | mutation do
74 | @desc "Create a user"
75 | field(:create_user, non_null(:user)) do
76 | arg(:username, non_null(:string))
77 | arg(:name, non_null(:string))
78 | arg(:email, non_null(:string))
79 | arg(:password, non_null(:string))
80 | resolve(&Resolvers.Users.create_user/3)
81 | end
82 |
83 | @desc "Authenticate and receive an authorization token"
84 | field(:authenticate, non_null(:string)) do
85 | arg(:email, non_null(:string))
86 | arg(:password, non_null(:string))
87 | resolve(&Resolvers.Users.authenticate/3)
88 | end
89 |
90 | @desc "Create a category"
91 | field(:create_category, non_null(:category)) do
92 | arg(:title, non_null(:string))
93 | resolve(&Resolvers.Categories.create_category/3)
94 | end
95 |
96 | @desc "Create a thread"
97 | field(:create_thread, non_null(:thread)) do
98 | arg(:category_id, non_null(:id))
99 | arg(:title, non_null(:string))
100 | arg(:body, non_null(:string))
101 | resolve(&Resolvers.Threads.create_thread/3)
102 | end
103 |
104 | @desc "Create a post"
105 | field(:create_post, non_null(:post)) do
106 | arg(:thread_id, non_null(:id))
107 | arg(:body, non_null(:string))
108 | resolve(&Resolvers.Posts.create_post/3)
109 | end
110 | end
111 |
112 | subscription do
113 | @desc "Get notified when a category is added"
114 | field(:category_added, non_null(:category)) do
115 | config(fn _, _ ->
116 | {:ok, topic: :global}
117 | end)
118 |
119 | trigger(:create_category, topic: fn _ -> :global end)
120 | end
121 |
122 | @desc "Get notified when a thread is added"
123 | field(:thread_added, non_null(:thread)) do
124 | arg(:category_id, non_null(:id))
125 |
126 | config(fn args, _ ->
127 | {:ok, topic: args.category_id}
128 | end)
129 |
130 | trigger(:create_thread, topic: fn thread -> thread.category_id end)
131 | end
132 |
133 | @desc "Get notified when a post is added"
134 | field(:post_added, non_null(:post)) do
135 | arg(:thread_id, non_null(:id))
136 |
137 | config(fn args, _ ->
138 | {:ok, topic: args.thread_id}
139 | end)
140 |
141 | trigger(:create_post, topic: fn post -> post.thread_id end)
142 | end
143 | end
144 | end
145 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/schema/categories_types.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Schema.CategoriesTypes do
2 | use Absinthe.Schema.Notation
3 | alias FirestormWeb.Resolvers
4 |
5 | object :paginated_categories do
6 | field(:page, non_null(:integer)) do
7 | resolve(fn pagination, _, _ -> {:ok, pagination.page_number} end)
8 | end
9 |
10 | field(:per_page, non_null(:integer)) do
11 | resolve(fn pagination, _, _ -> {:ok, pagination.page_size} end)
12 | end
13 |
14 | field(:total_pages, non_null(:integer))
15 | field(:total_entries, non_null(:integer))
16 | field(:entries, non_null(list_of(non_null(:category))))
17 | end
18 |
19 | object :category do
20 | field(:id, non_null(:id))
21 | field(:title, non_null(:string))
22 | field(:slug, non_null(:string))
23 |
24 | field :threads, non_null(list_of(non_null(:thread))) do
25 | resolve(&Resolvers.Threads.list_threads/3)
26 | end
27 |
28 | field(:inserted_at, non_null(:datetime))
29 | field(:updated_at, non_null(:datetime))
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/schema/pagination_types.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Schema.PaginationTypes do
2 | use Absinthe.Schema.Notation
3 |
4 | @desc "Pagination options"
5 | input_object :pagination do
6 | field(:per_page, non_null(:integer))
7 | field(:page, non_null(:integer))
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/schema/posts_types.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Schema.PostsTypes do
2 | use Absinthe.Schema.Notation
3 | import Absinthe.Resolution.Helpers
4 |
5 | object :post do
6 | field(:id, non_null(:id))
7 | field(:body, non_null(:string))
8 | field(:user, non_null(:user), resolve: dataloader(FirestormData.Posts.Post))
9 | field(:thread, non_null(:thread), resolve: dataloader(FirestormData.Posts.Post))
10 | field(:inserted_at, non_null(:datetime))
11 | field(:updated_at, non_null(:datetime))
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/schema/threads_types.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Schema.ThreadsTypes do
2 | use Absinthe.Schema.Notation
3 | import Absinthe.Resolution.Helpers
4 | alias FirestormWeb.Resolvers
5 |
6 | object :thread do
7 | field(:id, non_null(:id))
8 | field(:title, non_null(:string))
9 | field(:slug, non_null(:string))
10 |
11 | field(:category, non_null(:category), resolve: dataloader(FirestormData.Threads.Thread))
12 |
13 | field(:posts, non_null(list_of(non_null(:post)))) do
14 | resolve(&Resolvers.Posts.list_posts/3)
15 | end
16 |
17 | field(:inserted_at, non_null(:datetime))
18 | field(:updated_at, non_null(:datetime))
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/schema/users_types.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Schema.UsersTypes do
2 | use Absinthe.Schema.Notation
3 | alias FirestormWeb.Resolvers
4 |
5 | object :user do
6 | field(:id, non_null(:id))
7 | field(:username, non_null(:string))
8 | field(:name, non_null(:string))
9 | field(:avatar_url, non_null(:string), resolve: &Resolvers.Users.gravatar/3)
10 | field(:inserted_at, non_null(:datetime))
11 | field(:updated_at, non_null(:datetime))
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/category/edit.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.category_path(@conn, :update, @category)) %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/category/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, [multipart: true, id: "torch-form"], fn f -> %>
2 |
3 |
4 | Details
5 |
6 | <%= if @changeset.action do %>
7 | Oops, something went wrong! Please check the errors below.
8 | <% end %>
9 |
10 |
11 | <%= label f, :title %>
12 | <%= text_input f, :title %>
13 | <%= error_tag f, :title %>
14 |
15 |
16 |
17 | <%= label f, :slug %>
18 | <%= text_input f, :slug %>
19 | <%= error_tag f, :slug %>
20 |
21 |
22 |
23 | <%= submit "Submit", class: "torch-submit-button" %>
24 |
25 |
26 | <% end %>
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/category/index.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | Find Categories
10 | <%= form_tag @conn.request_path, method: :get, id: "torch-filters-form" do %>
11 |
12 |
13 | Title
14 | <%= filter_select(:category, :title, @conn.params) %>
15 | <%= filter_string_input(:category, :title, @conn.params) %>
16 |
17 |
18 |
19 | Slug
20 | <%= filter_select(:category, :slug, @conn.params) %>
21 | <%= filter_string_input(:category, :slug, @conn.params) %>
22 |
23 |
24 |
25 |
26 |
27 | Inserted at
28 | <%= filter_date_input(:category, :inserted_at, @conn.params) %>
29 |
30 |
31 |
32 | Updated at
33 | <%= filter_date_input(:category, :updated_at, @conn.params) %>
34 |
35 |
36 |
37 | Search
38 | <%= link "Clear Filters", to: Routes.category_path(@conn, :index) %>
39 | <% end %>
40 |
41 |
42 |
43 |
44 | <%= if length(@categories) > 0 do %>
45 |
46 |
47 |
48 |
49 | <%= table_link(@conn, "Title", :title) %>
50 |
51 | <%= table_link(@conn, "Slug", :slug) %>
52 |
53 | <%= table_link(@conn, "Inserted at", :inserted_at) %>
54 |
55 | <%= table_link(@conn, "Updated at", :updated_at) %>
56 |
57 | Actions
58 |
59 |
60 |
61 | <%= for category <- @categories do %>
62 |
63 |
64 | <%= category.title %>
65 |
66 | <%= category.slug %>
67 |
68 | <%= category.inserted_at %>
69 |
70 | <%= category.updated_at %>
71 |
72 |
73 | <%= link "Show", to: Routes.category_path(@conn, :show, category) %>
74 | <%= link "Edit", to: Routes.category_path(@conn, :edit, category) %>
75 | <%= link "Delete", to: Routes.category_path(@conn, :delete, category), method: :delete, data: [confirm: "Are you sure?"] %>
76 |
77 |
78 | <% end %>
79 |
80 |
81 | <%= render Torch.PaginationView, "_pagination.html", assigns %>
82 | <% else %>
83 | No Categories match your search.
84 | <% end %>
85 |
86 |
87 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/category/new.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.category_path(@conn, :create)) %>
13 |
14 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/category/show.html.eex:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/post/edit.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.post_path(@conn, :update, @post)) %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/post/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, [multipart: true, id: "torch-form"], fn f -> %>
2 |
3 |
4 | Details
5 |
6 | <%= if @changeset.action do %>
7 | Oops, something went wrong! Please check the errors below.
8 | <% end %>
9 |
10 |
11 | <%= label f, :body %>
12 | <%= textarea f, :body %>
13 | <%= error_tag f, :body %>
14 |
15 |
16 |
17 | <%= label f, :thread_id %>
18 | <%= textarea f, :thread_id %>
19 | <%= error_tag f, :thread_id %>
20 |
21 |
22 |
23 | <%= label f, :user_id %>
24 | <%= textarea f, :user_id %>
25 | <%= error_tag f, :user_id %>
26 |
27 |
28 |
29 | <%= submit "Submit", class: "torch-submit-button" %>
30 |
31 |
32 | <% end %>
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/post/index.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | Find Posts
10 | <%= form_tag @conn.request_path, method: :get, id: "torch-filters-form" do %>
11 |
12 |
13 | Body
14 | <%= filter_select(:post, :body, @conn.params) %>
15 | <%= filter_string_input(:post, :body, @conn.params) %>
16 |
17 |
18 |
19 |
20 |
21 | Inserted at
22 | <%= filter_date_input(:post, :inserted_at, @conn.params) %>
23 |
24 |
25 |
26 | Updated at
27 | <%= filter_date_input(:post, :updated_at, @conn.params) %>
28 |
29 |
30 |
31 | Search
32 | <%= link "Clear Filters", to: Routes.post_path(@conn, :index) %>
33 | <% end %>
34 |
35 |
36 |
37 |
38 | <%= if length(@posts) > 0 do %>
39 |
40 |
41 |
42 |
43 | <%= table_link(@conn, "Body", :body) %>
44 |
45 | <%= table_link(@conn, "Inserted at", :inserted_at) %>
46 |
47 | <%= table_link(@conn, "Updated at", :updated_at) %>
48 |
49 | Actions
50 |
51 |
52 |
53 | <%= for post <- @posts do %>
54 |
55 |
56 | <%= post.body %>
57 |
58 | <%= post.inserted_at %>
59 |
60 | <%= post.updated_at %>
61 |
62 |
63 | <%= link "Show", to: Routes.post_path(@conn, :show, post) %>
64 | <%= link "Edit", to: Routes.post_path(@conn, :edit, post) %>
65 | <%= link "Delete", to: Routes.post_path(@conn, :delete, post), method: :delete, data: [confirm: "Are you sure?"] %>
66 |
67 |
68 | <% end %>
69 |
70 |
71 | <%= render Torch.PaginationView, "_pagination.html", assigns %>
72 | <% else %>
73 | No Posts match your search.
74 | <% end %>
75 |
76 |
77 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/post/new.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.post_path(@conn, :create)) %>
13 |
14 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/post/show.html.eex:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 | Body:
17 | <%= @post.body %>
18 |
19 |
20 |
21 | Inserted at:
22 | <%= @post.inserted_at %>
23 |
24 |
25 |
26 | Updated at:
27 | <%= @post.updated_at %>
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/thread/edit.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.thread_path(@conn, :update, @thread)) %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/thread/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, [multipart: true, id: "torch-form"], fn f -> %>
2 |
3 |
4 | Details
5 |
6 | <%= if @changeset.action do %>
7 | Oops, something went wrong! Please check the errors below.
8 | <% end %>
9 |
10 |
11 | <%= label f, :title %>
12 | <%= text_input f, :title %>
13 | <%= error_tag f, :title %>
14 |
15 |
16 |
17 | <%= label f, :slug %>
18 | <%= text_input f, :slug %>
19 | <%= error_tag f, :slug %>
20 |
21 |
22 |
23 | <%= label f, :category_id %>
24 | <%= text_input f, :category_id %>
25 | <%= error_tag f, :category_id %>
26 |
27 |
28 |
29 | <%= submit "Submit", class: "torch-submit-button" %>
30 |
31 |
32 | <% end %>
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/thread/index.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | Find Threads
10 | <%= form_tag @conn.request_path, method: :get, id: "torch-filters-form" do %>
11 |
12 |
13 | Title
14 | <%= filter_select(:thread, :title, @conn.params) %>
15 | <%= filter_string_input(:thread, :title, @conn.params) %>
16 |
17 |
18 |
19 | Slug
20 | <%= filter_select(:thread, :slug, @conn.params) %>
21 | <%= filter_string_input(:thread, :slug, @conn.params) %>
22 |
23 |
24 |
25 |
26 |
27 | Inserted at
28 | <%= filter_date_input(:thread, :inserted_at, @conn.params) %>
29 |
30 |
31 |
32 | Updated at
33 | <%= filter_date_input(:thread, :updated_at, @conn.params) %>
34 |
35 |
36 |
37 | Search
38 | <%= link "Clear Filters", to: Routes.thread_path(@conn, :index) %>
39 | <% end %>
40 |
41 |
42 |
43 |
44 | <%= if length(@threads) > 0 do %>
45 |
46 |
47 |
48 |
49 | <%= table_link(@conn, "Title", :title) %>
50 |
51 | <%= table_link(@conn, "Slug", :slug) %>
52 |
53 | <%= table_link(@conn, "Inserted at", :inserted_at) %>
54 |
55 | <%= table_link(@conn, "Updated at", :updated_at) %>
56 |
57 | Actions
58 |
59 |
60 |
61 | <%= for thread <- @threads do %>
62 |
63 |
64 | <%= thread.title %>
65 |
66 | <%= thread.slug %>
67 |
68 | <%= thread.inserted_at %>
69 |
70 | <%= thread.updated_at %>
71 |
72 |
73 | <%= link "Show", to: Routes.thread_path(@conn, :show, thread) %>
74 | <%= link "Edit", to: Routes.thread_path(@conn, :edit, thread) %>
75 | <%= link "Delete", to: Routes.thread_path(@conn, :delete, thread), method: :delete, data: [confirm: "Are you sure?"] %>
76 |
77 |
78 | <% end %>
79 |
80 |
81 | <%= render Torch.PaginationView, "_pagination.html", assigns %>
82 | <% else %>
83 | No Threads match your search.
84 | <% end %>
85 |
86 |
87 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/thread/new.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.thread_path(@conn, :create)) %>
13 |
14 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/thread/show.html.eex:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 | Category:
17 | <%= link "Category", to: Routes.category_path(@conn, :show, @thread.category_id) %> - <%= @thread.category_id %>
18 |
19 |
20 |
21 | Title:
22 | <%= @thread.title %>
23 |
24 |
25 |
26 | Slug:
27 | <%= @thread.slug %>
28 |
29 |
30 |
31 | Inserted at:
32 | <%= @thread.inserted_at %>
33 |
34 |
35 |
36 | Updated at:
37 | <%= @thread.updated_at %>
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/user/edit.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.user_path(@conn, :update, @user)) %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/user/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, [multipart: true, id: "torch-form"], fn f -> %>
2 |
3 |
4 | Details
5 |
6 | <%= if @changeset.action do %>
7 | Oops, something went wrong! Please check the errors below.
8 | <% end %>
9 |
10 |
11 | <%= label f, :email %>
12 | <%= text_input f, :email %>
13 | <%= error_tag f, :email %>
14 |
15 |
16 |
17 | <%= label f, :name %>
18 | <%= text_input f, :name %>
19 | <%= error_tag f, :name %>
20 |
21 |
22 |
23 | <%= label f, :username %>
24 | <%= text_input f, :username %>
25 | <%= error_tag f, :username %>
26 |
27 |
28 |
29 | <%= submit "Submit", class: "torch-submit-button" %>
30 |
31 |
32 | <% end %>
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/user/index.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | Find Users
10 | <%= form_tag @conn.request_path, method: :get, id: "torch-filters-form" do %>
11 |
12 |
13 | Email
14 | <%= filter_select(:user, :email, @conn.params) %>
15 | <%= filter_string_input(:user, :email, @conn.params) %>
16 |
17 |
18 |
19 | Name
20 | <%= filter_select(:user, :name, @conn.params) %>
21 | <%= filter_string_input(:user, :name, @conn.params) %>
22 |
23 |
24 |
25 | Username
26 | <%= filter_select(:user, :username, @conn.params) %>
27 | <%= filter_string_input(:user, :username, @conn.params) %>
28 |
29 |
30 |
31 |
32 |
33 | Search
34 | <%= link "Clear Filters", to: Routes.user_path(@conn, :index) %>
35 | <% end %>
36 |
37 |
38 |
39 |
40 | <%= if length(@users) > 0 do %>
41 |
42 |
43 |
44 |
45 | <%= table_link(@conn, "Email", :email) %>
46 |
47 | <%= table_link(@conn, "Name", :name) %>
48 |
49 | <%= table_link(@conn, "Username", :username) %>
50 |
51 | Actions
52 |
53 |
54 |
55 | <%= for user <- @users do %>
56 |
57 |
58 | <%= user.email %>
59 |
60 | <%= user.name %>
61 |
62 | <%= user.username %>
63 |
64 |
65 | <%= link "Show", to: Routes.user_path(@conn, :show, user) %>
66 | <%= link "Edit", to: Routes.user_path(@conn, :edit, user) %>
67 | <%= link "Delete", to: Routes.user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"] %>
68 |
69 |
70 | <% end %>
71 |
72 |
73 | <%= render Torch.PaginationView, "_pagination.html", assigns %>
74 | <% else %>
75 | No Users match your search.
76 | <% end %>
77 |
78 |
79 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/user/new.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%= render "form.html", Map.put(assigns, :action, Routes.user_path(@conn, :create)) %>
13 |
14 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/admin/user/show.html.eex:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 | Email:
17 | <%= @user.email %>
18 |
19 |
20 |
21 | Name:
22 | <%= @user.name %>
23 |
24 |
25 |
26 | Username:
27 | <%= @user.username %>
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/auth/forgot_password_verification.html.eex:
--------------------------------------------------------------------------------
1 | Forgot your password?
2 |
3 | No problem. Click here to reset it in the app. .
4 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/auth/login.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | " alt="Phoenix Framework Logo"/>
5 |
6 |
Firestorm Admin Login
7 | <%= form_for @conn, Routes.auth_path(@conn, :authenticate), fn f -> %>
8 | <%= text_input f, :email , placeholder: "Email", type: "email"%>
9 | <%= password_input f, :password, placeholder: "Password", type: "password" %>
10 | <%= submit "Submit" %>
11 | <% end %>
12 |
13 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Firestorm · Phoenix Framework
8 | "/>
9 |
10 |
11 |
12 | <%= render @view_module, @view_template, assigns %>
13 | <%= get_flash(@conn, :info) %>
14 | <%= get_flash(@conn, :error) %>
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/layout/torch.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 | Admin
4 |
5 |
6 | ">
7 | ">
8 |
9 |
10 |
11 |
37 |
38 | <%= Torch.FlashView.render("_flash_messages.html", assigns) %>
39 | <%= render @view_module, @view_template, assigns %>
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 | <%= gettext "Welcome to %{name}!", name: "Phoenix" %>
3 | A productive web framework that does not compromise speed and maintainability.
4 |
5 |
6 |
7 |
8 | Resources
9 |
20 |
21 |
22 | Help
23 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/admin/category_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.CategoryView do
2 | use FirestormWeb, :view
3 |
4 | import Torch.TableView
5 | import Torch.FilterView
6 | end
7 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/admin/post_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.PostView do
2 | use FirestormWeb, :view
3 |
4 | import Torch.TableView
5 | import Torch.FilterView
6 | end
7 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/admin/thread_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.ThreadView do
2 | use FirestormWeb, :view
3 |
4 | import Torch.TableView
5 | import Torch.FilterView
6 | end
7 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/admin/user_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.Admin.UserView do
2 | use FirestormWeb, :view
3 |
4 | import Torch.TableView
5 | import Torch.FilterView
6 | end
7 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/auth_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.AuthView do
2 | use FirestormWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.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(FirestormWeb.Gettext, "errors", msg, msg, count, opts)
40 | else
41 | Gettext.dgettext(FirestormWeb.Gettext, "errors", msg, opts)
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.ErrorView do
2 | use FirestormWeb, :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 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.LayoutView do
2 | use FirestormWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/apps/firestorm/lib/firestorm_web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.PageView do
2 | use FirestormWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/apps/firestorm/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :firestorm,
7 | version: "0.1.0",
8 | build_path: "../../_build",
9 | config_path: "../../config/config.exs",
10 | deps_path: "../../deps",
11 | lockfile: "../../mix.lock",
12 | elixir: "~> 1.5",
13 | elixirc_paths: elixirc_paths(Mix.env()),
14 | compilers: [:phoenix, :gettext] ++ Mix.compilers(),
15 | start_permanent: Mix.env() == :prod,
16 | deps: deps()
17 | ]
18 | end
19 |
20 | # Configuration for the OTP application.
21 | #
22 | # Type `mix help compile.app` for more information.
23 | def application do
24 | [
25 | mod: {Firestorm.Application, []},
26 | extra_applications: [:logger, :runtime_tools, :phoenix_ecto]
27 | ]
28 | end
29 |
30 | # Specifies which paths to compile per environment.
31 | defp elixirc_paths(:test), do: ["lib", "test/support"]
32 | defp elixirc_paths(_), do: ["lib"]
33 |
34 | # Specifies your project dependencies.
35 | #
36 | # Type `mix help deps` for examples and options.
37 | defp deps do
38 | [
39 | {:phoenix, "~> 1.4.0"},
40 | {:phoenix_ecto, "~> 4.0.0"},
41 | {:phoenix_pubsub, "~> 1.1"},
42 | {:phoenix_html, "~> 2.11"},
43 | {:phoenix_live_reload, "~> 1.2", only: :dev},
44 | {:gettext, "~> 0.11"},
45 | {:jason, "~> 1.0"},
46 | {:plug_cowboy, "~> 2.0"},
47 | {:absinthe, "~> 1.4"},
48 | {:faker, "~> 0.10"},
49 | {:ex_machina, "~> 2.3", only: :test},
50 | {:absinthe_plug, "~> 1.4"},
51 | {:absinthe_phoenix, "~> 1.4"},
52 | {:corsica, "~> 1.1"},
53 | {:ecto, "~> 3.0"},
54 | {:torch, "~> 2.0"},
55 | {:dataloader, "~> 1.0"},
56 | {:gravity, "~> 1.0"},
57 | {:firestorm_data, in_umbrella: true}
58 | ]
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/apps/firestorm/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 |
--------------------------------------------------------------------------------
/apps/firestorm/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 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/static/css/app.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary-color: #DB3440;
3 | --background-color: #fff;
4 | }
5 |
6 | * {
7 | box-sizing: border-box;
8 | }
9 |
10 | body {
11 | background: var(--background-color);
12 | display: flex;
13 | justify-content: center;
14 | align-items: center;
15 | flex-direction: column;
16 | font-family: 'Montserrat', sans-serif;
17 | height: 100vh;
18 | margin: -20px 0 50px;
19 | }
20 |
21 | .centered {
22 | text-align: center;
23 | }
24 |
25 | button {
26 | border-radius: 20px;
27 | border: 1px solid #DB3440;
28 | background-color: #DB3440;
29 | color: #FFFFFF;
30 | font-size: 12px;
31 | font-weight: bold;
32 | padding: 12px 45px;
33 | letter-spacing: 1px;
34 | text-transform: uppercase;
35 | transition: transform 80ms ease-in;
36 | }
37 |
38 | input {
39 | background-color: #eee;
40 | border: none;
41 | padding: 12px 15px;
42 | margin: 8px 0;
43 | width: 100%;
44 | }
45 |
46 | .container {
47 | background-color: #fff;
48 | position: relative;
49 | overflow: hidden;
50 | max-width: 100%;
51 | min-height: 480px;
52 | }
--------------------------------------------------------------------------------
/apps/firestorm/priv/static/css/base_admin.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary-color: #DB3440;
3 | --background-color: #fff;
4 | }
5 |
6 | section#torch-account-info {
7 | background-color: var(--primary-color);
8 | }
9 |
10 | section#torch-nav .torch-nav a.active {
11 | border-color: var(--primary-color);
12 | }
13 |
14 | section#torch-nav .torch-logo a h3 {
15 | color: var(--primary-color);
16 | }
17 |
18 | section#torch-nav .torch-logo a:before {
19 | background-image: url(/images/firestorm-logo.png);
20 | }
21 |
22 | section#torch-table table td a {
23 | color: var(--primary-color);
24 | }
25 |
26 | section#torch-filters a {
27 | color: var(--primary-color);
28 | }
29 |
30 | .color-gray {
31 | color: #4A4A4A !important;
32 | }
33 |
34 | .torch-actions {
35 | text-align: center;
36 | }
37 |
38 | .centered {
39 | text-align: center;
40 | }
--------------------------------------------------------------------------------
/apps/firestorm/priv/static/images/firestorm-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/firestormforum/firestorm_elixir/80caba13daa21ef6087aa85f6cf0dd1f016e9aef/apps/firestorm/priv/static/images/firestorm-logo.png
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.context/access_no_schema.ex:
--------------------------------------------------------------------------------
1 |
2 | import Torch.Helpers, only: [sort: 1, paginate: 4]
3 | import Filtrex.Type.Config
4 |
5 | alias <%= inspect schema.module %>
6 |
7 | @pagination [page_size: 15]
8 | @pagination_distance 5
9 |
10 | @doc """
11 | Paginate the list of <%= schema.plural %> using filtrex
12 | filters.
13 |
14 | ## Examples
15 |
16 | iex> list_<%= schema.plural %>(%{})
17 | %{<%= schema.plural %>: [%<%= inspect schema.alias %>{}], ...}
18 | """
19 | @spec paginate_<%= schema.plural %>(map) :: {:ok, map} | {:error, any}
20 | def paginate_<%= schema.plural %>(params \\ %{}) do
21 | params =
22 | params
23 | |> Map.put_new("sort_direction", "desc")
24 | |> Map.put_new("sort_field", "inserted_at")
25 |
26 | {:ok, sort_direction} = Map.fetch(params, "sort_direction")
27 | {:ok, sort_field} = Map.fetch(params, "sort_field")
28 |
29 | with {:ok, filter} <- Filtrex.parse_params(filter_config(<%= inspect String.to_atom(schema.plural) %>), params["<%= schema.singular %>"] || %{}),
30 | %Scrivener.Page{} = page <- do_paginate_<%= schema.plural %>(filter, params) do
31 | {:ok,
32 | %{
33 | <%= schema.plural %>: page.entries,
34 | page_number: page.page_number,
35 | page_size: page.page_size,
36 | total_pages: page.total_pages,
37 | total_entries: page.total_entries,
38 | distance: @pagination_distance,
39 | sort_field: sort_field,
40 | sort_direction: sort_direction
41 | }
42 | }
43 | else
44 | {:error, error} -> {:error, error}
45 | error -> {:error, error}
46 | end
47 | end
48 |
49 | defp do_paginate_<%= schema.plural %>(filter, params) do
50 | <%= inspect schema.alias %>
51 | |> Filtrex.query(filter)
52 | |> order_by(^sort(params))
53 | |> paginate(Repo, params, @pagination)
54 | end
55 |
56 | @doc """
57 | Returns the list of <%= schema.plural %>.
58 |
59 | ## Examples
60 |
61 | iex> list_<%= schema.plural %>()
62 | [%<%= inspect schema.alias %>{}, ...]
63 |
64 | """
65 | def list_<%= schema.plural %> do
66 | Repo.all(<%= inspect schema.alias %>)
67 | end
68 |
69 | @doc """
70 | Gets a single <%= schema.singular %>.
71 |
72 | Raises `Ecto.NoResultsError` if the <%= schema.human_singular %> does not exist.
73 |
74 | ## Examples
75 |
76 | iex> get_<%= schema.singular %>!(123)
77 | %<%= inspect schema.alias %>{}
78 |
79 | iex> get_<%= schema.singular %>!(456)
80 | ** (Ecto.NoResultsError)
81 |
82 | """
83 | def get_<%= schema.singular %>!(id), do: Repo.get!(<%= inspect schema.alias %>, id)
84 |
85 | @doc """
86 | Creates a <%= schema.singular %>.
87 |
88 | ## Examples
89 |
90 | iex> create_<%= schema.singular %>(%{field: value})
91 | {:ok, %<%= inspect schema.alias %>{}}
92 |
93 | iex> create_<%= schema.singular %>(%{field: bad_value})
94 | {:error, %Ecto.Changeset{}}
95 |
96 | """
97 | def create_<%= schema.singular %>(attrs \\ %{}) do
98 | %<%= inspect schema.alias %>{}
99 | |> <%= inspect schema.alias %>.changeset(attrs)
100 | |> Repo.insert()
101 | end
102 |
103 | @doc """
104 | Updates a <%= schema.singular %>.
105 |
106 | ## Examples
107 |
108 | iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: new_value})
109 | {:ok, %<%= inspect schema.alias %>{}}
110 |
111 | iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: bad_value})
112 | {:error, %Ecto.Changeset{}}
113 |
114 | """
115 | def update_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do
116 | <%= schema.singular %>
117 | |> <%= inspect schema.alias %>.changeset(attrs)
118 | |> Repo.update()
119 | end
120 |
121 | @doc """
122 | Deletes a <%= inspect schema.alias %>.
123 |
124 | ## Examples
125 |
126 | iex> delete_<%= schema.singular %>(<%= schema.singular %>)
127 | {:ok, %<%= inspect schema.alias %>{}}
128 |
129 | iex> delete_<%= schema.singular %>(<%= schema.singular %>)
130 | {:error, %Ecto.Changeset{}}
131 |
132 | """
133 | def delete_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do
134 | Repo.delete(<%= schema.singular %>)
135 | end
136 |
137 | @doc """
138 | Returns an `%Ecto.Changeset{}` for tracking <%= schema.singular %> changes.
139 |
140 | ## Examples
141 |
142 | iex> change_<%= schema.singular %>(<%= schema.singular %>)
143 | %Ecto.Changeset{source: %<%= inspect schema.alias %>{}}
144 |
145 | """
146 | def change_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do
147 | <%= inspect schema.alias %>.changeset(<%= schema.singular %>, %{})
148 | end
149 |
150 | defp filter_config(<%= inspect String.to_atom(schema.plural) %>) do
151 | defconfig do
152 | <%= for {name, type} <- schema.attrs do %><%= cond do %>
153 | <% type in [:string, :text] -> %>text <%= inspect name %>
154 | <% type in [:integer, :number] -> %>number <%= inspect name %>
155 | <% type in [:naive_datetime, :utc_datetime, :datetime, :date] -> %>date <%= inspect name %>
156 | <% type in [:boolean] -> %>boolean <%= inspect name %>
157 | <% true -> %> #TODO add config for <%= name %> of type <%= type %>
158 | <% end %><% end %>
159 | end
160 | end
161 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.context/context.ex:
--------------------------------------------------------------------------------
1 | defmodule <%= inspect context.module %> do
2 | @moduledoc """
3 | The <%= context.name %> context.
4 | """
5 |
6 | import Ecto.Query, warn: false
7 | alias <%= inspect schema.repo %>
8 | end
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.context/context_test.exs:
--------------------------------------------------------------------------------
1 | defmodule <%= inspect context.module %>Test do
2 | use <%= inspect context.base_module %>.DataCase
3 |
4 | alias <%= inspect context.module %>
5 | end
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.context/schema_access.ex:
--------------------------------------------------------------------------------
1 | import Torch.Helpers, only: [sort: 1, paginate: 4]
2 | import Filtrex.Type.Config
3 |
4 | alias <%= inspect schema.module %>
5 |
6 | @pagination [page_size: 15]
7 | @pagination_distance 5
8 |
9 | @doc """
10 | Paginate the list of <%= schema.plural %> using filtrex
11 | filters.
12 |
13 | ## Examples
14 |
15 | iex> list_<%= schema.plural %>(%{})
16 | %{<%= schema.plural %>: [%<%= inspect schema.alias %>{}], ...}
17 | """
18 | @spec paginate_<%= schema.plural %>(map) :: {:ok, map} | {:error, any}
19 | def paginate_<%= schema.plural %>(params \\ %{}) do
20 | params =
21 | params
22 | |> Map.put_new("sort_direction", "desc")
23 | |> Map.put_new("sort_field", "inserted_at")
24 |
25 | {:ok, sort_direction} = Map.fetch(params, "sort_direction")
26 | {:ok, sort_field} = Map.fetch(params, "sort_field")
27 |
28 | with {:ok, filter} <- Filtrex.parse_params(filter_config(<%= inspect String.to_atom(schema.plural) %>), params["<%= schema.singular %>"] || %{}),
29 | %Scrivener.Page{} = page <- do_paginate_<%= schema.plural %>(filter, params) do
30 | {:ok,
31 | %{
32 | <%= schema.plural %>: page.entries,
33 | page_number: page.page_number,
34 | page_size: page.page_size,
35 | total_pages: page.total_pages,
36 | total_entries: page.total_entries,
37 | distance: @pagination_distance,
38 | sort_field: sort_field,
39 | sort_direction: sort_direction
40 | }
41 | }
42 | else
43 | {:error, error} -> {:error, error}
44 | error -> {:error, error}
45 | end
46 | end
47 |
48 | defp do_paginate_<%= schema.plural %>(filter, params) do
49 | <%= inspect schema.alias %>
50 | |> Filtrex.query(filter)
51 | |> order_by(^sort(params))
52 | |> paginate(Repo, params, @pagination)
53 | end
54 |
55 | @doc """
56 | Returns the list of <%= schema.plural %>.
57 |
58 | ## Examples
59 |
60 | iex> list_<%= schema.plural %>()
61 | [%<%= inspect schema.alias %>{}, ...]
62 |
63 | """
64 | def list_<%= schema.plural %> do
65 | Repo.all(<%= inspect schema.alias %>)
66 | end
67 |
68 | @doc """
69 | Gets a single <%= schema.singular %>.
70 |
71 | Raises `Ecto.NoResultsError` if the <%= schema.human_singular %> does not exist.
72 |
73 | ## Examples
74 |
75 | iex> get_<%= schema.singular %>!(123)
76 | %<%= inspect schema.alias %>{}
77 |
78 | iex> get_<%= schema.singular %>!(456)
79 | ** (Ecto.NoResultsError)
80 |
81 | """
82 | def get_<%= schema.singular %>!(id), do: Repo.get!(<%= inspect schema.alias %>, id)
83 |
84 | @doc """
85 | Creates a <%= schema.singular %>.
86 |
87 | ## Examples
88 |
89 | iex> create_<%= schema.singular %>(%{field: value})
90 | {:ok, %<%= inspect schema.alias %>{}}
91 |
92 | iex> create_<%= schema.singular %>(%{field: bad_value})
93 | {:error, %Ecto.Changeset{}}
94 |
95 | """
96 | def create_<%= schema.singular %>(attrs \\ %{}) do
97 | %<%= inspect schema.alias %>{}
98 | |> <%= inspect schema.alias %>.changeset(attrs)
99 | |> Repo.insert()
100 | end
101 |
102 | @doc """
103 | Updates a <%= schema.singular %>.
104 |
105 | ## Examples
106 |
107 | iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: new_value})
108 | {:ok, %<%= inspect schema.alias %>{}}
109 |
110 | iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: bad_value})
111 | {:error, %Ecto.Changeset{}}
112 |
113 | """
114 | def update_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do
115 | <%= schema.singular %>
116 | |> <%= inspect schema.alias %>.changeset(attrs)
117 | |> Repo.update()
118 | end
119 |
120 | @doc """
121 | Deletes a <%= inspect schema.alias %>.
122 |
123 | ## Examples
124 |
125 | iex> delete_<%= schema.singular %>(<%= schema.singular %>)
126 | {:ok, %<%= inspect schema.alias %>{}}
127 |
128 | iex> delete_<%= schema.singular %>(<%= schema.singular %>)
129 | {:error, %Ecto.Changeset{}}
130 |
131 | """
132 | def delete_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do
133 | Repo.delete(<%= schema.singular %>)
134 | end
135 |
136 | @doc """
137 | Returns an `%Ecto.Changeset{}` for tracking <%= schema.singular %> changes.
138 |
139 | ## Examples
140 |
141 | iex> change_<%= schema.singular %>(<%= schema.singular %>)
142 | %Ecto.Changeset{source: %<%= inspect schema.alias %>{}}
143 |
144 | """
145 | def change_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do
146 | <%= inspect schema.alias %>.changeset(<%= schema.singular %>, %{})
147 | end
148 |
149 | defp filter_config(<%= inspect String.to_atom(schema.plural) %>) do
150 | defconfig do
151 | <%= for {name, type} <- schema.attrs do %><%= cond do %>
152 | <% type in [:string, :text] -> %>text <%= inspect name %>
153 | <% type in [:integer, :number] -> %>number <%= inspect name %>
154 | <% type in [:naive_datetime, :utc_datetime, :datetime, :date] -> %>date <%= inspect name %>
155 | <% type in [:boolean] -> %>boolean <%= inspect name %>
156 | <% true -> %> #TODO add config for <%= name %> of type <%= type %>
157 | <% end %><% end %>
158 | end
159 | end
160 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.context/test_cases.exs:
--------------------------------------------------------------------------------
1 |
2 | describe "<%= schema.plural %>" do
3 | alias <%= inspect schema.module %>
4 |
5 | @valid_attrs <%= inspect schema.params.create %>
6 | @update_attrs <%= inspect schema.params.update %>
7 | @invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
8 |
9 | def <%= schema.singular %>_fixture(attrs \\ %{}) do
10 | {:ok, <%= schema.singular %>} =
11 | attrs
12 | |> Enum.into(@valid_attrs)
13 | |> <%= inspect context.alias %>.create_<%= schema.singular %>()
14 |
15 | <%= schema.singular %>
16 | end
17 |
18 | test "paginate_<%= schema.plural %>/1 returns paginated list of <%= schema.plural %>" do
19 | for _ <- 1..20 do
20 | <%= schema.singular %>_fixture()
21 | end
22 |
23 | {:ok, %{<%= schema.plural %>: <%= schema.plural %>} = page} = <%= inspect context.alias %>.paginate_<%= schema.plural %>(%{})
24 |
25 | assert length(<%= schema.plural %>) == 15
26 | assert page.page_number == 1
27 | assert page.page_size == 15
28 | assert page.total_pages == 2
29 | assert page.total_entries == 20
30 | assert page.distance == 5
31 | assert page.sort_field == "inserted_at"
32 | assert page.sort_direction == "desc"
33 | end
34 |
35 | test "list_<%= schema.plural %>/0 returns all <%= schema.plural %>" do
36 | <%= schema.singular %> = <%= schema.singular %>_fixture()
37 | assert <%= inspect context.alias %>.list_<%= schema.plural %>() == [<%= schema.singular %>]
38 | end
39 |
40 | test "get_<%= schema.singular %>!/1 returns the <%= schema.singular %> with given id" do
41 | <%= schema.singular %> = <%= schema.singular %>_fixture()
42 | assert <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id) == <%= schema.singular %>
43 | end
44 |
45 | test "create_<%= schema.singular %>/1 with valid data creates a <%= schema.singular %>" do
46 | assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(@valid_attrs)<%= for {field, value} <- schema.params.create do %>
47 | assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>
48 | end
49 |
50 | test "create_<%= schema.singular %>/1 with invalid data returns error changeset" do
51 | assert {:error, %Ecto.Changeset{}} = <%= inspect context.alias %>.create_<%= schema.singular %>(@invalid_attrs)
52 | end
53 |
54 | test "update_<%= schema.singular %>/2 with valid data updates the <%= schema.singular %>" do
55 | <%= schema.singular %> = <%= schema.singular %>_fixture()
56 | assert {:ok, <%= schema.singular %>} = <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, @update_attrs)
57 | assert %<%= inspect schema.alias %>{} = <%= schema.singular %><%= for {field, value} <- schema.params.update do %>
58 | assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>
59 | end
60 |
61 | test "update_<%= schema.singular %>/2 with invalid data returns error changeset" do
62 | <%= schema.singular %> = <%= schema.singular %>_fixture()
63 | assert {:error, %Ecto.Changeset{}} = <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, @invalid_attrs)
64 | assert <%= schema.singular %> == <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id)
65 | end
66 |
67 | test "delete_<%= schema.singular %>/1 deletes the <%= schema.singular %>" do
68 | <%= schema.singular %> = <%= schema.singular %>_fixture()
69 | assert {:ok, %<%= inspect schema.alias %>{}} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>)
70 | assert_raise Ecto.NoResultsError, fn -> <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id) end
71 | end
72 |
73 | test "change_<%= schema.singular %>/1 returns a <%= schema.singular %> changeset" do
74 | <%= schema.singular %> = <%= schema.singular %>_fixture()
75 | assert %Ecto.Changeset{} = <%= inspect context.alias %>.change_<%= schema.singular %>(<%= schema.singular %>)
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/controller.ex:
--------------------------------------------------------------------------------
1 | defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Controller do
2 | use <%= inspect context.web_module %>, :controller
3 |
4 | alias <%= inspect context.module %>
5 | alias <%= inspect schema.module %>
6 |
7 | plug(:put_layout, {<%= inspect context.web_module %>.LayoutView, "torch.html"})
8 |
9 | def index(conn, params) do
10 | case <%= inspect context.alias %>.paginate_<%= schema.plural %>(params) do
11 | {:ok, assigns} ->
12 | render(conn, "index.html", assigns)
13 | error ->
14 | conn
15 | |> put_flash(:error, "There was an error rendering <%= schema.human_plural %>. #{inspect(error)}")
16 | |> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :index))
17 | end
18 | end
19 |
20 | def new(conn, _params) do
21 | changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(%<%= inspect schema.alias %>{})
22 | render(conn, "new.html", changeset: changeset)
23 | end
24 |
25 | def create(conn, %{<%= inspect schema.singular %> => <%= schema.singular %>_params}) do
26 | case <%= inspect context.alias %>.create_<%= schema.singular %>(<%= schema.singular %>_params) do
27 | {:ok, <%= schema.singular %>} ->
28 | conn
29 | |> put_flash(:info, "<%= schema.human_singular %> created successfully.")
30 | |> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>))
31 | {:error, %Ecto.Changeset{} = changeset} ->
32 | render(conn, "new.html", changeset: changeset)
33 | end
34 | end
35 |
36 | def show(conn, %{"id" => id}) do
37 | <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id)
38 | render(conn, "show.html", <%= schema.singular %>: <%= schema.singular %>)
39 | end
40 |
41 | def edit(conn, %{"id" => id}) do
42 | <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id)
43 | changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(<%= schema.singular %>)
44 | render(conn, "edit.html", <%= schema.singular %>: <%= schema.singular %>, changeset: changeset)
45 | end
46 |
47 | def update(conn, %{"id" => id, <%= inspect schema.singular %> => <%= schema.singular %>_params}) do
48 | <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id)
49 |
50 | case <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params) do
51 | {:ok, <%= schema.singular %>} ->
52 | conn
53 | |> put_flash(:info, "<%= schema.human_singular %> updated successfully.")
54 | |> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>))
55 | {:error, %Ecto.Changeset{} = changeset} ->
56 | render(conn, "edit.html", <%= schema.singular %>: <%= schema.singular %>, changeset: changeset)
57 | end
58 | end
59 |
60 | def delete(conn, %{"id" => id}) do
61 | <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id)
62 | {:ok, _<%= schema.singular %>} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>)
63 |
64 | conn
65 | |> put_flash(:info, "<%= schema.human_singular %> deleted successfully.")
66 | |> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :index))
67 | end
68 | end
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>ControllerTest do
2 | use <%= inspect context.web_module %>.ConnCase
3 |
4 | alias <%= inspect context.module %>
5 |
6 | @create_attrs <%= inspect schema.params.create %>
7 | @update_attrs <%= inspect schema.params.update %>
8 | @invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
9 |
10 | def fixture(:<%= schema.singular %>) do
11 | {:ok, <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(@create_attrs)
12 | <%= schema.singular %>
13 | end
14 |
15 | describe "index" do
16 | test "lists all <%= schema.plural %>", %{conn: conn} do
17 | conn = get conn, Routes.<%= schema.route_helper %>_path(conn, :index)
18 | assert html_response(conn, 200) =~ "<%= schema.human_plural %>"
19 | end
20 | end
21 |
22 | describe "new <%= schema.singular %>" do
23 | test "renders form", %{conn: conn} do
24 | conn = get conn, Routes.<%= schema.route_helper %>_path(conn, :new)
25 | assert html_response(conn, 200) =~ "New <%= schema.human_singular %>"
26 | end
27 | end
28 |
29 | describe "create <%= schema.singular %>" do
30 | test "redirects to show when data is valid", %{conn: conn} do
31 | conn = post conn, Routes.<%= schema.route_helper %>_path(conn, :create), <%= schema.singular %>: @create_attrs
32 |
33 | assert %{id: id} = redirected_params(conn)
34 | assert redirected_to(conn) == Routes.<%= schema.route_helper %>_path(conn, :show, id)
35 |
36 | conn = get conn, Routes.<%= schema.route_helper %>_path(conn, :show, id)
37 | assert html_response(conn, 200) =~ "<%= schema.human_singular %> Details"
38 | end
39 |
40 | test "renders errors when data is invalid", %{conn: conn} do
41 | conn = post conn, Routes.<%= schema.route_helper %>_path(conn, :create), <%= schema.singular %>: @invalid_attrs
42 | assert html_response(conn, 200) =~ "New <%= schema.human_singular %>"
43 | end
44 | end
45 |
46 | describe "edit <%= schema.singular %>" do
47 | setup [:create_<%= schema.singular %>]
48 |
49 | test "renders form for editing chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
50 | conn = get conn, Routes.<%= schema.route_helper %>_path(conn, :edit, <%= schema.singular %>)
51 | assert html_response(conn, 200) =~ "Edit <%= schema.human_singular %>"
52 | end
53 | end
54 |
55 | describe "update <%= schema.singular %>" do
56 | setup [:create_<%= schema.singular %>]
57 |
58 | test "redirects when data is valid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
59 | conn = put conn, Routes.<%= schema.route_helper %>_path(conn, :update, <%= schema.singular %>), <%= schema.singular %>: @update_attrs
60 | assert redirected_to(conn) == Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>)
61 |
62 | conn = get conn, Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>)<%= if schema.string_attr do %>
63 | assert html_response(conn, 200) =~ <%= inspect Mix.Phoenix.Schema.default_param(schema, :update) %><% else %>
64 | assert html_response(conn, 200)<% end %>
65 | end
66 |
67 | test "renders errors when data is invalid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
68 | conn = put conn, Routes.<%= schema.route_helper %>_path(conn, :update, <%= schema.singular %>), <%= schema.singular %>: @invalid_attrs
69 | assert html_response(conn, 200) =~ "Edit <%= schema.human_singular %>"
70 | end
71 | end
72 |
73 | describe "delete <%= schema.singular %>" do
74 | setup [:create_<%= schema.singular %>]
75 |
76 | test "deletes chosen <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
77 | conn = delete conn, Routes.<%= schema.route_helper %>_path(conn, :delete, <%= schema.singular %>)
78 | assert redirected_to(conn) == Routes.<%= schema.route_helper %>_path(conn, :index)
79 | assert_error_sent 404, fn ->
80 | get conn, Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>)
81 | end
82 | end
83 | end
84 |
85 | defp create_<%= schema.singular %>(_) do
86 | <%= schema.singular %> = fixture(:<%= schema.singular %>)
87 | {:ok, <%= schema.singular %>: <%= schema.singular %>}
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/edit.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%%= render "form.html", Map.put(assigns, :action, Routes.<%= schema.route_helper %>_path(@conn, :update, @<%= schema.singular %>)) %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/form.html.eex:
--------------------------------------------------------------------------------
1 | <%%= form_for @changeset, @action, [multipart: true, id: "torch-form"], fn f -> %>
2 |
3 |
4 | Details
5 |
6 | <%%= if @changeset.action do %>
7 | Oops, something went wrong! Please check the errors below.
8 | <%% end %>
9 | <%= for {label, input, error} <- inputs, input do %>
10 |
11 | <%= label %>
12 | <%= input %>
13 | <%= error %>
14 |
15 | <% end %>
16 |
17 | <%%= submit "Submit", class: "torch-submit-button" %>
18 |
19 |
20 | <%% end %>
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/index.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | Find <%= schema.human_plural %>
10 | <%%= form_tag @conn.request_path, method: :get, id: "torch-filters-form" do %>
11 | <%= for {key, type} <- schema.attrs, type in [:string, :text] do %>
12 |
13 | <%= Phoenix.Naming.humanize(Atom.to_string(key)) %>
14 | <%%= filter_select(:<%= schema.singular %>, :<%= key %>, @conn.params) %>
15 | <%%= filter_<%= type %>_input(:<%= schema.singular %>, :<%= key %>, @conn.params) %>
16 |
17 | <% end %>
18 | <%= for {key, type} <- schema.attrs, type in [:boolean] do %>
19 |
20 | <%= Phoenix.Naming.humanize(Atom.to_string(key)) %>
21 | <%%= filter_boolean_input(:<%= schema.singular %>, :<%= key %>, @conn.params) %>
22 |
23 | <% end %>
24 | <%= for {key, type} <- schema.attrs, type in [:date, :datetime, :utc_datetime, :naive_datetime] do %>
25 |
26 | <%= Phoenix.Naming.humanize(Atom.to_string(key)) %>
27 | <%%= filter_date_input(:<%= schema.singular %>, :<%= key %>, @conn.params) %>
28 |
29 | <% end %>
30 | <%= for {key, type} <- schema.attrs, type in [:number, :integer] do %>
31 |
32 | <%= Phoenix.Naming.humanize(Atom.to_string(key)) %>
33 | <%%= number_filter_select(:<%= schema.singular %>, :<%= key %>, @conn.params) %>
34 | <%%= filter_number_input(:<%= schema.singular %>, :<%= key %>, @conn.params) %>
35 |
36 | <% end %>
37 | Search
38 | <%%= link "Clear Filters", to: Routes.<%= schema.route_helper %>_path(@conn, :index) %>
39 | <%% end %>
40 |
41 |
42 |
43 |
44 | <%%= if length(@<%= schema.plural %>) > 0 do %>
45 |
46 |
47 |
48 | <%= for {k, _} <- schema.attrs do %>
49 | <%%= table_link(@conn, "<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>", <%= inspect(k) %>) %>
50 | <% end %>
51 | Actions
52 |
53 |
54 |
55 | <%%= for <%= schema.singular %> <- @<%= schema.plural %> do %>
56 |
57 | <%= for {k, _} <- schema.attrs do %>
58 | <%%= <%= schema.singular %>.<%= k %> %>
59 | <% end %>
60 |
61 | <%%= link "Show", to: Routes.<%= schema.route_helper %>_path(@conn, :show, <%= schema.singular %>) %>
62 | <%%= link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, <%= schema.singular %>) %>
63 | <%%= link "Delete", to: Routes.<%= schema.route_helper %>_path(@conn, :delete, <%= schema.singular %>), method: :delete, data: [confirm: "Are you sure?"] %>
64 |
65 |
66 | <%% end %>
67 |
68 |
69 | <%%= render Torch.PaginationView, "_pagination.html", assigns %>
70 | <%% else %>
71 | No <%= schema.human_plural %> match your search.
72 | <%% end %>
73 |
74 |
75 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/new.html.eex:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
12 | <%%= render "form.html", Map.put(assigns, :action, Routes.<%= schema.route_helper %>_path(@conn, :create)) %>
13 |
14 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/show.html.eex:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/apps/firestorm/priv/templates/phx.gen.html/view.ex:
--------------------------------------------------------------------------------
1 | defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>View do
2 | use <%= inspect context.web_module %>, :view
3 |
4 | import Torch.TableView
5 | import Torch.FilterView
6 | end
7 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/mutations/categories_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Mutations.CategoriesTest do
2 | use FirestormWeb.ConnCase
3 | alias FirestormWeb.Schema
4 | alias FirestormData.Users
5 |
6 | @email "josh@smoothterminal.com"
7 | @name "Josh Adams"
8 | @username "knewter"
9 | @password "password"
10 | @title "Some category"
11 |
12 | test "creating a category without a user fails" do
13 | context = %{}
14 |
15 | {:ok, %{errors: errors}} = Absinthe.run(create_category_query(), Schema, context: context)
16 |
17 | assert "unauthorized" in Enum.map(errors, & &1.message)
18 | end
19 |
20 | test "creating a category" do
21 | {:ok, user} =
22 | Users.create_user(%{
23 | username: @username,
24 | email: @email,
25 | password: @password,
26 | name: @name
27 | })
28 |
29 | context = %{current_user: user}
30 |
31 | {:ok, %{data: %{"createCategory" => category}}} =
32 | Absinthe.run(create_category_query(), Schema, context: context)
33 |
34 | assert category["id"]
35 | assert category["title"] == @title
36 | end
37 |
38 | defp create_category_query do
39 | """
40 | mutation {
41 | createCategory(title: "#{@title}") {
42 | id
43 | title
44 | }
45 | }
46 | """
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/mutations/posts_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Mutations.PostsTest do
2 | use FirestormWeb.ConnCase
3 | alias FirestormWeb.Schema
4 |
5 | alias FirestormData.{
6 | Categories,
7 | Threads,
8 | Users
9 | }
10 |
11 | @email "josh@smoothterminal.com"
12 | @name "Josh Adams"
13 | @username "knewter"
14 | @password "password"
15 | @category_title "Some category"
16 | @thread_title "Some thread"
17 | @first_post_body "First post"
18 | @body "Second post"
19 |
20 | setup do
21 | {:ok, category} = Categories.create_category(%{title: @category_title})
22 |
23 | {:ok, user} =
24 | Users.create_user(%{
25 | username: @username,
26 | email: @email,
27 | password: @password,
28 | name: @name
29 | })
30 |
31 | {:ok, thread} =
32 | Threads.create_thread(category, user, %{
33 | title: @thread_title,
34 | body: @first_post_body
35 | })
36 |
37 | {:ok, user: user, thread: thread}
38 | end
39 |
40 | test "creating a post without a user fails", %{
41 | thread: thread
42 | } do
43 | context = %{}
44 |
45 | {:ok, %{errors: errors}} = Absinthe.run(create_post_query(thread), Schema, context: context)
46 |
47 | assert "unauthorized" in Enum.map(errors, & &1.message)
48 | end
49 |
50 | test "creating a post", %{
51 | thread: thread,
52 | user: user
53 | } do
54 | context = %{current_user: user}
55 |
56 | {:ok, %{data: %{"createPost" => post}}} =
57 | Absinthe.run(create_post_query(thread), Schema, context: context)
58 |
59 | assert post["id"]
60 | assert post["body"] == @body
61 | assert post["user"]["username"] == @username
62 | assert post["user"]["name"] == @name
63 | end
64 |
65 | defp create_post_query(thread) do
66 | """
67 | mutation {
68 | createPost(body: "#{@body}", threadId: "#{thread.id}") {
69 | id
70 | body
71 | user {
72 | id
73 | username
74 | name
75 | }
76 | }
77 | }
78 | """
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/mutations/threads_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Mutations.ThreadsTest do
2 | use FirestormWeb.ConnCase
3 | alias FirestormWeb.Schema
4 |
5 | alias FirestormData.{
6 | Categories,
7 | Users
8 | }
9 |
10 | @email "josh@smoothterminal.com"
11 | @name "Josh Adams"
12 | @username "knewter"
13 | @password "password"
14 | @category_title "Some category"
15 | @title "Some thread"
16 | @body "First post"
17 |
18 | setup do
19 | {:ok, category} = Categories.create_category(%{title: @category_title})
20 |
21 | {:ok, user} =
22 | Users.create_user(%{
23 | username: @username,
24 | email: @email,
25 | password: @password,
26 | name: @name
27 | })
28 |
29 | {:ok, category: category, user: user}
30 | end
31 |
32 | test "creating a thread without a user fails", %{category: category} do
33 | context = %{}
34 |
35 | {:ok, %{errors: errors}} =
36 | Absinthe.run(create_thread_query(category), Schema, context: context)
37 |
38 | assert "unauthorized" in Enum.map(errors, & &1.message)
39 | end
40 |
41 | test "creating a thread", %{
42 | category: category,
43 | user: user
44 | } do
45 | context = %{current_user: user}
46 |
47 | {:ok, %{data: %{"createThread" => thread}}} =
48 | Absinthe.run(create_thread_query(category), Schema, context: context)
49 |
50 | assert thread["id"]
51 | assert thread["title"] == @title
52 | assert [first_post] = thread["posts"]
53 | assert first_post["id"]
54 | assert first_post["body"] == @body
55 | assert first_post["user"]["username"] == @username
56 | assert first_post["user"]["name"] == @name
57 | end
58 |
59 | defp create_thread_query(category) do
60 | """
61 | mutation {
62 | createThread(title: "#{@title}", body: "#{@body}", categoryId: "#{category.id}") {
63 | id
64 | title
65 | posts {
66 | id
67 | body
68 | user {
69 | id
70 | username
71 | name
72 | }
73 | }
74 | }
75 | }
76 | """
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/mutations/users_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Mutations.UsersTest do
2 | use FirestormWeb.ConnCase
3 | alias FirestormWeb.Schema
4 | alias FirestormData.Users
5 |
6 | @email "josh@smoothterminal.com"
7 | @name "Josh Adams"
8 | @username "knewter"
9 | @password "password"
10 |
11 | test "creating a user" do
12 | query = """
13 | mutation {
14 | createUser(email: "#{@email}", name: "#{@name}", username: "#{@username}", password: "#{
15 | @password
16 | }") {
17 | id
18 | name
19 | username
20 | avatarUrl
21 | }
22 | }
23 | """
24 |
25 | context = %{}
26 |
27 | {:ok, %{data: %{"createUser" => user}}} = Absinthe.run(query, Schema, context: context)
28 |
29 | assert user["id"]
30 | assert user["name"] == @name
31 | assert user["username"] == @username
32 | assert user["avatarUrl"] == Gravity.image(@email)
33 | end
34 |
35 | test "authentication" do
36 | {:ok, user} =
37 | Users.create_user(%{
38 | username: @username,
39 | email: @email,
40 | password: @password,
41 | name: @name
42 | })
43 |
44 | query = """
45 | mutation {
46 | authenticate(email: "#{@email}", password: "#{@password}")
47 | }
48 | """
49 |
50 | context = %{}
51 |
52 | {:ok, %{data: %{"authenticate" => token}}} = Absinthe.run(query, Schema, context: context)
53 |
54 | assert {:ok, user.id} ==
55 | Phoenix.Token.verify(FirestormWeb.Endpoint, "user auth", token, max_age: 86400)
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/queries/categories_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Queries.CategoriesTest do
2 | use FirestormWeb.ConnCase
3 | alias FirestormWeb.Schema
4 |
5 | alias FirestormData.{
6 | Categories,
7 | Threads,
8 | Users
9 | }
10 |
11 | @title "Some category"
12 |
13 | test "getting a paginated list of categories without passing explicit pagination options returns all results" do
14 | query = """
15 | {
16 | categories {
17 | totalPages
18 | totalEntries
19 | page
20 | perPage
21 | entries {
22 | id
23 | title
24 | slug
25 | }
26 | }
27 | }
28 | """
29 |
30 | context = %{}
31 |
32 | {:ok, %{data: %{"categories" => categories}}} = Absinthe.run(query, Schema, context: context)
33 |
34 | assert [] == categories["entries"]
35 | assert categories["page"] == 1
36 |
37 | assert categories["perPage"] == 20
38 | assert categories["totalPages"] == 1
39 | assert categories["totalEntries"] == 0
40 | end
41 |
42 | test "get a paginated list of categories" do
43 | {:ok, _} = Categories.create_category(%{title: @title})
44 |
45 | query = """
46 | {
47 | categories(pagination: {page: 1, perPage: 2}) {
48 | totalPages
49 | totalEntries
50 | page
51 | perPage
52 | entries {
53 | id
54 | title
55 | slug
56 | }
57 | }
58 | }
59 | """
60 |
61 | context = %{}
62 |
63 | {:ok, %{data: %{"categories" => categories}}} = Absinthe.run(query, Schema, context: context)
64 |
65 | assert [first_category] = categories["entries"]
66 | assert first_category["title"] == @title
67 | assert categories["page"] == 1
68 | assert categories["perPage"] == 2
69 | assert categories["totalPages"] == 1
70 | assert categories["totalEntries"] == 1
71 | end
72 |
73 | test "getting a category by id" do
74 | {:ok, category} = Categories.create_category(%{title: @title})
75 |
76 | {:ok, user} =
77 | Users.create_user(%{
78 | username: "knewter",
79 | name: "Josh Adams",
80 | email: "josh@smoothterminal.com"
81 | })
82 |
83 | {:ok, thread} =
84 | Threads.create_thread(category, user, %{title: "Thread title", body: "First post"})
85 |
86 | query = """
87 | {
88 | category(id: "#{category.id}") {
89 | id
90 | title
91 | slug
92 | threads {
93 | id
94 | title
95 | slug
96 | insertedAt
97 | updatedAt
98 | }
99 | }
100 | }
101 | """
102 |
103 | context = %{}
104 |
105 | {:ok, %{data: %{"category" => returned_category}}} =
106 | Absinthe.run(query, Schema, context: context)
107 |
108 | assert returned_category["id"] == category.id
109 | assert returned_category["title"] == category.title
110 | assert returned_category["slug"] == "some-category"
111 | assert [first_thread] = returned_category["threads"]
112 | assert first_thread["id"] == thread.id
113 | assert first_thread["title"] == thread.title
114 | assert first_thread["slug"] == "thread-title"
115 | assert first_thread["insertedAt"]
116 | assert first_thread["updatedAt"]
117 | end
118 | end
119 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/queries/threads_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Queries.ThreadsTest do
2 | use FirestormWeb.ConnCase
3 | alias FirestormWeb.Schema
4 |
5 | alias FirestormData.{
6 | Categories,
7 | Threads,
8 | Users
9 | }
10 |
11 | test "getting a thread by id" do
12 | {:ok, category} = Categories.create_category(%{title: "Some title"})
13 |
14 | {:ok, user} =
15 | Users.create_user(%{
16 | username: "knewter",
17 | name: "Josh Adams",
18 | email: "josh@smoothterminal.com"
19 | })
20 |
21 | {:ok, thread} =
22 | Threads.create_thread(category, user, %{title: "Thread title", body: "First post"})
23 |
24 | query = """
25 | {
26 | thread(id: "#{thread.id}") {
27 | id
28 | title
29 | slug
30 | category {
31 | id
32 | }
33 | posts {
34 | id
35 | body
36 | insertedAt
37 | updatedAt
38 | thread {
39 | id
40 | }
41 | user {
42 | id
43 | name
44 | username
45 | avatarUrl
46 | }
47 | }
48 | }
49 | }
50 | """
51 |
52 | context = %{}
53 |
54 | {:ok, %{data: %{"thread" => returned_thread}}} = Absinthe.run(query, Schema, context: context)
55 |
56 | assert returned_thread["id"] == thread.id
57 | assert returned_thread["title"] == thread.title
58 | assert returned_thread["slug"] == "thread-title"
59 | assert [first_post] = returned_thread["posts"]
60 | assert first_post["body"] == "First post"
61 | assert first_post["insertedAt"]
62 | assert first_post["updatedAt"]
63 | assert first_post["user"]["id"] == user.id
64 | assert first_post["user"]["name"] == user.name
65 | assert first_post["user"]["username"] == user.username
66 | assert first_post["user"]["avatarUrl"]
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/subscriptions/category_added_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Subscriptions.CategoryAddedTest do
2 | use FirestormWeb.SubscriptionCase
3 |
4 | alias FirestormData.{
5 | Users
6 | }
7 |
8 | @title "Some category"
9 |
10 | test "categoryAdded subscription", %{socket: socket} do
11 | subscription_query = """
12 | subscription {
13 | categoryAdded {
14 | id
15 | title
16 | }
17 | }
18 | """
19 |
20 | ref = push_doc(socket, subscription_query)
21 |
22 | assert_reply(ref, :ok, %{subscriptionId: _subscription_id})
23 |
24 | create_category_mutation = """
25 | mutation f {
26 | createCategory(title: "#{@title}") {
27 | title
28 | id
29 | }
30 | }
31 | """
32 |
33 | ref =
34 | push_doc(
35 | socket,
36 | create_category_mutation
37 | )
38 |
39 | assert_reply(ref, :ok, reply)
40 | data = reply.data["createCategory"]
41 | assert data["title"] == @title
42 |
43 | assert_push("subscription:data", push)
44 | data = push.result.data["categoryAdded"]
45 | assert data["title"] == @title
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/subscriptions/post_added_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Subscriptions.PostAddedTest do
2 | use FirestormWeb.SubscriptionCase
3 |
4 | alias FirestormData.{
5 | Categories,
6 | Threads
7 | }
8 |
9 | @category_title "Some category"
10 | @thread_title "Some thread"
11 | @body "Second post"
12 |
13 | setup %{user: user} do
14 | {:ok, category} = Categories.create_category(%{title: @category_title})
15 | {:ok, thread} = Threads.create_thread(category, user, %{title: @thread_title, body: @body})
16 | {:ok, category: category, thread: thread}
17 | end
18 |
19 | test "threadAdded subscription", %{
20 | socket: socket,
21 | thread: thread,
22 | user: user
23 | } do
24 | subscription_query = """
25 | subscription {
26 | postAdded(threadId: "#{thread.id}") {
27 | body
28 | user {
29 | id
30 | }
31 | }
32 | }
33 | """
34 |
35 | ref = push_doc(socket, subscription_query)
36 |
37 | assert_reply(ref, :ok, %{subscriptionId: _subscription_id})
38 |
39 | create_post_mutation = """
40 | mutation f {
41 | createPost(threadId: "#{thread.id}", body: "#{@body}") {
42 | id
43 | body
44 | }
45 | }
46 | """
47 |
48 | ref =
49 | push_doc(
50 | socket,
51 | create_post_mutation
52 | )
53 |
54 | assert_reply(ref, :ok, reply)
55 | data = reply.data["createPost"]
56 | assert data["body"] == @body
57 |
58 | assert_push("subscription:data", push)
59 | data = push.result.data["postAdded"]
60 | assert data["body"] == @body
61 | assert data["user"]["id"] == user.id
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/apps/firestorm/test/absinthe/subscriptions/thread_added_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Absinthe.Subscriptions.ThreadAddedTest do
2 | use FirestormWeb.SubscriptionCase
3 |
4 | alias FirestormData.Categories
5 |
6 | @category_title "Some category"
7 | @title "Some thread"
8 | @body "First post"
9 |
10 | setup do
11 | {:ok, category} = Categories.create_category(%{title: @category_title})
12 | {:ok, category: category}
13 | end
14 |
15 | test "threadAdded subscription", %{
16 | socket: socket,
17 | category: category,
18 | user: user
19 | } do
20 | subscription_query = """
21 | subscription {
22 | threadAdded(categoryId: "#{category.id}") {
23 | id
24 | title
25 | posts {
26 | body
27 | user {
28 | id
29 | }
30 | }
31 | }
32 | }
33 | """
34 |
35 | ref = push_doc(socket, subscription_query)
36 |
37 | assert_reply(ref, :ok, %{subscriptionId: _subscription_id})
38 |
39 | create_thread_mutation = """
40 | mutation f {
41 | createThread(categoryId: "#{category.id}", title: "#{@title}", body: "#{@body}") {
42 | title
43 | id
44 | }
45 | }
46 | """
47 |
48 | ref =
49 | push_doc(
50 | socket,
51 | create_thread_mutation
52 | )
53 |
54 | assert_reply(ref, :ok, reply)
55 | data = reply.data["createThread"]
56 | assert data["title"] == @title
57 |
58 | assert_push("subscription:data", push)
59 | data = push.result.data["threadAdded"]
60 | assert data["title"] == @title
61 | assert [first_post] = data["posts"]
62 | assert first_post["body"] == @body
63 | assert first_post["user"]["id"] == user.id
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm/firestorm_admin/firestorm_admin_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.FirestormAdminTest do
2 | use FirestormData.DataCase
3 | import Firestorm.Factory
4 |
5 | alias Firestorm.FirestormAdmin
6 |
7 | describe "categories" do
8 | alias FirestormData.Categories.Category
9 |
10 | @valid_attrs %{
11 | inserted_at: ~N[2010-04-17 14:00:00],
12 | slug: "some slug",
13 | title: "some title",
14 | updated_at: ~N[2010-04-17 14:00:00]
15 | }
16 | @update_attrs %{
17 | inserted_at: ~N[2011-05-18 15:01:01],
18 | slug: "some updated slug",
19 | title: "some updated title",
20 | updated_at: ~N[2011-05-18 15:01:01]
21 | }
22 | @invalid_attrs %{inserted_at: nil, slug: nil, title: nil, updated_at: nil}
23 |
24 | def category_fixture() do
25 | {:ok, category} = insert(:category)
26 |
27 | category
28 | end
29 |
30 | test "paginate_categories/1 returns paginated list of categories" do
31 | for _ <- 1..20 do
32 | insert(:category)
33 | end
34 |
35 | {:ok, %{categories: categories} = page} = FirestormAdmin.paginate_categories(%{})
36 |
37 | assert length(categories) == 15
38 | assert page.page_number == 1
39 | assert page.page_size == 15
40 | assert page.total_pages == 2
41 | assert page.total_entries == 20
42 | assert page.distance == 5
43 | assert page.sort_field == "inserted_at"
44 | assert page.sort_direction == "desc"
45 | end
46 |
47 | test "list_categories/0 returns all categories" do
48 | category = insert(:category)
49 | assert FirestormAdmin.list_categories() == [category]
50 | end
51 |
52 | test "get_category!/1 returns the category with given id" do
53 | category = insert(:category)
54 | assert FirestormAdmin.get_category!(category.id) == category
55 | end
56 |
57 | test "create_category/1 with valid data creates a category" do
58 | assert {:ok, %Category{} = category} = FirestormAdmin.create_category(@valid_attrs)
59 | assert category.title == "some title"
60 | assert category.slug == "some-title"
61 | end
62 |
63 | test "create_category/1 with invalid data returns error changeset" do
64 | assert {:error, %Ecto.Changeset{}} = FirestormAdmin.create_category(@invalid_attrs)
65 | end
66 |
67 | test "update_category/2 with valid data updates the category" do
68 | category = insert(:category)
69 | assert {:ok, category} = FirestormAdmin.update_category(category, @update_attrs)
70 | assert %Category{} = category
71 | assert category.title == "some updated title"
72 | assert category.slug == "some-updated-title"
73 | end
74 |
75 | test "update_category/2 with invalid data returns error changeset" do
76 | category = insert(:category)
77 |
78 | assert {:error, %Ecto.Changeset{}} =
79 | FirestormAdmin.update_category(category, @invalid_attrs)
80 |
81 | assert category == FirestormAdmin.get_category!(category.id)
82 | end
83 |
84 | test "delete_category/1 deletes the category" do
85 | category = insert(:category)
86 | assert {:ok, %Category{}} = FirestormAdmin.delete_category(category)
87 | assert_raise Ecto.NoResultsError, fn -> FirestormAdmin.get_category!(category.id) end
88 | end
89 |
90 | test "change_category/1 returns a category changeset" do
91 | category = insert(:category)
92 | assert %Ecto.Changeset{} = FirestormAdmin.change_category(category)
93 | end
94 | end
95 |
96 | describe "threads" do
97 | alias FirestormData.Threads.Thread
98 |
99 | @valid_attrs %{
100 | title: "some title"
101 | }
102 |
103 | @invalid_attrs %{inserted_at: nil, slug: nil, title: nil, updated_at: nil}
104 |
105 | test "paginate_threads/1 returns paginated list of threads" do
106 | for _ <- 1..20 do
107 | insert(:thread)
108 | end
109 |
110 | {:ok, %{threads: threads} = page} = FirestormAdmin.paginate_threads(%{})
111 |
112 | assert length(threads) == 15
113 | assert page.page_number == 1
114 | assert page.page_size == 15
115 | assert page.total_pages == 2
116 | assert page.total_entries == 20
117 | assert page.distance == 5
118 | assert page.sort_field == "inserted_at"
119 | assert page.sort_direction == "desc"
120 | end
121 |
122 | test "list_threads/0 returns all threads" do
123 | thread = insert(:thread)
124 | assert FirestormAdmin.list_threads() == [thread]
125 | end
126 |
127 | test "get_thread!/1 returns the thread with given id" do
128 | thread = insert(:thread)
129 | assert FirestormAdmin.get_thread!(thread.id) == thread
130 | end
131 |
132 | test "create_thread/1 with valid data creates a thread" do
133 | category = insert(:category)
134 |
135 | attrs = %{category_id: category.id, title: "some title"}
136 | assert {:ok, %Thread{} = thread} = FirestormAdmin.create_thread(attrs)
137 | assert thread.title == "some title"
138 | assert thread.slug == "some-title"
139 | end
140 |
141 | test "create_thread/1 with invalid data returns error changeset" do
142 | assert {:error, %Ecto.Changeset{}} = FirestormAdmin.create_thread(@invalid_attrs)
143 | end
144 |
145 | test "update_thread/2 with valid data updates the thread" do
146 | thread = insert(:thread)
147 | assert {:ok, thread} = FirestormAdmin.update_thread(thread, @update_attrs)
148 | assert %Thread{} = thread
149 |
150 | assert thread.title == "some updated title"
151 | assert thread.slug == "some-updated-title"
152 | end
153 |
154 | test "update_thread/2 with invalid data returns error changeset" do
155 | thread = insert(:thread)
156 | assert {:error, %Ecto.Changeset{}} = FirestormAdmin.update_thread(thread, @invalid_attrs)
157 | assert thread == FirestormAdmin.get_thread!(thread.id)
158 | end
159 |
160 | test "delete_thread/1 deletes the thread" do
161 | thread = insert(:thread)
162 | assert {:ok, %Thread{}} = FirestormAdmin.delete_thread(thread)
163 | assert_raise Ecto.NoResultsError, fn -> FirestormAdmin.get_thread!(thread.id) end
164 | end
165 |
166 | test "change_thread/1 returns a thread changeset" do
167 | thread = insert(:thread)
168 | assert %Ecto.Changeset{} = FirestormAdmin.change_thread(thread)
169 | end
170 | end
171 |
172 | describe "posts" do
173 | alias FirestormData.Posts.Post
174 |
175 | @valid_attrs %{
176 | body: "some body",
177 | user_id: "",
178 | thread_id: ""
179 | }
180 | @update_attrs %{
181 | body: "some updated body",
182 | inserted_at: ~N[2011-05-18 15:01:01],
183 | updated_at: ~N[2011-05-18 15:01:01]
184 | }
185 | @invalid_attrs %{body: nil, inserted_at: nil, updated_at: nil}
186 |
187 | test "paginate_posts/1 returns paginated list of posts" do
188 | for _ <- 1..20 do
189 | insert(:post)
190 | end
191 |
192 | {:ok, %{posts: posts} = page} = FirestormAdmin.paginate_posts(%{})
193 |
194 | assert length(posts) == 15
195 | assert page.page_number == 1
196 | assert page.page_size == 15
197 | assert page.total_pages == 2
198 | assert page.total_entries == 20
199 | assert page.distance == 5
200 | assert page.sort_field == "inserted_at"
201 | assert page.sort_direction == "desc"
202 | end
203 |
204 | test "list_posts/0 returns all posts" do
205 | post = insert(:post)
206 | assert FirestormAdmin.list_posts() == [post]
207 | end
208 |
209 | test "get_post!/1 returns the post with given id" do
210 | post = insert(:post)
211 | assert FirestormAdmin.get_post!(post.id) == post
212 | end
213 |
214 | test "create_post/1 with valid data creates a post" do
215 | user = insert(:user)
216 | thread = insert(:thread)
217 |
218 | attrs = %{@valid_attrs | thread_id: thread.id}
219 | attrs = %{attrs | user_id: user.id}
220 |
221 | assert {:ok, %Post{} = post} = FirestormAdmin.create_post(attrs)
222 | assert post.body == "some body"
223 | end
224 |
225 | test "create_post/1 with invalid data returns error changeset" do
226 | assert {:error, %Ecto.Changeset{}} = FirestormAdmin.create_post(@invalid_attrs)
227 | end
228 |
229 | test "update_post/2 with valid data updates the post" do
230 | user = insert(:user)
231 | thread = insert(:thread)
232 | post = insert(:post, user: user, thread: thread)
233 |
234 | assert {:ok, post} = FirestormAdmin.update_post(post, @update_attrs)
235 | assert %Post{} = post
236 | assert post.body == "some updated body"
237 | end
238 |
239 | test "update_post/2 with invalid data returns error changeset" do
240 | post = insert(:post)
241 | assert {:error, %Ecto.Changeset{}} = FirestormAdmin.update_post(post, @invalid_attrs)
242 | assert post == FirestormAdmin.get_post!(post.id)
243 | end
244 |
245 | test "delete_post/1 deletes the post" do
246 | post = insert(:post)
247 | assert {:ok, %Post{}} = FirestormAdmin.delete_post(post)
248 | assert_raise Ecto.NoResultsError, fn -> FirestormAdmin.get_post!(post.id) end
249 | end
250 |
251 | test "change_post/1 returns a post changeset" do
252 | post = insert(:post)
253 | assert %Ecto.Changeset{} = FirestormAdmin.change_post(post)
254 | end
255 | end
256 |
257 | describe "users" do
258 | alias FirestormData.Users.User
259 |
260 | @valid_attrs %{email: "some email", name: "some name", username: "some username"}
261 | @update_attrs %{
262 | email: "some updated email",
263 | name: "some updated name",
264 | username: "some updated username"
265 | }
266 | @invalid_attrs %{email: nil, name: nil, username: nil}
267 |
268 | test "paginate_users/1 returns paginated list of users" do
269 | for _ <- 1..20 do
270 | insert(:user)
271 | end
272 |
273 | {:ok, %{users: users} = page} = FirestormAdmin.paginate_users(%{})
274 |
275 | assert length(users) == 15
276 | assert page.page_number == 1
277 | assert page.page_size == 15
278 | assert page.total_pages == 2
279 | assert page.total_entries == 20
280 | assert page.distance == 5
281 | assert page.sort_field == "inserted_at"
282 | assert page.sort_direction == "desc"
283 | end
284 |
285 | test "list_users/0 returns all users" do
286 | user = insert(:user)
287 | assert FirestormAdmin.list_users() == [user]
288 | end
289 |
290 | test "get_user!/1 returns the user with given id" do
291 | user = insert(:user)
292 | assert FirestormAdmin.get_user!(user.id) == user
293 | end
294 |
295 | test "create_user/1 with valid data creates a user" do
296 | assert {:ok, %User{} = user} = FirestormAdmin.create_user(@valid_attrs)
297 | assert user.email == "some email"
298 | assert user.name == "some name"
299 | assert user.username == "some username"
300 | end
301 |
302 | test "create_user/1 with invalid data returns error changeset" do
303 | assert {:error, %Ecto.Changeset{}} = FirestormAdmin.create_user(@invalid_attrs)
304 | end
305 |
306 | test "update_user/2 with valid data updates the user" do
307 | user = insert(:user)
308 | assert {:ok, user} = FirestormAdmin.update_user(user, @update_attrs)
309 | assert %User{} = user
310 | assert user.email == "some updated email"
311 | assert user.name == "some updated name"
312 | assert user.username == "some updated username"
313 | end
314 |
315 | test "update_user/2 with invalid data returns error changeset" do
316 | user = insert(:user)
317 | assert {:error, %Ecto.Changeset{}} = FirestormAdmin.update_user(user, @invalid_attrs)
318 | assert user == FirestormAdmin.get_user!(user.id)
319 | end
320 |
321 | test "delete_user/1 deletes the user" do
322 | user = insert(:user)
323 | assert {:ok, %User{}} = FirestormAdmin.delete_user(user)
324 | assert_raise Ecto.NoResultsError, fn -> FirestormAdmin.get_user!(user.id) end
325 | end
326 |
327 | test "change_user/1 returns a user changeset" do
328 | user = insert(:user)
329 | assert %Ecto.Changeset{} = FirestormAdmin.change_user(user)
330 | end
331 | end
332 | end
333 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/controllers/category_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.CategoryControllerTest do
2 | use FirestormWeb.ConnCase
3 | import Firestorm.Factory
4 |
5 | alias FirestormWeb.Router.Helpers, as: Routes
6 |
7 | @create_attrs %{
8 | inserted_at: ~N[2010-04-17 14:00:00],
9 | slug: "some slug",
10 | title: "some title",
11 | updated_at: ~N[2010-04-17 14:00:00]
12 | }
13 | @update_attrs %{
14 | inserted_at: ~N[2011-05-18 15:01:01],
15 | slug: "some updated slug",
16 | title: "some updated title",
17 | updated_at: ~N[2011-05-18 15:01:01]
18 | }
19 | @invalid_attrs %{inserted_at: nil, slug: nil, title: nil, updated_at: nil}
20 |
21 | @moduletag :logged_in_admin
22 |
23 | describe "index" do
24 | test "lists all categories", %{conn: conn} do
25 | conn = get(conn, Routes.category_path(conn, :index))
26 | assert html_response(conn, 200) =~ "Categories"
27 | end
28 | end
29 |
30 | describe "new category" do
31 | test "renders form", %{conn: conn} do
32 | conn = get(conn, Routes.category_path(conn, :new))
33 | assert html_response(conn, 200) =~ "New Category"
34 | end
35 | end
36 |
37 | describe "create category" do
38 | test "redirects to show when data is valid", %{conn: conn} do
39 | conn = post conn, Routes.category_path(conn, :create), category: @create_attrs
40 |
41 | assert %{id: id} = redirected_params(conn)
42 | assert redirected_to(conn) == Routes.category_path(conn, :show, id)
43 |
44 | conn = get(conn, Routes.category_path(conn, :show, id))
45 | assert html_response(conn, 200) =~ "Category Details"
46 | end
47 |
48 | test "renders errors when data is invalid", %{conn: conn} do
49 | conn = post conn, Routes.category_path(conn, :create), category: @invalid_attrs
50 | assert html_response(conn, 200) =~ "New Category"
51 | end
52 | end
53 |
54 | describe "edit category" do
55 | setup [:create_category]
56 |
57 | test "renders form for editing chosen category", %{conn: conn, category: category} do
58 | conn = get(conn, Routes.category_path(conn, :edit, category))
59 | assert html_response(conn, 200) =~ "Edit Category"
60 | end
61 | end
62 |
63 | describe "update category" do
64 | setup [:create_category]
65 |
66 | test "redirects when data is valid", %{conn: conn, category: category} do
67 | conn = put conn, Routes.category_path(conn, :update, category), category: @update_attrs
68 | assert redirected_to(conn) == Routes.category_path(conn, :show, category)
69 |
70 | conn = get(conn, Routes.category_path(conn, :show, category))
71 | assert html_response(conn, 200) =~ "some-updated-title"
72 | end
73 |
74 | test "renders errors when data is invalid", %{conn: conn, category: category} do
75 | conn = put conn, Routes.category_path(conn, :update, category), category: @invalid_attrs
76 | assert html_response(conn, 200) =~ "Edit Category"
77 | end
78 | end
79 |
80 | describe "delete category" do
81 | setup [:create_category]
82 |
83 | test "deletes chosen category", %{conn: conn, category: category} do
84 | conn = delete(conn, Routes.category_path(conn, :delete, category))
85 | assert redirected_to(conn) == Routes.category_path(conn, :index)
86 |
87 | assert_error_sent 404, fn ->
88 | get(conn, Routes.category_path(conn, :show, category))
89 | end
90 | end
91 | end
92 |
93 | defp create_category(_) do
94 | category = insert(:category)
95 | {:ok, category: category}
96 | end
97 | end
98 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.PageControllerTest do
2 | use FirestormWeb.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 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/controllers/post_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.PostControllerTest do
2 | use FirestormWeb.ConnCase
3 |
4 | import Firestorm.Factory
5 |
6 | alias FirestormWeb.Router.Helpers, as: Routes
7 |
8 | @create_attrs %{
9 | body: "some body",
10 | user_id: "",
11 | thread_id: ""
12 | }
13 | @update_attrs %{
14 | body: "some updated body",
15 | user_id: "",
16 | thread_id: ""
17 | }
18 | @invalid_attrs %{body: nil, inserted_at: nil, updated_at: nil}
19 |
20 | @moduletag :logged_in_admin
21 |
22 | describe "index" do
23 | test "lists all posts", %{conn: conn} do
24 | conn = get(conn, Routes.post_path(conn, :index))
25 | assert html_response(conn, 200) =~ "Posts"
26 | end
27 | end
28 |
29 | describe "new post" do
30 | test "renders form", %{conn: conn} do
31 | conn = get(conn, Routes.post_path(conn, :new))
32 | assert html_response(conn, 200) =~ "New Post"
33 | end
34 | end
35 |
36 | describe "create post" do
37 | test "redirects to show when data is valid", %{conn: conn} do
38 | user = insert(:user)
39 | thread = insert(:thread)
40 |
41 | attrs = %{@create_attrs | thread_id: thread.id}
42 | attrs = %{attrs | user_id: user.id}
43 |
44 | conn = post conn, Routes.post_path(conn, :create), post: attrs
45 |
46 | assert %{id: id} = redirected_params(conn)
47 | assert redirected_to(conn) == Routes.post_path(conn, :show, id)
48 |
49 | conn = get(conn, Routes.post_path(conn, :show, id))
50 | assert html_response(conn, 200) =~ "Post Details"
51 | end
52 |
53 | test "renders errors when data is invalid", %{conn: conn} do
54 | conn = post conn, Routes.post_path(conn, :create), post: @invalid_attrs
55 | assert html_response(conn, 200) =~ "New Post"
56 | end
57 | end
58 |
59 | describe "edit post" do
60 | setup [:create_post]
61 |
62 | test "renders form for editing chosen post", %{conn: conn, post: post} do
63 | conn = get(conn, Routes.post_path(conn, :edit, post))
64 | assert html_response(conn, 200) =~ "Edit Post"
65 | end
66 | end
67 |
68 | describe "update post" do
69 | setup [:create_post]
70 |
71 | test "redirects when data is valid", %{conn: conn, post: post} do
72 | user = insert(:user)
73 | thread = insert(:thread)
74 |
75 | attrs = %{@update_attrs | thread_id: thread.id}
76 | attrs = %{attrs | user_id: user.id}
77 |
78 | conn = put conn, Routes.post_path(conn, :update, post), post: attrs
79 | assert redirected_to(conn) == Routes.post_path(conn, :show, post)
80 |
81 | conn = get(conn, Routes.post_path(conn, :show, post))
82 | assert html_response(conn, 200) =~ "some updated body"
83 | end
84 |
85 | test "renders errors when data is invalid", %{conn: conn, post: post} do
86 | conn = put conn, Routes.post_path(conn, :update, post), post: @invalid_attrs
87 | assert html_response(conn, 200) =~ "Edit Post"
88 | end
89 | end
90 |
91 | describe "delete post" do
92 | setup [:create_post]
93 |
94 | test "deletes chosen post", %{conn: conn, post: post} do
95 | conn = delete(conn, Routes.post_path(conn, :delete, post))
96 | assert redirected_to(conn) == Routes.post_path(conn, :index)
97 |
98 | assert_error_sent 404, fn ->
99 | get(conn, Routes.post_path(conn, :show, post))
100 | end
101 | end
102 | end
103 |
104 | defp create_post(_) do
105 | post = insert(:post)
106 | {:ok, post: post}
107 | end
108 | end
109 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/controllers/thread_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.ThreadControllerTest do
2 | use FirestormWeb.ConnCase
3 | import Firestorm.Factory
4 |
5 | alias FirestormWeb.Router.Helpers, as: Routes
6 |
7 | @create_attrs %{
8 | inserted_at: ~N[2010-04-17 14:00:00],
9 | slug: "some slug",
10 | title: "some title",
11 | category_id: "",
12 | updated_at: ~N[2010-04-17 14:00:00]
13 | }
14 |
15 | @invalid_attrs %{inserted_at: nil, slug: nil, title: nil, updated_at: nil}
16 |
17 | @moduletag :logged_in_admin
18 |
19 | describe "index" do
20 | test "lists all threads", %{conn: conn} do
21 | conn = get(conn, Routes.thread_path(conn, :index))
22 | assert html_response(conn, 200) =~ "Threads"
23 | end
24 | end
25 |
26 | describe "new thread" do
27 | test "renders form", %{conn: conn} do
28 | conn = get(conn, Routes.thread_path(conn, :new))
29 | assert html_response(conn, 200) =~ "New Thread"
30 | end
31 | end
32 |
33 | describe "create thread" do
34 | test "redirects to show when data is valid", %{conn: conn} do
35 | category = insert(:category)
36 |
37 | attrs = %{@create_attrs | category_id: category.id}
38 |
39 | conn = post conn, Routes.thread_path(conn, :create), thread: attrs
40 |
41 | assert %{id: id} = redirected_params(conn)
42 | assert redirected_to(conn) == Routes.thread_path(conn, :show, id)
43 |
44 | conn = get(conn, Routes.thread_path(conn, :show, id))
45 | assert html_response(conn, 200) =~ "Thread Details"
46 | end
47 |
48 | test "renders errors when data is invalid", %{conn: conn} do
49 | conn = post conn, Routes.thread_path(conn, :create), thread: @invalid_attrs
50 | assert html_response(conn, 200) =~ "New Thread"
51 | end
52 | end
53 |
54 | describe "edit thread" do
55 | setup [:create_thread]
56 |
57 | test "renders form for editing chosen thread", %{conn: conn, thread: thread} do
58 | conn = get(conn, Routes.thread_path(conn, :edit, thread))
59 | assert html_response(conn, 200) =~ "Edit Thread"
60 | end
61 | end
62 |
63 | describe "update thread" do
64 | setup [:create_thread]
65 |
66 | test "redirects when data is valid", %{conn: conn, thread: thread} do
67 | category = insert(:category)
68 |
69 | attrs = %{@create_attrs | category_id: category.id}
70 | attrs = %{attrs | title: "some updated title"}
71 |
72 | conn = put conn, Routes.thread_path(conn, :update, thread), thread: attrs
73 | assert redirected_to(conn) == Routes.thread_path(conn, :show, thread)
74 |
75 | conn = get(conn, Routes.thread_path(conn, :show, thread))
76 | assert html_response(conn, 200) =~ "some-updated-title"
77 | end
78 |
79 | test "renders errors when data is invalid", %{conn: conn, thread: thread} do
80 | conn = put conn, Routes.thread_path(conn, :update, thread), thread: @invalid_attrs
81 | assert html_response(conn, 200) =~ "Edit Thread"
82 | end
83 | end
84 |
85 | describe "delete thread" do
86 | setup [:create_thread]
87 |
88 | test "deletes chosen thread", %{conn: conn, thread: thread} do
89 | conn = delete(conn, Routes.thread_path(conn, :delete, thread))
90 | assert redirected_to(conn) == Routes.thread_path(conn, :index)
91 |
92 | assert_error_sent 404, fn ->
93 | get(conn, Routes.thread_path(conn, :show, thread))
94 | end
95 | end
96 | end
97 |
98 | defp create_thread(_) do
99 | thread = insert(:thread)
100 | {:ok, thread: thread}
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/controllers/user_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.UserControllerTest do
2 | use FirestormWeb.ConnCase
3 |
4 | alias Firestorm.FirestormAdmin
5 |
6 | import Firestorm.Factory
7 |
8 | alias FirestormWeb.Router.Helpers, as: Routes
9 |
10 | @create_attrs %{email: "some email", name: "some name", username: "some username"}
11 | @update_attrs %{
12 | email: "some updated email",
13 | name: "some updated name",
14 | username: "some updated username"
15 | }
16 | @invalid_attrs %{email: nil, name: nil, username: nil}
17 |
18 | @moduletag :logged_in_admin
19 |
20 | describe "index" do
21 | test "lists all users", %{conn: conn} do
22 | conn = get(conn, Routes.user_path(conn, :index))
23 | assert html_response(conn, 200) =~ "Users"
24 | end
25 | end
26 |
27 | describe "new user" do
28 | test "renders form", %{conn: conn} do
29 | conn = get(conn, Routes.user_path(conn, :new))
30 | assert html_response(conn, 200) =~ "New User"
31 | end
32 | end
33 |
34 | describe "create user" do
35 | test "redirects to show when data is valid", %{conn: conn} do
36 | conn = post conn, Routes.user_path(conn, :create), user: @create_attrs
37 |
38 | assert %{id: id} = redirected_params(conn)
39 | assert redirected_to(conn) == Routes.user_path(conn, :show, id)
40 |
41 | conn = get(conn, Routes.user_path(conn, :show, id))
42 | assert html_response(conn, 200) =~ "User Details"
43 | end
44 |
45 | test "renders errors when data is invalid", %{conn: conn} do
46 | conn = post conn, Routes.user_path(conn, :create), user: @invalid_attrs
47 | assert html_response(conn, 200) =~ "New User"
48 | end
49 | end
50 |
51 | describe "edit user" do
52 | setup [:create_user]
53 |
54 | test "renders form for editing chosen user", %{conn: conn, user: user} do
55 | conn = get(conn, Routes.user_path(conn, :edit, user))
56 | assert html_response(conn, 200) =~ "Edit User"
57 | end
58 | end
59 |
60 | describe "update user" do
61 | setup [:create_user]
62 |
63 | test "redirects when data is valid", %{conn: conn, user: user} do
64 | conn = put conn, Routes.user_path(conn, :update, user), user: @update_attrs
65 | assert redirected_to(conn) == Routes.user_path(conn, :show, user)
66 |
67 | conn = get(conn, Routes.user_path(conn, :show, user))
68 | assert html_response(conn, 200) =~ "some updated email"
69 | end
70 |
71 | test "renders errors when data is invalid", %{conn: conn, user: user} do
72 | conn = put conn, Routes.user_path(conn, :update, user), user: @invalid_attrs
73 | assert html_response(conn, 200) =~ "Edit User"
74 | end
75 | end
76 |
77 | describe "delete user" do
78 | setup [:create_user]
79 |
80 | test "deletes chosen user", %{conn: conn, user: user} do
81 | conn = delete(conn, Routes.user_path(conn, :delete, user))
82 | assert redirected_to(conn) == Routes.user_path(conn, :index)
83 |
84 | assert_error_sent 404, fn ->
85 | get(conn, Routes.user_path(conn, :show, user))
86 | end
87 | end
88 | end
89 |
90 | defp create_user(_) do
91 | user = insert(:user)
92 | {:ok, user: user}
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.ErrorViewTest do
2 | use FirestormWeb.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(FirestormWeb.ErrorView, "404.html", []) == "Not Found"
9 | end
10 |
11 | test "renders 500.html" do
12 | assert render_to_string(FirestormWeb.ErrorView, "500.html", []) == "Internal Server Error"
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.LayoutViewTest do
2 | use FirestormWeb.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/apps/firestorm/test/firestorm_web/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.PageViewTest do
2 | use FirestormWeb.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/apps/firestorm/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.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 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | # The default endpoint for testing
24 | @endpoint FirestormWeb.Endpoint
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(FirestormData.Repo, ownership_timeout: 30000)
30 |
31 | unless tags[:async] do
32 | Ecto.Adapters.SQL.Sandbox.mode(FirestormData.Repo, {:shared, self()})
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/apps/firestorm/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common datastructures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 | import Firestorm.Factory
18 | import Plug.Test
19 | import Plug.Conn
20 |
21 | using do
22 | quote do
23 | # Import conveniences for testing with connections
24 | use Phoenix.ConnTest
25 | import FirestormWeb.Router.Helpers
26 |
27 | # The default endpoint for testing
28 | @endpoint FirestormWeb.Endpoint
29 | end
30 | end
31 |
32 | setup tags do
33 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(FirestormData.Repo, ownership_timeout: 30000)
34 |
35 | unless tags[:async] do
36 | Ecto.Adapters.SQL.Sandbox.mode(FirestormData.Repo, {:shared, self()})
37 | end
38 |
39 | conn = Phoenix.ConnTest.build_conn()
40 |
41 | %{user: user, conn: conn} =
42 | if tags[:logged_in_admin] do
43 | user = insert(:user, admin: true)
44 |
45 | conn =
46 | conn
47 | |> init_test_session(%{})
48 | |> put_session(:current_user_id, user.id)
49 |
50 | %{user: user, conn: conn}
51 | else
52 | %{user: nil, conn: conn}
53 | end
54 |
55 | {:ok, conn: conn, user: user}
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/apps/firestorm/test/support/factories.ex:
--------------------------------------------------------------------------------
1 | defmodule Firestorm.Factory do
2 | use ExMachina.Ecto, repo: FirestormData.Repo
3 |
4 | alias FirestormData.{
5 | Users.User,
6 | Categories.Category,
7 | Threads.Thread,
8 | Posts.Post
9 | }
10 |
11 | def user_factory do
12 | %User{
13 | name: Faker.Name.name(),
14 | username: Faker.Name.name(),
15 | admin: false,
16 | email: sequence(:email, &"email-#{&1}@example.com")
17 | }
18 | end
19 |
20 | def category_factory do
21 | %Category{
22 | title: Faker.Lorem.word()
23 | }
24 | end
25 |
26 | def thread_factory do
27 | %Thread{
28 | title: Faker.Lorem.word(),
29 | category: build(:category)
30 | }
31 | end
32 |
33 | def post_factory do
34 | %Post{
35 | body: Faker.Lorem.sentence()
36 | # thread: build(:thread),
37 | # user: build(:user)
38 | }
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/apps/firestorm/test/support/subscription_case.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormWeb.SubscriptionCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | subscription tests.
5 | """
6 |
7 | use ExUnit.CaseTemplate
8 |
9 | using do
10 | quote do
11 | use FirestormWeb.ChannelCase
12 | use Absinthe.Phoenix.SubscriptionTest, schema: FirestormWeb.Schema
13 | alias FirestormData.Users
14 |
15 | setup do
16 | {:ok, user} =
17 | Users.create_user(%{
18 | email: "josh@smoothterminal.com",
19 | name: "Josh Adams",
20 | username: "knewter"
21 | })
22 |
23 | params = %{
24 | "token" => sign_auth_token(user.id)
25 | }
26 |
27 | {:ok, socket} = Phoenix.ChannelTest.connect(FirestormWeb.UserSocket, params)
28 | {:ok, socket} = Absinthe.Phoenix.SubscriptionTest.join_absinthe(socket)
29 |
30 | {:ok, socket: socket, user: user}
31 | end
32 |
33 | defp sign_auth_token(id), do: Phoenix.Token.sign(FirestormWeb.Endpoint, "user auth", id)
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/apps/firestorm/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/apps/firestorm_data/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/apps/firestorm_data/.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 | firestorm_data-*.tar
24 |
25 |
--------------------------------------------------------------------------------
/apps/firestorm_data/README.md:
--------------------------------------------------------------------------------
1 | # FirestormData
2 |
3 | **TODO: Add description**
4 |
5 | ## Installation
6 |
7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed
8 | by adding `firestorm_data` to your list of dependencies in `mix.exs`:
9 |
10 | ```elixir
11 | def deps do
12 | [
13 | {:firestorm_data, "~> 0.1.0"}
14 | ]
15 | end
16 | ```
17 |
18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
20 | be found at [https://hexdocs.pm/firestorm_data](https://hexdocs.pm/firestorm_data).
21 |
22 |
--------------------------------------------------------------------------------
/apps/firestorm_data/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | config :firestorm_data,
6 | ecto_repos: [FirestormData.Repo]
7 |
8 | config :firestorm_data, FirestormData.Repo, migration_timestamps: [type: :naive_datetime_usec]
9 |
10 | import_config "#{Mix.env()}.exs"
11 |
--------------------------------------------------------------------------------
/apps/firestorm_data/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | config :firestorm_data, FirestormData.Repo,
4 | database: "firestorm_data_repo_dev",
5 | hostname: "localhost"
6 |
--------------------------------------------------------------------------------
/apps/firestorm_data/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | config :firestorm_data, FirestormData.Repo,
4 | adapter: Ecto.Adapters.Postgres
5 |
--------------------------------------------------------------------------------
/apps/firestorm_data/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | config :firestorm_data, FirestormData.Repo,
4 | adapter: Ecto.Adapters.Postgres,
5 | username: System.get_env("DATABASE_POSTGRESQL_USERNAME"),
6 | password: System.get_env("DATABASE_POSTGRESQL_PASSWORD"),
7 | database: "firestorm_data_repo_test",
8 | hostname: "localhost",
9 | pool: Ecto.Adapters.SQL.Sandbox,
10 | ownership_timeout: 30_000
11 |
12 | # Print only warnings and errors during test
13 | config :logger, level: :warn
14 |
15 | config :bcrypt_elixir, log_rounds: 4
16 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData do
2 | @moduledoc """
3 | Documentation for FirestormData.
4 | """
5 |
6 | @doc """
7 | Hello world.
8 |
9 | ## Examples
10 |
11 | iex> FirestormData.hello()
12 | :world
13 |
14 | """
15 | def hello do
16 | :world
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/application.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.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 | FirestormData.Repo
12 | ]
13 |
14 | # See https://hexdocs.pm/elixir/Supervisor.html
15 | # for other strategies and supported options
16 | opts = [strategy: :one_for_one, name: FirestormData.Supervisor]
17 | Supervisor.start_link(children, opts)
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/categories/categories.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Categories do
2 | @moduledoc """
3 | Threads exist within categories.
4 | """
5 |
6 | alias FirestormData.{
7 | Repo,
8 | Categories.Category
9 | }
10 |
11 | @type index_params :: %{
12 | optional(:pagination) => %{
13 | optional(:per_page) => non_neg_integer(),
14 | optional(:page) => non_neg_integer()
15 | }
16 | }
17 | @type index :: %{
18 | entries: [Category.t()],
19 | page: non_neg_integer(),
20 | per_page: non_neg_integer(),
21 | total_pages: non_neg_integer(),
22 | total_entries: non_neg_integer()
23 | }
24 |
25 | @doc """
26 | List categories
27 | """
28 | @spec list_categories(index_params()) :: index()
29 | def list_categories(options) do
30 | pagination = Map.get(options, :pagination, %{page: 1, per_page: 20})
31 | page = Map.get(pagination, :page, 1)
32 | per_page = Map.get(pagination, :per_page, 20)
33 |
34 | Repo.paginate(Category, page: page, page_size: per_page)
35 | end
36 |
37 | @doc """
38 | Finds a category by attributes
39 |
40 | ## Examples
41 |
42 | iex> find_category(%{title: "Elixir"})
43 | {:ok, %Category{}}
44 |
45 | iex> find_category(%{field: bad_value})
46 | {:error, :no_such_category}
47 |
48 | """
49 | @spec find_category(map()) :: {:ok, Category.t()} | {:error, :no_such_category}
50 | def find_category(attrs \\ %{}) do
51 | Category
52 | |> Repo.get_by(attrs)
53 | |> case do
54 | nil -> {:error, :no_such_category}
55 | category -> {:ok, category}
56 | end
57 | end
58 |
59 | @doc """
60 | Creates a category.
61 |
62 | ## Examples
63 |
64 | iex> create_category(%{title: "Elixir"})
65 | {:ok, %Category{}}
66 |
67 | iex> create_category(%{field: bad_value})
68 | {:error, %Ecto.Changeset{}}
69 |
70 | """
71 | @spec create_category(map()) :: {:ok, Category.t()} | {:error, Ecto.Changeset.t()}
72 | def create_category(attrs \\ %{}) do
73 | %Category{}
74 | |> Category.changeset(attrs)
75 | |> Repo.insert()
76 | end
77 |
78 | @doc """
79 | Gets a single category by id.
80 |
81 | ## Examples
82 |
83 | iex> get_category("123")
84 | {:ok, %Category{}}
85 |
86 | iex> get_category!("456")
87 | {:error, :no_such_category}
88 |
89 | """
90 | @spec get_category(String.t()) :: {:ok, Category.t()} | {:error, :no_such_category}
91 | def get_category(id) do
92 | case Repo.get(Category, id) do
93 | nil -> {:error, :no_such_category}
94 | category -> {:ok, category}
95 | end
96 | end
97 |
98 | @doc """
99 | Updates a category.
100 |
101 | ## Examples
102 |
103 | iex> update_category(category, %{field: new_value})
104 | {:ok, %Category{}}
105 |
106 | iex> update_category(category, %{field: bad_value})
107 | {:error, %Ecto.Changeset{}}
108 |
109 | """
110 | @spec update_category(Category.t(), map()) :: {:ok, Category.t()} | {:error, Ecto.Changeset.t()}
111 | def update_category(%Category{} = category, attrs) do
112 | category
113 | |> Category.changeset(attrs)
114 | |> Repo.update()
115 | end
116 |
117 | @doc """
118 | Deletes a Category.
119 |
120 | ## Examples
121 |
122 | iex> delete_category(category)
123 | {:ok, %Category{}}
124 |
125 | iex> delete_category(category)
126 | {:error, %Ecto.Changeset{}}
127 |
128 | """
129 | @spec delete_category(Category.t()) :: {:ok, Category.t()} | {:error, Ecto.Changeset.t()}
130 | def delete_category(%Category{} = category) do
131 | Repo.delete(category)
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/categories/category.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Categories.Category.TitleSlug do
2 | use EctoAutoslugField.Slug, from: :title, to: :slug
3 | end
4 |
5 | defmodule FirestormData.Categories.Category do
6 | @moduledoc """
7 | Schema for forum categories.
8 | """
9 |
10 | use FirestormData.Data, :model
11 |
12 | alias FirestormData.{
13 | Categories.Category,
14 | Threads.Thread
15 | }
16 |
17 | alias FirestormData.Categories.Category.TitleSlug
18 |
19 | @type t :: %Category{
20 | id: String.t(),
21 | title: String.t(),
22 | slug: String.t(),
23 | threads: [Thread.t()] | %Ecto.Association.NotLoaded{},
24 | inserted_at: DateTime.t(),
25 | updated_at: DateTime.t()
26 | }
27 | schema "firestorm_categories_categories" do
28 | field(:title, :string)
29 | field(:slug, TitleSlug.Type)
30 | has_many(:threads, Thread)
31 |
32 | timestamps()
33 | end
34 |
35 | def new_changeset(%__MODULE__{} = category, attrs \\ %{}) do
36 | category
37 | |> cast(attrs, [:title])
38 | end
39 |
40 | def changeset(%__MODULE__{} = category, attrs \\ %{}) do
41 | category
42 | |> cast(attrs, [:title])
43 | |> TitleSlug.maybe_generate_slug()
44 | |> TitleSlug.unique_constraint()
45 | |> validate_required([:title])
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/categories/category_index.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Categories.CategoryIndex do
2 | @moduledoc false
3 |
4 | @type params :: %{
5 | optional(:pagination) => %{
6 | optional(:per_page) => non_neg_integer(),
7 | optional(:page) => non_neg_integer()
8 | }
9 | }
10 | end
11 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/data.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Data do
2 | def model do
3 | quote do
4 | use Ecto.Schema
5 | import Ecto
6 | import Ecto.Changeset
7 | import Ecto.Query
8 |
9 | # We use uuid primary and foreign keys, and we want microseconds in our timestamps
10 | @primary_key {:id, :binary_id, autogenerate: true}
11 | @foreign_key_type :binary_id
12 | @timestamps_opts [type: :utc_datetime_usec]
13 | end
14 | end
15 |
16 | @doc """
17 | When used, dispatch to the appropriate controller/view/etc.
18 | """
19 | defmacro __using__(which) when is_atom(which) do
20 | apply(__MODULE__, which, [])
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/posts/post.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Posts.Post do
2 | @moduledoc """
3 | Schema for forum posts.
4 | """
5 |
6 | use FirestormData.Data, :model
7 |
8 | alias FirestormData.{
9 | Threads.Thread,
10 | Posts.Post,
11 | Users.User
12 | }
13 |
14 | @type t :: %Post{
15 | id: String.t(),
16 | body: String.t(),
17 | thread: Thread.t() | %Ecto.Association.NotLoaded{},
18 | user: User.t() | %Ecto.Association.NotLoaded{},
19 | inserted_at: DateTime.t(),
20 | updated_at: DateTime.t()
21 | }
22 | schema "firestorm_posts_posts" do
23 | field(:body, :string)
24 | belongs_to(:thread, Thread)
25 | belongs_to(:user, User)
26 |
27 | timestamps()
28 | end
29 |
30 | def changeset(%__MODULE__{} = post, attrs \\ %{}) do
31 | post
32 | |> cast(attrs, [:body, :thread_id, :user_id])
33 | |> validate_required([:body, :thread_id, :user_id])
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/posts/posts.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Posts do
2 | @moduledoc """
3 | Posts exist on Threads
4 | """
5 |
6 | import Ecto.Query
7 |
8 | alias FirestormData.{
9 | Repo,
10 | Posts.Post,
11 | Threads.Thread,
12 | Users.User
13 | }
14 |
15 | @doc """
16 | Creates a post within a thread.
17 |
18 | ## Examples
19 |
20 | iex> create_post(thread, user, %{body: "don't you think?"})
21 | {:ok, %Post{}}
22 |
23 | iex> create_post(thread, user, %{body: nil_value})
24 | {:error, %Ecto.Changeset{}}
25 |
26 | """
27 | def create_post(%Thread{} = thread, %User{} = user, attrs) do
28 | attrs =
29 | attrs
30 | |> Map.put(:thread_id, thread.id)
31 | |> Map.put(:user_id, user.id)
32 |
33 | %Post{}
34 | |> Post.changeset(attrs)
35 | |> Repo.insert()
36 | end
37 |
38 | def list_posts(%Thread{id: thread_id}) do
39 | Post
40 | |> where([p], p.thread_id == ^thread_id)
41 | |> order_by([p], desc: p.inserted_at)
42 | |> Repo.all()
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo do
2 | use Ecto.Repo, otp_app: :firestorm_data, adapter: Ecto.Adapters.Postgres
3 | use Scrivener, page_size: 20
4 |
5 | def init(_type, config) do
6 | {:ok, Keyword.put(config, :url, System.get_env("DATABASE_URL"))}
7 | end
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/threads/thread.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Threads.Thread.TitleSlug do
2 | use EctoAutoslugField.Slug, from: :title, to: :slug
3 | end
4 |
5 | defmodule FirestormData.Threads.Thread do
6 | @moduledoc """
7 | Schema for forum threads.
8 | """
9 |
10 | use FirestormData.Data, :model
11 |
12 | alias FirestormData.{
13 | Posts.Post,
14 | Threads.Thread,
15 | Categories.Category
16 | }
17 |
18 | alias FirestormData.Threads.Thread.TitleSlug
19 |
20 | @type t :: %Thread{
21 | id: String.t(),
22 | title: String.t(),
23 | slug: String.t(),
24 | category: Category.t() | %Ecto.Association.NotLoaded{},
25 | posts: [Post.t()] | %Ecto.Association.NotLoaded{},
26 | inserted_at: DateTime.t(),
27 | updated_at: DateTime.t()
28 | }
29 | schema "firestorm_threads_threads" do
30 | field(:title, :string)
31 | field(:slug, TitleSlug.Type)
32 | belongs_to(:category, Category)
33 | has_many(:posts, Post)
34 |
35 | timestamps()
36 | end
37 |
38 | def changeset(%__MODULE__{} = thread, attrs \\ %{}) do
39 | thread
40 | |> cast(attrs, [:title, :category_id])
41 | |> TitleSlug.maybe_generate_slug()
42 | |> TitleSlug.unique_constraint()
43 | |> validate_required([:title, :category_id])
44 | end
45 |
46 | def new_changeset(%{thread: thread_attrs, post: post_attrs}) do
47 | post_changeset =
48 | %Post{}
49 | |> cast(post_attrs, [:body, :user_id])
50 | |> validate_required([:body, :user_id])
51 |
52 | %__MODULE__{}
53 | |> changeset(thread_attrs)
54 | |> put_assoc(:posts, [post_changeset])
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/threads/threads.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Threads do
2 | @moduledoc """
3 | A Thread has a series of posts within it.
4 | """
5 |
6 | alias FirestormData.{
7 | Repo,
8 | Categories.Category,
9 | Threads.Thread
10 | }
11 |
12 | import Ecto.Query
13 |
14 | @doc """
15 | Gets a thread within a category.
16 |
17 | ## Examples
18 |
19 | iex> get_thread(category, "123")
20 | {:ok, %Thread{}}
21 |
22 | iex> get_thread(category, "456")
23 | {:error, :no_such_thread}
24 |
25 | """
26 | @spec get_thread(Category.t(), String.t()) :: {:ok, Thread.t()} | {:error, :no_such_thread}
27 | def get_thread(%Category{id: category_id}, id) do
28 | case Repo.get_by(Thread, id: id, category_id: category_id) do
29 | nil -> {:error, :no_such_thread}
30 | thread -> {:ok, thread}
31 | end
32 | end
33 |
34 | @doc """
35 | Gets a thread by id
36 |
37 | ## Examples
38 |
39 | iex> get_thread("123")
40 | {:ok, %Thread{}}
41 |
42 | iex> get_thread("nope")
43 | {:error, :no_such_thread}
44 |
45 | """
46 | @spec get_thread(String.t()) :: {:ok, Thread.t()} | {:error, :no_such_thread}
47 | def get_thread(id) do
48 | case Repo.get_by(Thread, id: id) do
49 | nil -> {:error, :no_such_thread}
50 | thread -> {:ok, thread}
51 | end
52 | end
53 |
54 | @doc """
55 | Creates a thread.
56 |
57 | ## Examples
58 |
59 | iex> create_thread(category, user, %{title: "OTP is cool", body: "First Post"})
60 | {:ok, %Thread{}}
61 |
62 | iex> create_thread(category, user, %{title: nil})
63 | {:error, %Ecto.Changeset{}}
64 |
65 | """
66 | def create_thread(category, user, attrs \\ %{}) do
67 | post_attrs =
68 | attrs
69 | |> Map.take([:body])
70 | |> Map.put(:user_id, user.id)
71 |
72 | thread_attrs =
73 | attrs
74 | |> Map.take([:title])
75 | |> Map.put(:category_id, category.id)
76 |
77 | %{thread: thread_attrs, post: post_attrs}
78 | |> Thread.new_changeset()
79 | |> Repo.insert()
80 | end
81 |
82 | @doc """
83 | Updates a thread.
84 |
85 | ## Examples
86 |
87 | iex> update_thread(thread, %{title: "new title"})
88 | {:ok, %Thread{}}
89 |
90 | iex> update_thread(thread, %{title: nil})
91 | {:error, %Ecto.Changeset{}}
92 |
93 | """
94 | @spec update_thread(Thread.t(), map()) :: {:ok, Thread.t()} | {:error, Ecto.Changeset.t()}
95 | def update_thread(%Thread{} = thread, attrs) do
96 | thread
97 | |> Thread.changeset(attrs)
98 | |> Repo.update()
99 | end
100 |
101 | @doc """
102 | Deletes a Thread.
103 |
104 | ## Examples
105 |
106 | iex> delete_thread(thread)
107 | {:ok, %Thread{}}
108 |
109 | iex> delete_thread(thread)
110 | {:error, %Ecto.Changeset{}}
111 |
112 | """
113 | @spec delete_thread(Thread.t()) :: {:ok, Thread.t()} | {:error, Ecto.Changeset.t()}
114 | def delete_thread(%Thread{} = thread) do
115 | Repo.delete(thread)
116 | end
117 |
118 | @doc """
119 | Fetches the threads for a given category.
120 |
121 | ## Examples
122 |
123 | iex> list_threads(category)
124 | [%Thread{}]
125 |
126 | """
127 | @spec list_threads(Category.t()) :: [Thread.t()]
128 | def list_threads(%Category{id: category_id}) do
129 | Thread
130 | |> where([t], t.category_id == ^category_id)
131 | |> Repo.all()
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/users/user.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Users.User do
2 | @moduledoc """
3 | Schema for forum users.
4 | """
5 |
6 | use FirestormData.Data, :model
7 | alias FirestormData.Users.User
8 |
9 | @type t :: %User{
10 | email: String.t(),
11 | name: String.t(),
12 | username: String.t(),
13 | admin: boolean(),
14 | password_hash: String.t(),
15 | password: nil | String.t(),
16 | inserted_at: DateTime.t(),
17 | updated_at: DateTime.t()
18 | }
19 |
20 | schema "firestorm_users_users" do
21 | field(:email, :string)
22 | field(:name, :string)
23 | field(:admin, :boolean)
24 | field(:username, :string)
25 | field(:password_hash, :string)
26 | field(:password, :string, virtual: true)
27 |
28 | timestamps()
29 | end
30 |
31 | @required_fields ~w(username)a
32 | @optional_fields ~w(email name admin)a
33 |
34 | def changeset(%__MODULE__{} = user, attrs \\ %{}) do
35 | user
36 | |> cast(attrs, @required_fields ++ @optional_fields)
37 | |> validate_required(@required_fields)
38 | |> unique_constraint(:username)
39 | end
40 |
41 | def registration_changeset(%__MODULE__{} = user, attrs) do
42 | user
43 | |> changeset(attrs)
44 | |> cast(attrs, [:password])
45 | |> validate_length(:password, min: 6)
46 | |> put_password_hash()
47 | end
48 |
49 | defp put_password_hash(changeset) do
50 | case changeset do
51 | %Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
52 | changeset
53 | |> put_change(:password_hash, Comeonin.Bcrypt.hashpwsalt(pass))
54 |
55 | _ ->
56 | changeset
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/firestorm_data/users/users.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Users do
2 | @moduledoc """
3 | Users can interact with the forum in an authenticated manner.
4 | """
5 |
6 | alias FirestormData.{
7 | Repo,
8 | Users.User
9 | }
10 |
11 | @doc """
12 | Creates a user.
13 |
14 | ## Examples
15 |
16 | iex> create_user(%{field: value})
17 | {:ok, %User{}}
18 |
19 | iex> create_user(%{field: bad_value})
20 | {:error, %Ecto.Changeset{}}
21 |
22 | """
23 | @spec create_user(map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
24 | def create_user(attrs \\ %{}) do
25 | %User{}
26 | |> User.registration_changeset(attrs)
27 | |> Repo.insert()
28 | end
29 |
30 | @spec get_user(String.t()) :: {:ok, User.t()} | {:error, :no_such_user}
31 | def get_user(id) do
32 | case Repo.get(User, id) do
33 | nil -> {:error, :no_such_user}
34 | user -> {:ok, user}
35 | end
36 | end
37 |
38 | @spec delete_user(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
39 | def delete_user(user = %User{}) do
40 | Repo.delete(user)
41 | end
42 |
43 | @spec update_user(User.t(), map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
44 | def update_user(user = %User{}, attrs) do
45 | user
46 | |> User.changeset(attrs)
47 | |> Repo.update()
48 | end
49 |
50 | @spec find_user(map()) :: {:ok, User.t()} | {:error, :no_such_user}
51 | def find_user(attrs \\ %{}) do
52 | User
53 | |> Repo.get_by(attrs)
54 | |> case do
55 | nil -> {:error, :no_such_user}
56 | user -> {:ok, user}
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/apps/firestorm_data/lib/seed.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Seed do
2 | @moduledoc """
3 | Some seed data for getting the application into a usable state from an empty database.
4 |
5 | Presently this is used primarily for development purposes - a production-specific seeder could be
6 | created to replace it if needed.
7 | """
8 |
9 | alias FirestormData.{
10 | Users,
11 | Categories,
12 | Threads,
13 | Posts
14 | }
15 |
16 | @spec call() :: :ok
17 | def call do
18 | IO.puts("")
19 |
20 | IO.puts("== Creating Users ==")
21 |
22 | {:ok, adam} =
23 | Users.create_user(%{
24 | name: "Adam Dill",
25 | username: "adam",
26 | email: "adam@smoothterminal.com",
27 | admin: true,
28 | password: "password"
29 | })
30 |
31 | {:ok, josh} =
32 | Users.create_user(%{
33 | name: "Josh Adams",
34 | username: "josh",
35 | email: "josh@smoothterminal.com",
36 | admin: true,
37 | password: "password"
38 | })
39 |
40 | {:ok, franzejr} =
41 | Users.create_user(%{
42 | name: "Franzé",
43 | username: "franzejr",
44 | email: "franzejr@smoothterminal.com",
45 | admin: true,
46 | password: "password"
47 | })
48 |
49 | IO.puts("== Creating Categories ==")
50 | {:ok, programming_lang} = Categories.create_category(%{title: "Programming Languages"})
51 | {:ok, off_topic} = Categories.create_category(%{title: "Off Topic"})
52 | {:ok, soft_eng} = Categories.create_category(%{title: "Software Engineering"})
53 |
54 | IO.puts("== Creating Threads ==")
55 |
56 | {:ok, otp_cool} =
57 | Threads.create_thread(programming_lang, franzejr, %{
58 | title: "OTP is cool",
59 | body: "I really love otp"
60 | })
61 |
62 | {:ok, rv} =
63 | Threads.create_thread(off_topic, josh, %{
64 | title: "I lived in an RV for some months",
65 | body: "That was cool. I could work and enjoy the life."
66 | })
67 |
68 | IO.puts("== Creating Posts ==")
69 | Posts.create_post(otp_cool, franzejr, %{body: "Don't you think?"})
70 | Posts.create_post(otp_cool, josh, %{body: "I totally agree."})
71 | Posts.create_post(otp_cool, adam, %{body: "Howwdy! I agree too!"})
72 |
73 | Posts.create_post(rv, josh, %{body: "Ask me questions."})
74 |
75 | Posts.create_post(rv, franzejr, %{
76 | body: "How could you work inside the RV? How was your routine?"
77 | })
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/apps/firestorm_data/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :firestorm_data,
7 | version: "0.1.0",
8 | build_path: "../../_build",
9 | config_path: "../../config/config.exs",
10 | deps_path: "../../deps",
11 | dialyzer: [plt_add_deps: :transitive],
12 | lockfile: "../../mix.lock",
13 | elixir: "~> 1.7",
14 | elixirc_paths: elixirc_paths(Mix.env()),
15 | start_permanent: Mix.env() == :prod,
16 | deps: deps()
17 | ]
18 | end
19 |
20 | defp elixirc_paths(:test), do: ["lib", "test/support"]
21 | defp elixirc_paths(_), do: ["lib"]
22 |
23 | # Run "mix help compile.app" to learn about applications.
24 | def application do
25 | [
26 | extra_applications: [:logger],
27 | mod: {FirestormData.Application, []}
28 | ]
29 | end
30 |
31 | # Run "mix help deps" to learn about dependencies.
32 | defp deps do
33 | [
34 | {:ecto_sql, "~> 3.0"},
35 | {:postgrex, "~> 0.14"},
36 | # We use bcrypt to hash passwords
37 | {:bcrypt_elixir, "~> 1.1.1"},
38 | # We interact with bcrypt via comeonin
39 | {:comeonin, "~> 4.0"},
40 | # We want to paginate queries
41 | {:scrivener_ecto, "~> 2.0"},
42 | # EctoAutoslugField
43 | {:ecto_autoslug_field, "~> 2.0"},
44 | # We'd like as much type safety as we can get
45 | {:dialyxir, "~> 1.0.0-rc.4", only: [:dev], runtime: false}
46 | ]
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/apps/firestorm_data/priv/repo/migrations/20181114181247_create_categories.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo.Migrations.CreateCategories do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:firestorm_categories_categories, primary_key: false) do
6 | add(:id, :uuid, primary_key: true)
7 | add(:title, :string)
8 |
9 | timestamps()
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/apps/firestorm_data/priv/repo/migrations/20181114192933_create_threads.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo.Migrations.CreateThreads do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:firestorm_threads_threads, primary_key: false) do
6 | add(:id, :uuid, primary_key: true)
7 | add(:title, :string)
8 |
9 | add(
10 | :category_id,
11 | references(:firestorm_categories_categories, type: :binary_id, on_delete: :delete_all)
12 | )
13 |
14 | timestamps()
15 | end
16 |
17 | create(index(:firestorm_threads_threads, [:category_id]))
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/apps/firestorm_data/priv/repo/migrations/20181115164920_create_posts.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo.Migrations.CreatePosts do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:firestorm_posts_posts, primary_key: false) do
6 | add(:id, :uuid, primary_key: true)
7 | add(:body, :text)
8 |
9 | add(
10 | :thread_id,
11 | references(:firestorm_threads_threads, type: :binary_id, on_delete: :delete_all)
12 | )
13 |
14 | timestamps()
15 | end
16 |
17 | create(index(:firestorm_posts_posts, [:thread_id]))
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/apps/firestorm_data/priv/repo/migrations/20181116024055_create_users.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo.Migrations.CreateUsers do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:firestorm_users_users, primary_key: false) do
6 | add(:id, :uuid, primary_key: true)
7 | add(:username, :string)
8 | add(:email, :string)
9 | add(:name, :string)
10 | add(:password_hash, :string)
11 | add(:api_token, :string)
12 |
13 | timestamps()
14 | end
15 |
16 | create(unique_index(:firestorm_users_users, [:username]))
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/apps/firestorm_data/priv/repo/migrations/20181116031928_add_user_id_to_posts.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo.Migrations.AddUserIdToPosts do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:firestorm_posts_posts) do
6 | add(:user_id, references(:firestorm_users_users, type: :binary_id, on_delete: :nilify_all))
7 | end
8 |
9 | create(index(:firestorm_posts_posts, [:user_id]))
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/apps/firestorm_data/priv/repo/migrations/20191004172429_add_slug_to_threads_and_categories.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo.Migrations.AddSlugToThreadsAndCategories do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:firestorm_threads_threads) do
6 | add(:slug, :string)
7 | end
8 |
9 | alter table(:firestorm_categories_categories) do
10 | add(:slug, :string)
11 | end
12 |
13 | create(index(:firestorm_threads_threads, [:slug]))
14 | create(index(:firestorm_categories_categories, [:slug]))
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/apps/firestorm_data/priv/repo/migrations/20191004172431_add_admin_to_users.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.Repo.Migrations.AddAdminToUsers do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:firestorm_users_users) do
6 | add(:admin, :boolean, default: false)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/apps/firestorm_data/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 |
6 | IO.puts("seeding")
7 | FirestormData.Seed.call()
8 |
--------------------------------------------------------------------------------
/apps/firestorm_data/test/categories_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.CategoriesTest do
2 | use FirestormData.DataCase
3 |
4 | import FirestormData.Categories
5 |
6 | alias FirestormData.{
7 | Categories.Category,
8 | Repo,
9 | Threads,
10 | Threads.Thread,
11 | Users
12 | }
13 |
14 | test "create_category/1 with valid data creates a category" do
15 | attrs = %{title: "some title"}
16 | assert {:ok, %Category{} = category} = create_category(attrs)
17 | assert category.title == "some title"
18 | assert category.slug == "some-title"
19 | end
20 |
21 | test "categories have many threads" do
22 | {:ok, user} =
23 | Users.create_user(%{
24 | username: "knewter",
25 | name: "Josh Adams",
26 | email: "josh@smoothterminal.com"
27 | })
28 |
29 | attrs = %{title: "some title"}
30 | {:ok, %Category{} = category} = create_category(attrs)
31 |
32 | {:ok, %Thread{id: thread_id}} =
33 | Threads.create_thread(
34 | category,
35 | user,
36 | %{title: "Thread", body: "First post"}
37 | )
38 |
39 | category = Repo.preload(category, [:threads])
40 | assert thread_id in Enum.map(category.threads, & &1.id)
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/apps/firestorm_data/test/firestorm_data_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormDataTest do
2 | use ExUnit.Case
3 | doctest FirestormData
4 |
5 | test "greets the world" do
6 | assert FirestormData.hello() == :world
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/apps/firestorm_data/test/posts_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.PostsTest do
2 | use FirestormData.DataCase
3 |
4 | import FirestormData.Posts
5 |
6 | alias FirestormData.{
7 | Categories,
8 | Threads,
9 | Users,
10 | Posts.Post
11 | }
12 |
13 | setup do
14 | {:ok, category} = Categories.create_category(%{title: "Category"})
15 |
16 | {:ok, user} =
17 | Users.create_user(%{
18 | username: "knewter",
19 | name: "Josh Adams",
20 | email: "josh@smoothterminal.com"
21 | })
22 |
23 | {:ok, thread} = Threads.create_thread(category, user, %{title: "Thread", body: "First post"})
24 |
25 | {:ok, thread: thread, user: user}
26 | end
27 |
28 | test "creating a post in a thread", %{thread: thread, user: user} do
29 | assert {:ok, %Post{} = post} = create_post(thread, user, %{body: "Post body"})
30 | assert post.thread_id == thread.id
31 | assert post.body == "Post body"
32 | assert post.user_id == user.id
33 | end
34 |
35 | test "getting the posts for a thread", %{thread: thread, user: user} do
36 | assert {:ok, %Post{} = post} = create_post(thread, user, %{body: "Post body"})
37 | posts = list_posts(thread)
38 | assert post.id in Enum.map(posts, & &1.id)
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/apps/firestorm_data/test/support/data_case.ex:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.DataCase do
2 | @moduledoc """
3 | This module defines the setup for tests requiring
4 | access to the application's data layer.
5 |
6 | You may define functions here to be used as helpers in
7 | your tests.
8 |
9 | Finally, if the test case interacts with the database,
10 | it cannot be async. For this reason, every test runs
11 | inside a transaction which is reset at the beginning
12 | of the test unless the test case is marked as async.
13 | """
14 |
15 | use ExUnit.CaseTemplate
16 |
17 | using do
18 | quote do
19 | alias FirestormData.Repo
20 |
21 | import Ecto
22 | import Ecto.Changeset
23 | import Ecto.Query
24 | import FirestormData.DataCase
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(FirestormData.Repo)
30 |
31 | unless tags[:async] do
32 | Ecto.Adapters.SQL.Sandbox.mode(FirestormData.Repo, {:shared, self()})
33 | end
34 |
35 | :ok
36 | end
37 |
38 | @doc """
39 | A helper that transform changeset errors to a map of messages.
40 |
41 | changeset = Accounts.create_user(%{password: "short"})
42 | assert "password is too short" in errors_on(changeset).password
43 | assert %{password: ["password is too short"]} = errors_on(changeset)
44 |
45 | """
46 | def errors_on(changeset) do
47 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
48 | Enum.reduce(opts, message, fn {key, value}, acc ->
49 | String.replace(acc, "%{#{key}}", to_string(value))
50 | end)
51 | end)
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/apps/firestorm_data/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/apps/firestorm_data/test/threads_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.ThreadsTest do
2 | use FirestormData.DataCase
3 |
4 | import FirestormData.Threads
5 |
6 | alias FirestormData.{
7 | Categories,
8 | Threads.Thread,
9 | Users
10 | }
11 |
12 | setup do
13 | {:ok, category} = Categories.create_category(%{title: "New Category"})
14 |
15 | {:ok, user} =
16 | Users.create_user(%{
17 | username: "knewter",
18 | name: "Josh Adams",
19 | email: "josh@smoothterminal.com"
20 | })
21 |
22 | {:ok, category: category, user: user}
23 | end
24 |
25 | test "create_thread/3 with valid data creates a thread and its first post", %{
26 | category: category,
27 | user: user
28 | } do
29 | attrs = %{title: "Some title", body: "First post"}
30 | assert {:ok, %Thread{} = thread} = create_thread(category, user, attrs)
31 | assert thread.title == attrs.title
32 | assert thread.slug == "some-title"
33 | first_post = hd(thread.posts)
34 | assert first_post.thread_id == thread.id
35 | assert first_post.body == "First post"
36 | end
37 |
38 | test "get_thread returns the thread with given id", %{
39 | category: category,
40 | user: user
41 | } do
42 | {:ok, %Thread{id: id}} =
43 | create_thread(category, user, %{title: "New Title", body: "First post"})
44 |
45 | assert {:ok, %Thread{id: ^id}} = get_thread(category, id)
46 | end
47 |
48 | test "create_thread/3 with invalid data returns error changeset", %{
49 | category: category,
50 | user: user
51 | } do
52 | assert {:error, changeset} = create_thread(category, user, %{title: nil})
53 | assert "can't be blank" in errors_on(changeset).title
54 | end
55 |
56 | test "update_thread/2 with valid data updates the thread", %{
57 | category: category,
58 | user: user
59 | } do
60 | {:ok, thread = %Thread{}} =
61 | create_thread(category, user, %{title: "New Title", body: "First post"})
62 |
63 | updated_attrs = %{title: "some updated title"}
64 | assert {:ok, %Thread{} = thread} = update_thread(thread, updated_attrs)
65 | assert thread.title == updated_attrs.title
66 | end
67 |
68 | test "update_thread/2 with invalid data returns error changeset", %{
69 | category: category,
70 | user: user
71 | } do
72 | {:ok, thread = %Thread{}} =
73 | create_thread(category, user, %{title: "New Title", body: "First Post"})
74 |
75 | assert {:error, changeset} = update_thread(thread, %{title: nil})
76 | assert "can't be blank" in errors_on(changeset).title
77 | end
78 |
79 | test "delete_thread/1 deletes the thread", %{
80 | category: category,
81 | user: user
82 | } do
83 | {:ok, thread = %Thread{}} =
84 | create_thread(category, user, %{title: "New Title", body: "First Post"})
85 |
86 | assert {:ok, %Thread{}} = delete_thread(thread)
87 | assert {:error, :no_such_thread} = get_thread(category, thread.id)
88 | end
89 |
90 | test "list_threads/1 fetches threads for the given category", %{
91 | category: category,
92 | user: user
93 | } do
94 | {:ok, thread = %Thread{}} =
95 | create_thread(category, user, %{title: "New Title", body: "First Post"})
96 |
97 | threads = list_threads(category)
98 | assert thread.id in Enum.map(threads, & &1.id)
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/apps/firestorm_data/test/users_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormData.UsersTest do
2 | use FirestormData.DataCase
3 |
4 | import FirestormData.Users
5 | alias FirestormData.Users.User
6 |
7 | @attrs %{
8 | email: "josh@smoothterminal.com",
9 | name: "Josh Adams",
10 | username: "knewter"
11 | }
12 |
13 | test "create_user/1 with valid data creates a user" do
14 | assert {:ok, %User{} = user} = create_user(@attrs)
15 | assert user.email == @attrs.email
16 | assert user.name == @attrs.name
17 | assert user.username == @attrs.username
18 | end
19 |
20 | test "create_user/1 with valid data creates a user with a password" do
21 | assert {:ok, %User{} = user} = create_user(Map.put(@attrs, :password, "somepassword"))
22 | assert user.email == @attrs.email
23 | assert user.name == @attrs.name
24 | assert user.username == @attrs.username
25 | assert user.password_hash
26 | end
27 |
28 | test "create_user/1 with invalid data returns error changeset" do
29 | assert {:error, changeset} = create_user(Map.delete(@attrs, :username))
30 | assert "can't be blank" in errors_on(changeset).username
31 | end
32 |
33 | test "get_user returns the user with given id" do
34 | {:ok, user} = create_user(@attrs)
35 | assert {:ok, %User{}} = get_user(user.id)
36 | end
37 |
38 | test "update_user/2 with valid data updates the user" do
39 | {:ok, user} = create_user(@attrs)
40 | updated_attrs = %{email: "updated@example.com", name: "New name", username: "new_username"}
41 | assert {:ok, user = %User{}} = update_user(user, updated_attrs)
42 | assert user.email == updated_attrs.email
43 | assert user.name == updated_attrs.name
44 | assert user.username == updated_attrs.username
45 | end
46 |
47 | test "update_user/2 with invalid data returns error changeset" do
48 | {:ok, user} = create_user(@attrs)
49 | assert {:error, changeset} = update_user(user, %{username: nil})
50 | assert "can't be blank" in errors_on(changeset).username
51 | end
52 |
53 | test "delete_user/1 deletes the user" do
54 | {:ok, user} = create_user(@attrs)
55 | assert {:ok, %User{}} = delete_user(user)
56 | assert {:error, :no_such_user} = get_user(user.id)
57 | end
58 |
59 | test "find_user/1 finds a user with the provided attributes" do
60 | {:ok, _} = create_user(@attrs)
61 | assert {:ok, %User{}} = find_user(%{email: @attrs.email})
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/assets/images/firestorm-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/firestormforum/firestorm_elixir/80caba13daa21ef6087aa85f6cf0dd1f016e9aef/assets/images/firestorm-logo.png
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # By default, the umbrella project as well as each child
6 | # application will require this configuration file, ensuring
7 | # they all use the same configuration. While one could
8 | # configure all applications here, we prefer to delegate
9 | # back to each application for organization purposes.
10 | import_config "../apps/*/config/config.exs"
11 |
12 | # Sample configuration (overrides the imported configuration above):
13 | #
14 | # config :logger, :console,
15 | # level: :info,
16 | # format: "$date $time [$level] $metadata$message\n",
17 | # metadata: [:user_id]
18 |
19 | config :torch,
20 | otp_app: :firestorm,
21 | template_format: "eex"
22 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule FirestormUmbrella.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | apps_path: "apps",
7 | start_permanent: Mix.env() == :prod,
8 | deps: deps()
9 | ]
10 | end
11 |
12 | # Dependencies listed here are available only for this
13 | # project and cannot be accessed from applications inside
14 | # the apps folder.
15 | #
16 | # Run "mix help deps" for examples and options.
17 | defp deps do
18 | []
19 | end
20 | end
21 |
--------------------------------------------------------------------------------