├── .gitignore
├── .iex.exs
├── .travis.yml
├── LICENSE
├── README.md
├── assets
├── brunch-config.js
├── css
│ └── app.scss
├── js
│ └── app.js
├── package.json
└── static
│ ├── favicon.ico
│ ├── images
│ └── phoenix.png
│ └── robots.txt
├── config
├── config.exs
├── dev.exs
├── prod.exs
└── test.exs
├── etc
└── emqttd
│ ├── emq.conf
│ └── plugins
│ └── emq_auth_pgsql.conf
├── lib
├── iris.ex
├── iris
│ ├── accounts
│ │ ├── accounts.ex
│ │ ├── device.ex
│ │ ├── role.ex
│ │ └── user.ex
│ ├── application.ex
│ ├── ecto_enums.ex
│ ├── mailer.ex
│ └── repo.ex
├── iris_web.ex
├── iris_web
│ ├── channels
│ │ └── user_socket.ex
│ ├── controllers
│ │ ├── device_controller.ex
│ │ ├── message_controller.ex
│ │ ├── page_controller.ex
│ │ ├── registration_controller.ex
│ │ ├── service_controller.ex
│ │ ├── session_controller.ex
│ │ └── user_controller.ex
│ ├── email.ex
│ ├── endpoint.ex
│ ├── gettext.ex
│ ├── models
│ │ ├── message.ex
│ │ ├── service.ex
│ │ └── token.ex
│ ├── plugs
│ │ ├── authedicate_admin.ex
│ │ └── authedicate_user.ex
│ ├── router.ex
│ ├── support
│ │ └── AuthedicationToken.ex
│ ├── templates
│ │ ├── device
│ │ │ ├── edit.html.eex
│ │ │ ├── form.html.eex
│ │ │ ├── index.html.eex
│ │ │ ├── new.html.eex
│ │ │ └── show.html.eex
│ │ ├── email
│ │ │ ├── login.html.eex
│ │ │ ├── login.text.eex
│ │ │ ├── welcome.html.eex
│ │ │ └── welcome.text.eex
│ │ ├── layout
│ │ │ ├── app.html.eex
│ │ │ ├── email.html.eex
│ │ │ ├── email.text.eex
│ │ │ ├── footer.html.eex
│ │ │ ├── messages.html.eex
│ │ │ └── nav.html.eex
│ │ ├── page
│ │ │ ├── dashboard.html.eex
│ │ │ ├── devices.html.eex
│ │ │ ├── index.html.eex
│ │ │ ├── notification_form.html.eex
│ │ │ ├── services.html.eex
│ │ │ └── settings.html.eex
│ │ ├── registration
│ │ │ └── signup.html.eex
│ │ ├── session
│ │ │ └── login.html.eex
│ │ └── user
│ │ │ ├── edit.html.eex
│ │ │ ├── form.html.eex
│ │ │ ├── index.html.eex
│ │ │ ├── new.html.eex
│ │ │ └── show.html.eex
│ └── views
│ │ ├── changeset_view.ex
│ │ ├── device_view.ex
│ │ ├── email_view.ex
│ │ ├── error_helpers.ex
│ │ ├── error_view.ex
│ │ ├── layout_view.ex
│ │ ├── message_view.ex
│ │ ├── page_view.ex
│ │ ├── registration_view.ex
│ │ ├── service_view.ex
│ │ ├── session_view.ex
│ │ └── user_view.ex
└── mqtt
│ ├── messenger.ex
│ └── supervisor.ex
├── mix.exs
├── mix.lock
├── priv
├── gettext
│ ├── en
│ │ └── LC_MESSAGES
│ │ │ └── errors.po
│ └── errors.pot
└── repo
│ ├── migrations
│ ├── 20170629190008_create_service.exs
│ ├── 20170629195901_create_message.exs
│ ├── 20170702202723_create_role.exs
│ ├── 20170702202724_create_user.exs
│ ├── 20170704174839_create_token.exs
│ └── 20170719054344_create_device.exs
│ └── seeds.exs
└── test
├── iris_web
├── controllers
│ ├── device_controller_test.exs
│ ├── message_controller_test.exs
│ ├── page_controller_test.exs
│ ├── registration_controller_test.exs
│ ├── service_controller_test.exs
│ ├── session_controller_test.exs
│ └── user_controller_test.exs
├── models
│ ├── device_test.exs
│ ├── message_test.exs
│ ├── role_test.exs
│ ├── service_test.exs
│ ├── token_test.exs
│ └── user_test.exs
└── views
│ ├── error_view_test.exs
│ ├── layout_view_test.exs
│ └── page_view_test.exs
├── support
├── channel_case.ex
├── conn_case.ex
├── data_case.ex
└── test_helper.ex
└── test_helper.exs
/.gitignore:
--------------------------------------------------------------------------------
1 | # App artifacts
2 | /_build
3 | /db
4 | /deps
5 | /*.ez
6 | /cover
7 |
8 | # Generated on crash by the VM
9 | erl_crash.dump
10 |
11 | # Static artifacts
12 | /assets/node_modules
13 |
14 | # Since we are building assets from web/static,
15 | # we ignore priv/static. You may want to comment
16 | # this depending on your deployment strategy.
17 | /priv/static/
18 |
19 | # The config/prod.secret.exs file by default contains sensitive
20 | # data and you should not commit it into version control.
21 | #
22 | # Alternatively, you may comment the line below and commit the
23 | # secrets file as long as you replace its contents by environment
24 | # variables.
25 | /config/prod.secret.exs
26 |
--------------------------------------------------------------------------------
/.iex.exs:
--------------------------------------------------------------------------------
1 | alias Iris.{Role, User, Endpoint}
2 | alias IrisWeb.Router.Helpers
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: required
3 |
4 | language: elixir
5 | elixir:
6 | - 1.5
7 | otp_release:
8 | - 20.0
9 |
10 | addons:
11 | postgresql: '9.4'
12 |
13 | services:
14 | - postgresql
15 |
16 | before_install:
17 | - wget http://emqtt.io/downloads/stable/ubuntu14_04-deb -O emqtt.deb
18 | - sudo dpkg -i emqtt.deb
19 | - sudo service emqttd start
20 |
21 | before_script:
22 | - mix do ecto.create, ecto.migrate
23 |
24 | env:
25 | - MIX_ENV=test
26 |
27 | script:
28 | - mix coveralls.travis
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Stelios Joseph Karras
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Iris
2 |
3 | A self hosted notification server without google cloud messaging, firebase cloud messaging or apple push notification service.
4 |
5 | ## Motivation
6 | The driving force behind this project is my need for a tool that:
7 | - Notifies the user when something is happening in a service/application.
8 | - The user can deploy it in her/his premises.
9 | - Uses a self hosted and open source message broker and not an external one.
10 |
11 | ## Project Status
12 |
13 | [](https://travis-ci.org/7-iris/iris_server)
14 | [](https://coveralls.io/github/7-iris/iris_server?branch=master)
15 |
16 | This project is under development, it is not ready for production use yet.
17 |
18 | ## Usage example
19 |
20 | To send a notification from bash:
21 |
22 | ```
23 | echo '{"service_token":"xyz","title":"Testing","text":"notification message"}' | curl -H "Content-Type: application/json" -XPOST -d @- http://localhost:4000/api/messages
24 | ```
25 |
26 | ## Installation
27 |
28 | ### Prerequisites
29 | In order to get iris to work you need to install the emqttd server first.
30 | You can find the instructions [here](http://emqtt.io/docs/v2/install.html).
31 |
32 | ### Installing via DEB package
33 | TODO
34 |
35 | ### Installing via docker image
36 | TODO
37 |
38 | ## Built With
39 |
40 | * [Phoenix](https://github.com/phoenixframework/phoenix)
41 | * [Hulaaki](https://github.com/suvash/hulaaki)
42 | * [Emqttd](https://github.com/emqtt/emqttd)
43 | * [Bootstrap](https://github.com/twbs/bootstrap)
44 | * [Bootswatch](https://github.com/thomaspark/bootswatch)
45 | * And many more...
46 |
47 | ## Resources
48 |
49 | * [Project roadmap](https://github.com/7-iris/iris_server/wiki/Roadmap)
50 | * [What is mqtt?](http://mqtt.org/faq)
51 | * [What is emqttd?](http://emqtt.io/about)
52 |
53 | ## Naming
54 | Iris in Greek mythology was one of the messengers of the Olympian gods.
55 |
56 | ## Authors
57 |
58 | * Stelios Joseph Karras [stylkarr@gmail.com](mailto://stylkarr@gmail.com)
59 |
60 | ## License
61 |
62 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE) file for details
63 |
64 | ## Acknowledgments
65 |
66 | * Inspired by [pushbullet](http://pushbullet.com/), [pushover](http://pushover.net/) and [pushjet](http://pushjet.io/)
67 |
--------------------------------------------------------------------------------
/assets/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See http://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: "js/app.js"
6 | },
7 | stylesheets: {
8 | joinTo: "css/app.css",
9 | order: {
10 | after: ["web/static/css/app.css"]
11 | }
12 | },
13 | templates: {
14 | joinTo: "js/app.js"
15 | }
16 | },
17 |
18 | conventions: {
19 | assets: /^(static)/
20 | },
21 |
22 | paths: {
23 | watched: ["static", "css", "js", "vendor"],
24 |
25 | // Where to compile files to
26 | public: "../priv/static"
27 | },
28 |
29 | // Configure your plugins
30 | plugins: {
31 | babel: {
32 | // Do not use ES6 compiler in vendor code
33 | ignore: [/vendor/]
34 | },
35 | sass: {
36 | options: {
37 | includePaths: ["node_modules/bootstrap/scss"],
38 | precision: 8 // Minimum precision required by bootstrap-sass
39 | }
40 | }
41 | },
42 |
43 | modules: {
44 | autoRequire: {
45 | "js/app.js": ["js/app"]
46 | }
47 | },
48 |
49 | npm: {
50 | enabled: true,
51 | globals: {
52 | $: "jquery",
53 | jQuery: "jquery",
54 | bootstrap: "bootstrap"
55 | }
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/assets/css/app.scss:
--------------------------------------------------------------------------------
1 | /* This file is for your main application css. */
2 | @import "bootswatch/_variables.scss";
3 | @import "bootstrap";
4 | @import "bootswatch/_bootswatch.scss";
5 |
--------------------------------------------------------------------------------
/assets/js/app.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/7-iris/iris_server/8faa370f506a003ae3c06b23d6c827bd746368f3/assets/js/app.js
--------------------------------------------------------------------------------
/assets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {},
3 | "license": "MIT",
4 | "scripts": {
5 | "deploy": "brunch build --production",
6 | "watch": "brunch watch --stdin"
7 | },
8 | "dependencies": {
9 | "bootstrap": "^4.0.0-alpha.6",
10 | "bootswatch": "https://github.com/thomaspark/bootswatch/archive/v4.0.0-alpha.6.tar.gz",
11 | "jquery": "^3.2.1",
12 | "phoenix": "file:../deps/phoenix",
13 | "phoenix_html": "file:../deps/phoenix_html"
14 | },
15 | "devDependencies": {
16 | "babel-brunch": "~6.0.0",
17 | "brunch": "2.7.4",
18 | "clean-css-brunch": "~2.0.0",
19 | "copycat-brunch": "^1.1.0",
20 | "css-brunch": "~2.0.0",
21 | "javascript-brunch": "~2.0.0",
22 | "sass-brunch": "^2.10.4",
23 | "uglify-js-brunch": "~2.0.1"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/assets/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/7-iris/iris_server/8faa370f506a003ae3c06b23d6c827bd746368f3/assets/static/favicon.ico
--------------------------------------------------------------------------------
/assets/static/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/7-iris/iris_server/8faa370f506a003ae3c06b23d6c827bd746368f3/assets/static/images/phoenix.png
--------------------------------------------------------------------------------
/assets/static/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # General application configuration
9 | config :iris,
10 | ecto_repos: [Iris.Repo]
11 |
12 | # Configures the endpoint
13 | config :iris, IrisWeb.Endpoint,
14 | url: [host: "localhost"],
15 | secret_key_base: "kdl17scWn5kPZxPtO8n6NE0eMwC7c2PIcFLhG+LQTp2jUDCNI5N6tHwXC3dCFLRq",
16 | render_errors: [view: IrisWeb.ErrorView, accepts: ~w(html json)],
17 | pubsub: [name: Iris.PubSub,
18 | 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 | # Import environment specific config. This must remain at the bottom
26 | # of this file so it overrides the configuration defined above.
27 | import_config "#{Mix.env}.exs"
28 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :iris, IrisWeb.Endpoint,
10 | http: [port: 4000],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
15 | cd: Path.expand("../assets", __DIR__)]]
16 |
17 |
18 | # Watch static and templates for browser reloading.
19 | config :iris, IrisWeb.Endpoint,
20 | live_reload: [
21 | patterns: [
22 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
23 | ~r{priv/gettext/.*(po)$},
24 | ~r{lib/iris_web/views/.*(ex)$},
25 | ~r{lib/iris_web/templates/.*(eex)$}
26 | ]
27 | ]
28 |
29 | # Do not include metadata nor timestamps in development logs
30 | config :logger,
31 | :console, format: "[$level] $message\n",
32 | level: :debug,
33 | truncate: 4096
34 |
35 | # Set a higher stacktrace during development. Avoid configuring such
36 | # in production as building large stacktraces may be expensive.
37 | config :phoenix, :stacktrace_depth, 20
38 |
39 | # Configure your database
40 | config :iris, Iris.Repo,
41 | adapter: Ecto.Adapters.Postgres,
42 | username: "postgres",
43 | password: "postgres",
44 | database: "iris_dev",
45 | hostname: "localhost",
46 | pool_size: 10
47 |
48 | config :iris, Mqtt.Messenger,
49 | client_id: "iris_server",
50 | host: "localhost",
51 | username: "admin@dev.com",
52 | password: "sagapo",
53 | port: 1883
54 |
55 | config :iris, Iris.Mailer,
56 | adapter: Bamboo.LocalAdapter
57 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we configure the host to read the PORT
4 | # from the system environment. Therefore, you will need
5 | # to set PORT=80 before running your server.
6 | #
7 | # You should also configure the url host to something
8 | # meaningful, we use this information when generating URLs.
9 | #
10 | # Finally, we also include the path to a manifest
11 | # containing the digested version of static files. This
12 | # manifest is generated by the mix phoenix.digest task
13 | # which you typically run after static files are built.
14 | config :iris, IrisWeb.Endpoint,
15 | http: [port: {:system, "PORT"}],
16 | url: [host: "example.com", port: 80],
17 | # cache_static_manifest: "priv/static/manifest.json"
18 |
19 | # Do not print debug messages in production
20 | config :logger, level: :info
21 |
22 | # ## SSL Support
23 | #
24 | # To get SSL working, you will need to add the `https` key
25 | # to the previous section and set your `:url` port to 443:
26 | #
27 | # config :iris, IrisWeb.Endpoint,
28 | # ...
29 | # url: [host: "example.com", port: 443],
30 | # https: [port: 443,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
33 | #
34 | # Where those two env variables return an absolute path to
35 | # the key and cert in disk or a relative path inside priv,
36 | # for example "priv/ssl/server.key".
37 | #
38 | # We also recommend setting `force_ssl`, ensuring no data is
39 | # ever sent via http, always redirecting to https:
40 | #
41 | # config :iris, IrisWeb.Endpoint,
42 | # force_ssl: [hsts: true]
43 | #
44 | # Check `Plug.SSL` for all available options in `force_ssl`.
45 |
46 | # ## Using releases
47 | #
48 | # If you are doing OTP releases, you need to instruct Phoenix
49 | # to start the server for all endpoints:
50 | #
51 | # config :phoenix, :serve_endpoints, true
52 | #
53 | # Alternatively, you can configure exactly which server to
54 | # start per endpoint:
55 | #
56 | # config :iris, IrisWeb.Endpoint, server: true
57 | #
58 |
59 | # Finally import the config/prod.secret.exs
60 | # which should be versioned separately.
61 | import_config "prod.secret.exs"
62 |
--------------------------------------------------------------------------------
/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 :iris, IrisWeb.Endpoint,
6 | http: [port: 4001],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
12 | # Configure your database
13 | config :iris, Iris.Repo,
14 | adapter: Ecto.Adapters.Postgres,
15 | username: "postgres",
16 | password: "postgres",
17 | database: "iris_test",
18 | hostname: "localhost",
19 | pool: Ecto.Adapters.SQL.Sandbox
20 |
21 | config :iris, Mqtt.Messenger,
22 | client_id: "iris_server",
23 | host: "localhost",
24 | username: "admin@dev.com",
25 | password: "sagapo",
26 | port: 1883
27 |
28 | config :iris, Iris.Mailer,
29 | adapter: Bamboo.LocalAdapter
30 |
--------------------------------------------------------------------------------
/etc/emqttd/emq.conf:
--------------------------------------------------------------------------------
1 | ##===================================================================
2 | ## EMQ Configuration R2.3
3 | ##===================================================================
4 |
5 | ##--------------------------------------------------------------------
6 | ## Cluster
7 | ##--------------------------------------------------------------------
8 |
9 | ## Cluster name
10 | cluster.name = emqcl
11 |
12 | ## Cluster discovery strategy: manual | static | mcast | dns | etcd | k8s
13 | cluster.discovery = manual
14 |
15 | ## Cluster Autoheal: on | off
16 | cluster.autoheal = on
17 |
18 | ## Clean down node of the cluster
19 | cluster.autoclean = 5m
20 |
21 | ##--------------------------------------------------------------------
22 | ## Node Args
23 | ##--------------------------------------------------------------------
24 |
25 | ## Node name
26 | node.name = emq@127.0.0.1
27 |
28 | ## Cookie for distributed node
29 | node.cookie = emqsecretcookie
30 |
31 | ## SMP support: enable, auto, disable
32 | node.smp = enable
33 |
34 | ## Enable kernel poll
35 | node.kernel_poll = on
36 |
37 | ## async thread pool
38 | node.async_threads = 32
39 |
40 | ## Erlang Process Limit
41 | node.process_limit = 256000
42 |
43 | ## Sets the maximum number of simultaneously existing ports for this system
44 | node.max_ports = 65536
45 |
46 | ## Set the distribution buffer busy limit (dist_buf_busy_limit)
47 | node.dist_buffer_size = 32MB
48 |
49 | ## Max ETS Tables.
50 | ## Note that mnesia and SSL will create temporary ets tables.
51 | node.max_ets_tables = 256000
52 |
53 | ## Tweak GC to run more often
54 | node.fullsweep_after = 1000
55 |
56 | ## Crash dump
57 | node.crash_dump = /var/log/emqttd/crash.dump
58 |
59 | ## Distributed node ticktime
60 | node.dist_net_ticktime = 60
61 |
62 | ## Distributed node port range
63 | node.dist_listen_min = 6369
64 | node.dist_listen_max = 6379
65 |
66 | ##--------------------------------------------------------------------
67 | ## Log
68 | ##--------------------------------------------------------------------
69 |
70 | ## Set the log dir
71 | log.dir = /var/log/emqttd
72 |
73 | ## Console log. Enum: off, file, console, both
74 | log.console = file
75 |
76 | ## Console log level. Enum: debug, info, notice, warning, error, critical, alert, emergency
77 | log.console.level = info
78 |
79 | ## Syslog. Enum: on, off
80 | log.syslog = on
81 |
82 | ## syslog level. Enum: debug, info, notice, warning, error, critical, alert, emergency
83 | log.syslog.level = info
84 |
85 | ## Console log file
86 | log.console.file = /var/log/emqttd/console.log
87 |
88 | ## Error log file
89 | log.error.file = /var/log/emqttd/error.log
90 |
91 | ## Enable the crash log. Enum: on, off
92 | log.crash = on
93 |
94 | log.crash.file = /var/log/emqttd/crash.log
95 |
96 | ##--------------------------------------------------------------------
97 | ## Allow Anonymous and Default ACL
98 | ##--------------------------------------------------------------------
99 |
100 | ## Allow Anonymous authentication
101 | mqtt.allow_anonymous = false
102 |
103 | ## ACL nomatch
104 | mqtt.acl_nomatch = allow
105 |
106 | ## Default ACL File
107 | mqtt.acl_file = /etc/emqttd/acl.conf
108 |
109 | ## Cache ACL for PUBLISH
110 | mqtt.cache_acl = true
111 |
112 | ##--------------------------------------------------------------------
113 | ## MQTT Protocol
114 | ##--------------------------------------------------------------------
115 |
116 | ## Max ClientId Length Allowed.
117 | mqtt.max_clientid_len = 1024
118 |
119 | ## Max Packet Size Allowed, 64K by default.
120 | mqtt.max_packet_size = 64KB
121 |
122 | ## Check Websocket Protocol Header. Enum: on, off
123 | mqtt.websocket_protocol_header = on
124 |
125 | ##--------------------------------------------------------------------
126 | ## MQTT Connection
127 | ##--------------------------------------------------------------------
128 |
129 | ## Force GC: integer. Value 0 disabled the Force GC.
130 | mqtt.conn.force_gc_count = 100
131 |
132 | ##--------------------------------------------------------------------
133 | ## MQTT Client
134 | ##--------------------------------------------------------------------
135 |
136 | ## Client Idle Timeout (Second)
137 | mqtt.client.idle_timeout = 30s
138 |
139 | ## Enable client Stats: on | off
140 | mqtt.client.enable_stats = off
141 |
142 | ##--------------------------------------------------------------------
143 | ## MQTT Session
144 | ##--------------------------------------------------------------------
145 |
146 | ## Max Number of Subscriptions, 0 means no limit.
147 | mqtt.session.max_subscriptions = 0
148 |
149 | ## Upgrade QoS?
150 | mqtt.session.upgrade_qos = off
151 |
152 | ## Max Size of the Inflight Window for QoS1 and QoS2 messages
153 | ## 0 means no limit
154 | mqtt.session.max_inflight = 32
155 |
156 | ## Retry Interval for redelivering QoS1/2 messages.
157 | mqtt.session.retry_interval = 20s
158 |
159 | ## Client -> Broker: Max Packets Awaiting PUBREL, 0 means no limit
160 | mqtt.session.max_awaiting_rel = 100
161 |
162 | ## Awaiting PUBREL Timeout
163 | mqtt.session.await_rel_timeout = 20s
164 |
165 | ## Enable Statistics: on | off
166 | mqtt.session.enable_stats = off
167 |
168 | ## Expired after 1 day:
169 | ## w - week
170 | ## d - day
171 | ## h - hour
172 | ## m - minute
173 | ## s - second
174 | mqtt.session.expiry_interval = 2h
175 |
176 | ## Ignore message from self publish
177 | mqtt.session.ignore_loop_deliver = false
178 |
179 | ##--------------------------------------------------------------------
180 | ## MQTT Message Queue
181 | ##--------------------------------------------------------------------
182 |
183 | ## Type: simple | priority
184 | mqtt.mqueue.type = simple
185 |
186 | ## Topic Priority: 0~255, Default is 0
187 | ## mqtt.mqueue.priority = topic/1=10,topic/2=8
188 |
189 | ## Max queue length. Enqueued messages when persistent client disconnected,
190 | ## or inflight window is full. 0 means no limit.
191 | mqtt.mqueue.max_length = 1000
192 |
193 | ## Low-water mark of queued messages
194 | mqtt.mqueue.low_watermark = 20%
195 |
196 | ## High-water mark of queued messages
197 | mqtt.mqueue.high_watermark = 60%
198 |
199 | ## Queue Qos0 messages?
200 | mqtt.mqueue.store_qos0 = true
201 |
202 | ##--------------------------------------------------------------------
203 | ## MQTT Broker and PubSub
204 | ##--------------------------------------------------------------------
205 |
206 | ## System Interval of publishing broker $SYS Messages
207 | mqtt.broker.sys_interval = 60
208 |
209 | ## PubSub Pool Size. Default should be scheduler numbers.
210 | mqtt.pubsub.pool_size = 8
211 | mqtt.pubsub.by_clientid = true
212 |
213 | ## Subscribe Asynchronously
214 | mqtt.pubsub.async = true
215 |
216 | ##--------------------------------------------------------------------
217 | ## MQTT Bridge
218 | ##--------------------------------------------------------------------
219 |
220 | ## Bridge Queue Size
221 | mqtt.bridge.max_queue_len = 10000
222 |
223 | ## Ping Interval of bridge node. Unit: Second
224 | mqtt.bridge.ping_down_interval = 1
225 |
226 | ##-------------------------------------------------------------------
227 | ## MQTT Plugins
228 | ##-------------------------------------------------------------------
229 |
230 | ## Dir of plugins' config
231 | mqtt.plugins.etc_dir =/etc/emqttd/plugins/
232 |
233 | ## File to store loaded plugin names.
234 | mqtt.plugins.loaded_file = /var/lib/emqttd/loaded_plugins
235 |
236 | ##--------------------------------------------------------------------
237 | ## MQTT Listeners
238 | ##--------------------------------------------------------------------
239 |
240 | ##--------------------------------------------------------------------
241 | ## External TCP Listener
242 |
243 | ## External TCP Listener: 1883, 127.0.0.1:1883, ::1:1883
244 | listener.tcp.external = 0.0.0.0:1883
245 |
246 | ## Size of acceptor pool
247 | listener.tcp.external.acceptors = 16
248 |
249 | ## Maximum number of concurrent clients
250 | listener.tcp.external.max_clients = 102400
251 |
252 | ## Rate Limit. Format is 'burst,rate', Unit is KB/Sec
253 | listener.tcp.external.access.2 = allow all
254 |
255 | ## TCP Socket Options
256 | listener.tcp.external.backlog = 1024
257 | listener.tcp.external.buffer = 4KB
258 | listener.tcp.external.nodelay = true
259 |
260 | ##--------------------------------------------------------------------
261 | ## Internal TCP Listener
262 |
263 | ## Internal TCP Listener: 11883, 127.0.0.1:11883, ::1:11883
264 | listener.tcp.internal = 127.0.0.1:11883
265 |
266 | ## Size of acceptor pool
267 | listener.tcp.internal.acceptors = 16
268 |
269 | ## Maximum number of concurrent clients
270 | listener.tcp.internal.max_clients = 102400
271 |
272 | ## TCP Socket Options
273 | listener.tcp.internal.backlog = 512
274 | listener.tcp.internal.tune_buffer = on
275 | listener.tcp.internal.buffer = 1MB
276 | listener.tcp.internal.recbuf = 4KB
277 | listener.tcp.internal.sndbuf = 1MB
278 | listener.tcp.internal.nodelay = true
279 |
280 | ##--------------------------------------------------------------------
281 | ## External SSL Listener
282 |
283 | ## SSL Listener: 8883, 127.0.0.1:8883, ::1:8883
284 | listener.ssl.external = 8883
285 |
286 | ## Size of acceptor pool
287 | listener.ssl.external.acceptors = 16
288 |
289 | ## Maximum number of concurrent clients
290 | listener.ssl.external.max_clients = 1024
291 |
292 | ## Proxy Protocol V1/2
293 |
294 | listener.ssl.external.access.1 = allow all
295 |
296 | ### SSL Options. See http://erlang.org/doc/man/ssl.html
297 |
298 | listener.ssl.external.handshake_timeout = 15s
299 | listener.ssl.external.keyfile = /etc/emqttd/certs/key.pem
300 | listener.ssl.external.certfile = /etc/emqttd/certs/cert.pem
301 |
302 | ## listener.ssl.external.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA
303 |
304 | ##--------------------------------------------------------------------
305 | ## External MQTT/WebSocket Listener
306 |
307 | listener.ws.external = 8083
308 | listener.ws.external.acceptors = 4
309 | listener.ws.external.max_clients = 64
310 | listener.ws.external.access.1 = allow all
311 |
312 | ## TCP Options
313 | listener.ws.external.backlog = 1024
314 | listener.ws.external.recbuf = 4KB
315 | listener.ws.external.sndbuf = 4KB
316 | listener.ws.external.buffer = 4KB
317 | listener.ws.external.nodelay = true
318 |
319 | ##--------------------------------------------------------------------
320 | ## External MQTT/WebSocket/SSL Listener
321 |
322 | listener.wss.external = 8084
323 | listener.wss.external.acceptors = 4
324 | listener.wss.external.max_clients = 64
325 | listener.wss.external.access.1 = allow all
326 |
327 | ## SSL Options
328 | listener.wss.external.handshake_timeout = 15s
329 | listener.wss.external.keyfile = /etc/emqttd/certs/key.pem
330 | listener.wss.external.certfile = /etc/emqttd/certs/cert.pem
331 |
332 | ##--------------------------------------------------------------------
333 | ## HTTP Management API Listener
334 |
335 | listener.api.mgmt = 127.0.0.1:8080
336 |
337 | listener.api.mgmt.acceptors = 4
338 |
339 | listener.api.mgmt.max_clients = 64
340 |
341 | listener.api.mgmt.access.1 = allow all
342 |
343 | ##-------------------------------------------------------------------
344 | ## System Monitor
345 | ##-------------------------------------------------------------------
346 |
347 | ## Long GC, don't monitor in production mode for:
348 | ## https://github.com/erlang/otp/blob/feb45017da36be78d4c5784d758ede619fa7bfd3/erts/emulator/beam/erl_gc.c#L421
349 | sysmon.long_gc = false
350 |
351 | ## Long Schedule(ms)
352 | sysmon.long_schedule = 240
353 |
354 | ## 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
355 | sysmon.large_heap = 8MB
356 |
357 | ## Busy Port
358 | sysmon.busy_port = false
359 |
360 | ## Busy Dist Port
361 | sysmon.busy_dist_port = true
362 |
--------------------------------------------------------------------------------
/etc/emqttd/plugins/emq_auth_pgsql.conf:
--------------------------------------------------------------------------------
1 | ## Postgre Server
2 | auth.pgsql.server = 127.0.0.1:5432
3 | auth.pgsql.pool = 1
4 | auth.pgsql.username = postgres
5 | auth.pgsql.password = postgres
6 | auth.pgsql.database = iris_dev
7 | auth.pgsql.encoding = utf8
8 | auth.pgsql.ssl = false
9 |
10 | ## Variables: %u = username, %c = clientid, %a = ipaddress
11 | auth.pgsql.auth_query = SELECT devices.password FROM public.devices, public.users WHERE devices.client_id = '%c' AND users.email = '%u' AND devices.user_id = users.id AND users.disabled_at IS NULL LIMIT 1
12 |
13 | auth.pgsql.password_hash = plain
14 |
15 | ## Superuser Query
16 | auth.pgsql.super_query = SELECT roles.admin FROM public.users, public.roles WHERE roles.id = users.role_id AND users.email = '%u' LIMIT 1
17 | #auth.pgsql.acl_query = SELECT allow, ipaddr, username, clientid, access, topic FROM mqtt_acl WHERE ipaddr = '%a' OR username = '%u' OR username = '$all' OR clientid = '%c'
18 | #auth.pgsql.acl_nomatch = deny
19 |
--------------------------------------------------------------------------------
/lib/iris.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris do
2 | @moduledoc """
3 | Iris keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/lib/iris/accounts/accounts.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Accounts do
2 | @moduledoc """
3 | The Accounts context.
4 | """
5 | import Ecto.Query, warn: false
6 | import Ecto
7 | alias Iris.{Role, User, Repo, Device}
8 |
9 | #### Users
10 |
11 | @doc """
12 | Returns the list of users.
13 | """
14 | def list_users do
15 | Repo.all(User)
16 | end
17 |
18 | @doc """
19 | Gets a single user.
20 |
21 | Raises `Ecto.NoResultsError` if the User does not exist.
22 | """
23 | def get_user!(id), do: Repo.get!(User, id)
24 |
25 | def get_user_by_email(email) do
26 | Repo.get_by(User, email: email)
27 | end
28 |
29 | @doc """
30 | Creates a user.
31 | """
32 | def create_user(attributes \\ %{}) do
33 | attributes = check_user_attributes(attributes)
34 | %User{}
35 | |> User.changeset(attributes)
36 | |> Repo.insert()
37 | end
38 |
39 | defp check_user_attributes(attributes) do
40 | case Map.get(attributes, :role_id) do
41 | nil ->
42 | [role|_] = Repo.all(from role in Role, where: [admin: false])
43 | Map.put(attributes, :role_id, role.id)
44 | |> AtomicMap.convert(safe: true);
45 | _ -> attributes
46 | end
47 | end
48 |
49 | @doc """
50 | Updates a user.
51 | """
52 | def update_user(%User{} = user, attributes) do
53 | user
54 | |> User.changeset(attributes)
55 | |> Repo.update()
56 | end
57 |
58 | @doc """
59 | Deletes a User.
60 | """
61 | def delete_user(%User{} = user) do
62 | Repo.delete(user)
63 | end
64 |
65 | @doc """
66 | Returns an `%Ecto.Changeset{}` for tracking user changes.
67 | """
68 | def change_user(%User{} = user) do
69 | User.changeset(user, %{})
70 | end
71 |
72 | #### Devices
73 |
74 | @doc """
75 | Returns a list of the devices that the user owns.
76 | """
77 | def list_devices_by_user(user) do
78 | Repo.all(assoc(user, :devices))
79 | end
80 |
81 | @doc """
82 | Gets a signle device
83 | """
84 | def get_device!(id, user) do
85 | Repo.get!(assoc(user, :devices), id)
86 | end
87 |
88 | @doc """
89 | Creates a device for the specified user.
90 | """
91 | def create_device(attributes \\ %{}, user) do
92 | build_assoc(user, :devices)
93 | |> struct(%{client_id: random_string(20), access_token: random_string(30), password: random_string(5) })
94 | |> Device.changeset(attributes)
95 | |> Repo.insert()
96 | end
97 |
98 | @doc """
99 | Updates a device.
100 | """
101 | def update_device(%Device{} = device, attributes) do
102 | device
103 | |> Device.changeset(attributes)
104 | |> Repo.update()
105 | end
106 |
107 | @doc """
108 | Deletes a device.
109 | """
110 | def delete_device(%Device{} = device) do
111 | Repo.delete(device)
112 | end
113 |
114 | @doc """
115 | Returns an `%Ecto.Changeset{}` for tracking user changes.
116 | """
117 | def change_device(%Device{} = device) do
118 | Device.changeset(device, %{})
119 | end
120 |
121 | @doc false
122 | defp random_string(length) do
123 | :crypto.strong_rand_bytes(length) |> Base.encode32 |> binary_part(0, length)
124 | end
125 |
126 | end
127 |
--------------------------------------------------------------------------------
/lib/iris/accounts/device.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Device do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | schema "devices" do
6 | field :name, :string
7 | field :password, :string
8 | field :access_token, :string
9 | field :client_id, :string
10 | field :status, :boolean, default: false
11 | field :last_synced, Ecto.DateTime
12 |
13 | belongs_to :user, Iris.User
14 |
15 | timestamps()
16 | end
17 |
18 | @doc """
19 | Builds a changeset based on the `struct` and `params`.
20 | """
21 | def changeset(struct, params \\ %{}) do
22 | struct
23 | |> cast(params, [:name, :password, :client_id, :access_token, :status, :last_synced, :user_id])
24 | |> validate_required([:name, :password, :client_id, :access_token, :user_id])
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/iris/accounts/role.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Role do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | schema "roles" do
6 | field :title, :string
7 | field :admin, :boolean, default: false
8 |
9 | timestamps()
10 | end
11 |
12 | @doc """
13 | Builds a changeset based on the `struct` and `params`.
14 | """
15 | def changeset(struct, params \\ %{}) do
16 | struct
17 | |> cast(params, [:title, :admin])
18 | |> validate_required([:title, :admin])
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/iris/accounts/user.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.User do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | alias Iris.{Repo, Role, User, Device}
6 |
7 | schema "users" do
8 | field :email, :string
9 | field :access_token, :string
10 | field :disabled_at, Ecto.DateTime
11 | field :disabled, :boolean, virtual: true
12 |
13 | belongs_to :role, Role
14 | has_many :devices, Device
15 |
16 | timestamps()
17 | end
18 |
19 | @doc """
20 | Builds a changeset based on the `struct` and `params`.
21 | """
22 | def changeset(%User{} = user, attributes) do
23 | user
24 | |> cast(attributes, [:email, :role_id])
25 | |> validate_required([:email, :role_id])
26 | |> validate_format(:email, ~r/@/)
27 | |> unique_constraint(:email)
28 | end
29 |
30 | @doc """
31 | Check if the user role is admin.
32 | """
33 | def is_admin?(user) do
34 | (role = Repo.get(Role, user.role_id)) && role.admin
35 | end
36 |
37 | @doc """
38 | If the disabled_at is not empty then the user is not active.
39 | """
40 | def is_active?(user) do
41 | user.disabled_at == nil
42 | end
43 |
44 | end
45 |
--------------------------------------------------------------------------------
/lib/iris/application.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Application do
2 | use Application
3 |
4 | def start(_type, _args) do
5 | import Supervisor.Spec
6 |
7 | children = [
8 | supervisor(Iris.Repo, []),
9 | supervisor(IrisWeb.Endpoint, []),
10 | supervisor(Mqtt.Supervisor, [])
11 | ]
12 |
13 | opts = [strategy: :one_for_one, name: Iris.Supervisor]
14 | Supervisor.start_link(children, opts)
15 | end
16 |
17 | def config_change(changed, _new, removed) do
18 | IrisWeb.Endpoint.config_change(changed, removed)
19 | :ok
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/iris/ecto_enums.ex:
--------------------------------------------------------------------------------
1 | import EctoEnum
2 | defenum ServiceTypeEnum, :service_type, [:P, :W, :A, :S, :O]
3 | defenum TokenTypeEnum, :token_type, [:auth, :deviceActivation]
4 |
--------------------------------------------------------------------------------
/lib/iris/mailer.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Mailer do
2 | use Bamboo.Mailer, otp_app: :iris
3 | end
4 |
--------------------------------------------------------------------------------
/lib/iris/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Repo do
2 | use Ecto.Repo, otp_app: :iris
3 | end
4 |
--------------------------------------------------------------------------------
/lib/iris_web.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb 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 IrisWeb, :controller
9 | use IrisWeb, :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: IrisWeb
23 | import Plug.Conn
24 | import IrisWeb.Router.Helpers
25 | import IrisWeb.Gettext
26 | end
27 | end
28 |
29 | def view do
30 | quote do
31 | use Phoenix.View, root: "lib/iris_web/templates", namespace: IrisWeb
32 |
33 | # Import convenience functions from controllers
34 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
35 |
36 | # Use all HTML functionality (forms, tags, etc)
37 | use Phoenix.HTML
38 |
39 | import IrisWeb.Router.Helpers
40 | import IrisWeb.ErrorHelpers
41 | import IrisWeb.Gettext
42 | end
43 | end
44 |
45 | def router do
46 | quote do
47 | use Phoenix.Router
48 | import Plug.Conn
49 | import Phoenix.Controller
50 | end
51 | end
52 |
53 | def channel do
54 | quote do
55 | use Phoenix.Channel
56 | import IrisWeb.Gettext
57 | end
58 | end
59 |
60 | @doc """
61 | When used, dispatch to the appropriate controller/view/etc.
62 | """
63 | defmacro __using__(which) when is_atom(which) do
64 | apply(__MODULE__, which, [])
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/iris_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", Iris.RoomChannel
6 |
7 | ## Transports
8 | transport :websocket, Phoenix.Transports.WebSocket
9 | # transport :longpoll, Phoenix.Transports.LongPoll
10 |
11 | # Socket params are passed from the client and can
12 | # be used to verify and authenticate a user. After
13 | # verification, you can put default assigns into
14 | # the socket that will be set for all channels, ie
15 | #
16 | # {:ok, assign(socket, :user_id, verified_user_id)}
17 | #
18 | # To deny connection, return `:error`.
19 | #
20 | # See `Phoenix.Token` documentation for examples in
21 | # performing token verification on connect.
22 | def connect(_params, socket) do
23 | {:ok, socket}
24 | end
25 |
26 | # Socket id's are topics that allow you to identify all sockets for a given user:
27 | #
28 | # def id(socket), do: "users_socket:#{socket.assigns.user_id}"
29 | #
30 | # Would allow you to broadcast a "disconnect" event and terminate
31 | # all active sockets and channels for a given user:
32 | #
33 | # IrisWeb.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
34 | #
35 | # Returning `nil` makes this socket anonymous.
36 | def id(_socket), do: nil
37 | end
38 |
--------------------------------------------------------------------------------
/lib/iris_web/controllers/device_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.DeviceController do
2 | use IrisWeb, :controller
3 |
4 | alias Iris.{Accounts, Device}
5 |
6 | plug Iris.Plugs.AuthedicateUser
7 |
8 | def index(conn, _params) do
9 | user = get_session(conn, :current_user)
10 | devices = Accounts.list_devices_by_user(user)
11 | render(conn, "index.html", devices: devices)
12 | end
13 |
14 | def new(conn, _params) do
15 | changeset = Accounts.change_device(%Device{status: false})
16 | render(conn, "new.html", changeset: changeset)
17 | end
18 |
19 | def create(conn, %{"device" => device_params}) do
20 | user = get_session(conn, :current_user)
21 | case Accounts.create_device(device_params, user) do
22 | {:ok, _device} ->
23 | conn
24 | |> put_flash(:info, "Device created successfully.")
25 | |> redirect(to: device_path(conn, :index))
26 | {:error, changeset} ->
27 | render(conn, "new.html", changeset: changeset)
28 | end
29 | end
30 |
31 | def show(conn, %{"id" => id}) do
32 | user = get_session(conn, :current_user)
33 | device = Accounts.get_device!(id, user)
34 | render(conn, "show.html", device: device)
35 | end
36 |
37 | def edit(conn, %{"id" => id}) do
38 | user = get_session(conn, :current_user)
39 | device = Accounts.get_device!(id, user)
40 | changeset = Accounts.change_device(device)
41 | render(conn, "edit.html", device: device, changeset: changeset)
42 | end
43 |
44 | def update(conn, %{"id" => id, "device" => device_params}) do
45 | user = get_session(conn, :current_user)
46 | device = Accounts.get_device!(id, user)
47 | case Accounts.update_device(device, device_params) do
48 | {:ok, device} ->
49 | conn
50 | |> put_flash(:info, "Device updated successfully.")
51 | |> redirect(to: device_path(conn, :show, device))
52 | {:error, changeset} ->
53 | render(conn, "edit.html", device: device, changeset: changeset)
54 | end
55 | end
56 |
57 | def delete(conn, %{"id" => id}) do
58 | user = get_session(conn, :current_user)
59 | device = Accounts.get_device!(id, user)
60 | case Accounts.delete_device(device) do
61 | {:ok, _device} ->
62 | conn
63 | |> put_flash(:info, "Device deleted successfully.")
64 | |> redirect(to: device_path(conn, :index))
65 | {:error, _} ->
66 | redirect(conn, to: device_path(conn, :index))
67 | end
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/lib/iris_web/controllers/message_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.MessageController do
2 | use IrisWeb, :controller
3 |
4 | alias Iris.{Message, Repo}
5 |
6 | def index(conn, _params) do
7 | messages = Repo.all(Message)
8 | render(conn, "index.json", messages: messages)
9 | end
10 |
11 | def create(conn, message_params) do
12 | changeset = Message.changeset(%Message{}, message_params)
13 | case Repo.insert(changeset) do
14 | {:ok, message} ->
15 | conn
16 | |> put_status(:created)
17 | |> put_resp_header("location", message_path(conn, :show, message))
18 | |> render("show.json", message: message)
19 | {:error, changeset} ->
20 | conn
21 | |> put_status(:unprocessable_entity)
22 | |> render(IrisWeb.ChangesetView, "error.json", changeset: changeset)
23 | end
24 | end
25 |
26 | def show(conn, %{"id" => id}) do
27 | message = Repo.get!(Message, id)
28 | render(conn, "show.json", message: message)
29 | end
30 |
31 | def update(conn, message_params) do
32 | message = Repo.get!(Message, message_params["id"])
33 | changeset = Message.changeset(message, message_params)
34 |
35 | case Repo.update(changeset) do
36 | {:ok, message} ->
37 | render(conn, "show.json", message: message)
38 | {:error, changeset} ->
39 | conn
40 | |> put_status(:unprocessable_entity)
41 | |> render(IrisWeb.ChangesetView, "error.json", changeset: changeset)
42 | end
43 | end
44 |
45 | def delete(conn, %{"id" => id}) do
46 | message = Repo.get!(Message, id)
47 | Repo.delete!(message)
48 | send_resp(conn, :no_content, "")
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/iris_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.PageController do
2 | use IrisWeb, :controller
3 |
4 | plug Iris.Plugs.AuthedicateUser when action != :index
5 |
6 | def index(conn, _) do
7 | render conn, "index.html"
8 | end
9 |
10 | def dashboard(conn, _) do
11 | render conn, "dashboard.html"
12 | end
13 |
14 | def settings(conn, _) do
15 | render conn, "settings.html"
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/lib/iris_web/controllers/registration_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.RegistrationController do
2 | use IrisWeb, :controller
3 |
4 | alias Iris.{Accounts, User, Mailer}
5 | alias IrisWeb.Email
6 |
7 | def signup(%{method: "GET"} = conn, _params) do
8 | render conn, "signup.html", changeset: Accounts.change_user(%User{})
9 | end
10 |
11 | def signup(%{method: "POST"} = conn, %{"user" => %{"email" => email}}) when is_nil(email) do
12 | conn
13 | |> put_flash(:info, "You have to provide an email address.")
14 | |> redirect(to: registration_path(conn, :signup))
15 | end
16 |
17 | def signup(%{method: "POST"} = conn, %{"user" => user_params}) do
18 | case Accounts.get_user_by_email(user_params["email"]) do
19 | nil -> create_user(conn, user_params)
20 | _ -> succes_signup(conn) # Don't leak which email address is already used
21 | end
22 | end
23 |
24 | defp create_user(conn, user_params) do
25 | case Accounts.create_user(user_params) do
26 | {:ok, user} ->
27 | Email.welcome(user)
28 | |> Mailer.deliver_later
29 | succes_signup(conn)
30 | {:error, changeset} ->
31 | render conn, "signup.html", changeset: changeset
32 | end
33 | end
34 |
35 | defp succes_signup(conn) do
36 | conn
37 | |> put_flash(:info, "You signed up successfully.")
38 | |> redirect(to: session_path(conn, :login))
39 | end
40 |
41 | end
42 |
--------------------------------------------------------------------------------
/lib/iris_web/controllers/service_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ServiceController do
2 | use IrisWeb, :controller
3 |
4 | alias Iris.{Service, Repo}
5 |
6 | plug Iris.Plugs.AuthedicateUser
7 |
8 | def index(conn, _params) do
9 | services = Repo.all(Service)
10 | render(conn, "index.json", services: services)
11 | end
12 |
13 | def create(conn, service_params) do
14 | changeset = Service.changeset(%Service{}, service_params)
15 |
16 | case Repo.insert(changeset) do
17 | {:ok, service} ->
18 | conn
19 | |> put_status(:created)
20 | |> put_resp_header("location", service_path(conn, :show, service))
21 | |> render("show.json", service: service)
22 | {:error, changeset} ->
23 | conn
24 | |> put_status(:unprocessable_entity)
25 | |> render(IrisWeb.ChangesetView, "error.json", changeset: changeset)
26 | end
27 | end
28 |
29 | def show(conn, %{"id" => id}) do
30 | service = Repo.get!(Service, id)
31 | render(conn, "show.json", service: service)
32 | end
33 |
34 | def update(conn, service_params) do
35 | service = Repo.get!(Service, service_params["id"])
36 | changeset = Service.changeset(service, service_params)
37 |
38 | case Repo.update(changeset) do
39 | {:ok, service} ->
40 | render(conn, "show.json", service: service)
41 | {:error, changeset} ->
42 | conn
43 | |> put_status(:unprocessable_entity)
44 | |> render(IrisWeb.ChangesetView, "error.json", changeset: changeset)
45 | end
46 | end
47 |
48 | def delete(conn, %{"id" => id}) do
49 | service = Repo.get!(Service, id)
50 | Repo.delete!(service)
51 | send_resp(conn, :no_content, "")
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/iris_web/controllers/session_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.SessionController do
2 | use IrisWeb, :controller
3 |
4 | alias Iris.{Accounts, User, Support.AuthenticationToken, Repo}
5 |
6 | def login(%{method: "GET"} = conn, %{"t" => token}) do
7 | case AuthenticationToken.verify_token_value(token) do
8 | {:ok, user} ->
9 | user = Iris.User
10 | |> Repo.get(user.id)
11 | |> Repo.preload(:role)
12 | conn
13 | |> assign(:current_user, user)
14 | |> put_session(:current_user, user)
15 | |> configure_session(renew: true)
16 | |> put_flash(:info, "You signed in successfully.")
17 | |> redirect(to: page_path(conn, :dashboard))
18 |
19 | {:error, _reason} ->
20 | conn
21 | |> put_flash(:error, "Invalid token.")
22 | |> redirect(to: session_path(conn, :login))
23 | end
24 | end
25 |
26 | def login(%{method: "GET"} = conn, _) do
27 | render conn, "login.html", changeset: Accounts.change_user(%User{})
28 | end
29 |
30 | def login(%{method: "POST"} = conn, %{"user" => %{"email" => email}}) when is_nil(email) do
31 | conn
32 | |> put_flash(:info, "You have to provide an email address.")
33 | |> redirect(to: session_path(conn, :login))
34 | end
35 |
36 | def login(%{method: "POST"} = conn, %{"user" => %{"email" => email}}) when not is_nil(email) do
37 | AuthenticationToken.send_token(email)
38 | conn
39 | |> put_flash(:info, "Check your inbox.")
40 | |> redirect(to: page_path(conn, :index))
41 | end
42 |
43 | def logout(conn, _) do
44 | conn
45 | |> delete_session(:current_user)
46 | |> put_flash(:info, "Logout successfully!")
47 | |> redirect(to: page_path(conn, :index))
48 | end
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/lib/iris_web/controllers/user_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.UserController do
2 | use IrisWeb, :controller
3 |
4 | alias Iris.{User, Accounts}
5 |
6 | plug Iris.Plugs.AuthedicateUser
7 | plug Iris.Plugs.AuthedicateAdmin
8 |
9 | def index(conn, _params) do
10 | users = Accounts.list_users()
11 | render(conn, "index.html", users: users)
12 | end
13 |
14 | def new(conn, _params) do
15 | changeset = Accounts.change_user(%User{})
16 | render(conn, "new.html", changeset: changeset)
17 | end
18 |
19 | def create(conn, %{"user" => user_params}) do
20 | case Accounts.create_user(user_params) do
21 | {:ok, _user} ->
22 | conn
23 | |> put_flash(:info, "User created successfully.")
24 | |> redirect(to: user_path(conn, :index))
25 | {:error, changeset} ->
26 | render(conn, "new.html", changeset: changeset)
27 | end
28 | end
29 |
30 | def show(conn, %{"id" => id}) do
31 | user = Accounts.get_user!(id)
32 | render(conn, "show.html", user: user)
33 | end
34 |
35 | def edit(conn, %{"id" => id}) do
36 | user = Accounts.get_user!(id)
37 | changeset = Accounts.change_user(user)
38 | render(conn, "edit.html", user: user, changeset: changeset)
39 | end
40 |
41 | def update(conn, %{"id" => id, "user" => user_params}) do
42 | user = Accounts.get_user!(id)
43 | case Accounts.update_user(user, user_params) do
44 | {:ok, user} ->
45 | conn
46 | |> put_flash(:info, "User updated successfully.")
47 | |> redirect(to: user_path(conn, :show, user))
48 | {:error, changeset} ->
49 | render(conn, "edit.html", user: user, changeset: changeset)
50 | end
51 | end
52 |
53 | def delete(conn, %{"id" => id}) do
54 | user = Accounts.get_user!(id)
55 | {:ok, _user} = Accounts.delete_user(user)
56 |
57 | conn
58 | |> put_flash(:info, "User deleted successfully.")
59 | |> redirect(to: user_path(conn, :index))
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/iris_web/email.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.Email do
2 |
3 | use Bamboo.Phoenix, view: IrisWeb.EmailView
4 |
5 | @doc """
6 | Email containing the magic login.
7 | """
8 | def login_link(auth_token, user) do
9 | hostname = get_host_name()
10 | new_email()
11 | |> to(user.email)
12 | |> from("no-reply@#{hostname}")
13 | |> subject("Login link")
14 | |> put_html_layout({IrisWeb.LayoutView, "email.html"})
15 | |> render(:login, token: auth_token)
16 | |> put_text_layout({IrisWeb.LayoutView, "email.text"})
17 | |> render(:login, token: auth_token)
18 | end
19 |
20 | @doc """
21 | Welcoming email.
22 | """
23 | def welcome(user) do
24 | hostname = get_host_name()
25 | new_email()
26 | |> to(user.email)
27 | |> from("no-reply@#{hostname}")
28 | |> subject("Welcome")
29 | |> put_html_layout({IrisWeb.LayoutView, "email.html"})
30 | |> render(:welcome, user: user)
31 | |> put_text_layout({IrisWeb.LayoutView, "email.text"})
32 | |> render(:welcome, user: user)
33 | end
34 |
35 | @doc false
36 | defp get_host_name() do
37 | [{:url, [{:host, hostname} | _]} | _] = Application.get_env(:iris, IrisWeb.Endpoint)
38 | hostname
39 | end
40 |
41 | end
42 |
--------------------------------------------------------------------------------
/lib/iris_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :iris
3 |
4 | socket "/socket", IrisWeb.UserSocket
5 |
6 | # Serve at "/" the static files from "priv/static" directory.
7 | #
8 | # You should set gzip to true if you are running phoenix.digest
9 | # when deploying your static files in production.
10 | plug Plug.Static,
11 | at: "/", from: :iris, gzip: false,
12 | only: ~w(css fonts images js favicon.ico robots.txt)
13 |
14 | # Code reloading can be explicitly enabled under the
15 | # :code_reloader configuration of your endpoint.
16 | if code_reloading? do
17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
18 | plug Phoenix.LiveReloader
19 | plug Phoenix.CodeReloader
20 | end
21 |
22 | plug Plug.RequestId
23 | plug Plug.Logger
24 |
25 | plug Plug.Parsers,
26 | parsers: [:urlencoded, :multipart, :json],
27 | pass: ["*/*"],
28 | json_decoder: Poison
29 |
30 | plug Plug.MethodOverride
31 | plug Plug.Head
32 |
33 | # The session will be stored in the cookie and signed,
34 | # this means its contents can be read but not tampered with.
35 | # Set :encryption_salt if you would also like to encrypt it.
36 | plug Plug.Session,
37 | store: :cookie,
38 | key: "_iris_key",
39 | signing_salt: "gjqVZk90"
40 |
41 | plug IrisWeb.Router
42 |
43 | @doc """
44 | Callback invoked for dynamically configuring the endpoint.
45 |
46 | It receives the endpoint configuration and checks if
47 | configuration should be loaded from the system environment.
48 | """
49 | def init(_key, config) do
50 | if config[:load_from_system_env] do
51 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
52 | {:ok, Keyword.put(config, :http, [:inet6, port: port])}
53 | else
54 | {:ok, config}
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/iris_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.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 Iris.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: :iris
24 | end
25 |
--------------------------------------------------------------------------------
/lib/iris_web/models/message.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Message do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | schema "messages" do
6 | field :title, :string
7 | field :text, :string
8 | field :priority, :integer
9 | field :link, :string
10 | field :service_token, :string
11 |
12 | timestamps()
13 | end
14 |
15 | @doc """
16 | Builds a changeset based on the `struct` and `params`.
17 | """
18 | def changeset(struct, params \\ %{}) do
19 | struct
20 | |> cast(params, [:title, :text, :priority, :link, :service_token])
21 | |> validate_required([:title, :text, :priority, :link, :service_token])
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/iris_web/models/service.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Service do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | schema "services" do
6 | field :name, :string
7 | field :description, :string
8 | field :icon, :string
9 | field :token, :string
10 | field :type, ServiceTypeEnum
11 |
12 | timestamps()
13 | end
14 |
15 | @doc """
16 | Builds a changeset based on the `struct` and `params`.
17 | """
18 | def changeset(struct, params \\ %{}) do
19 | struct
20 | |> cast(params, [:name, :description, :icon, :token, :type])
21 | |> validate_required([:name, :description, :icon, :token, :type])
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/iris_web/models/token.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Token do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | alias Iris.User
6 | alias IrisWeb.Endpoint
7 |
8 | schema "tokens" do
9 | field :value, :string
10 | field :type, TokenTypeEnum
11 |
12 | belongs_to :user, User
13 |
14 | timestamps()
15 | end
16 |
17 |
18 |
19 | @doc """
20 | Creates an token for the specificed user
21 | """
22 | def create_auth_token(struct, user) do
23 | struct
24 | |> cast(%{}, [])
25 | |> put_assoc(:user, user)
26 | |> put_change(:value, generate_auth_token(user))
27 | |> put_change(:type, "auth")
28 | |> changeset
29 | end
30 |
31 | @doc """
32 | Creates an device token for the specificed user
33 | """
34 | def create_device_token(struct, user) do
35 | struct
36 | |> cast(%{}, [])
37 | |> put_assoc(:user, user)
38 | |> put_change(:value, generate_device_token())
39 | |> put_change(:type, "device")
40 | |> changeset
41 | end
42 |
43 | def changeset(struct, params \\ %{}) do
44 | struct
45 | |> validate_required([:value, :user, :type])
46 | |> unique_constraint(:value)
47 | end
48 |
49 | defp generate_device_token() do
50 | :crypto.strong_rand_bytes(8) |> Base.url_encode64 |> binary_part(0, 8)
51 | end
52 |
53 | defp generate_auth_token(nil), do: nil
54 | defp generate_auth_token(user) do
55 | Phoenix.Token.sign(Endpoint, "user", user.id)
56 | end
57 |
58 | end
59 |
--------------------------------------------------------------------------------
/lib/iris_web/plugs/authedicate_admin.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Plugs.AuthedicateAdmin do
2 | import Plug.Conn
3 |
4 | alias Iris.User
5 |
6 | def init(default), do: default
7 |
8 | def call(conn, _default) do
9 | user = get_session(conn, :current_user)
10 | cond do
11 | user != nil && User.is_admin?(user) -> conn
12 | user != nil -> conn |> send_resp(403, "Forbidden") |> halt()
13 | true -> conn |> send_resp(401, "Unauthorized") |> halt()
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/iris_web/plugs/authedicate_user.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Plugs.AuthedicateUser do
2 | import Plug.Conn
3 |
4 | alias Iris.User
5 |
6 | def init(default), do: default
7 |
8 | def call(conn, _default) do
9 | user = get_session(conn, :current_user)
10 | cond do
11 | user && User.is_active?(user) -> conn
12 | user != nil -> conn |> send_resp(403, "Forbidden") |> halt()
13 | true -> conn |> send_resp(401, "Unauthorized") |> halt()
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/iris_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.Router do
2 | use IrisWeb, :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 | plug :fetch_session
15 | end
16 |
17 | if Mix.env == :dev do
18 | forward "/sent_emails", Bamboo.EmailPreviewPlug
19 | end
20 |
21 | scope "/", IrisWeb do
22 | pipe_through :browser
23 |
24 | get "/", PageController, :index
25 | get "/dashboard", PageController, :dashboard
26 | get "/settings", PageController, :settings
27 | resources "/users", UserController
28 | resources "/devices", DeviceController
29 |
30 | get "/signup", RegistrationController, :signup
31 | post "/signup", RegistrationController, :signup
32 |
33 | get "/login", SessionController, :login
34 | post "/login", SessionController, :login
35 | get "/logout", SessionController, :logout
36 | end
37 |
38 | scope "/api", IrisWeb do
39 | pipe_through :api
40 |
41 | resources "/services", ServiceController, except: [:new, :edit]
42 | resources "/messages", MessageController
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/iris_web/support/AuthedicationToken.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.Support.AuthenticationToken do
2 |
3 | import Ecto.Query, only: [where: 3]
4 |
5 | alias Iris.{Token, Mailer, Repo, User}
6 | alias IrisWeb.{Endpoint, Email}
7 |
8 | @token_max_age 1800
9 |
10 | def create_token(user) do
11 | changeset = Token.create_auth_token(%Token{}, user)
12 | auth_token = Repo.insert!(changeset)
13 | auth_token.value
14 | end
15 |
16 | def verify_token_value(value) do
17 | Token
18 | |> where([t], t.value == ^value)
19 | |> where([t], t.inserted_at > datetime_add(^Ecto.DateTime.utc, ^(@token_max_age * -1), "second"))
20 | |> where([t], t.type == "auth")
21 | |> Repo.one()
22 | |> verify_token()
23 | end
24 |
25 | defp verify_token(nil), do: {:error, :invalid}
26 | defp verify_token(token) do
27 | token =
28 | token
29 | |> Repo.preload(:user)
30 | |> Repo.delete!
31 |
32 | user_id = token.user.id
33 | case Phoenix.Token.verify(Endpoint, "user", token.value, max_age: @token_max_age) do
34 | {:ok, ^user_id} -> {:ok, token.user}
35 | {:error, reason} -> {:error, reason}
36 | end
37 | end
38 |
39 | def send_token(nil), do: {:error, :not_found}
40 |
41 | def send_token(email) when is_binary(email) do
42 | User
43 | |> Repo.get_by(email: email)
44 | |> send_token()
45 | end
46 |
47 | def send_token(user = %User{}) do
48 | mail_token(user)
49 | end
50 |
51 | def mail_token(nil), do: {:error, :not_found}
52 |
53 | def mail_token(user) do
54 | user
55 | |> create_token()
56 | |> Email.login_link(user)
57 | |> Mailer.deliver_now()
58 | :ok
59 | end
60 |
61 | end
62 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/device/edit.html.eex:
--------------------------------------------------------------------------------
1 |
Edit device
2 |
3 | <%= render "form.html", changeset: @changeset,
4 | action: device_path(@conn, :update, @device) %>
5 |
6 | <%= link "Back", to: device_path(@conn, :index) %>
7 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/device/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
9 | <%= label f, :name, class: "control-label" %>
10 | <%= text_input f, :name, class: "form-control" %>
11 | <%= error_tag f, :name %>
12 |
13 |
14 |
15 | <%= label f, :status, class: "control-label" %>
16 | <%= checkbox f, :status, class: "form-control" %>
17 | <%= error_tag f, :status %>
18 |
19 |
20 |
21 | <%= submit "Submit", class: "btn btn-primary" %>
22 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/device/index.html.eex:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 | Name |
10 | Client Id |
11 | Status |
12 | Last synced |
13 |
14 | |
15 |
16 |
17 |
18 | <%= for device <- @devices do %>
19 |
20 | <%= device.name %> |
21 | <%= device.client_id %> |
22 | <%= device.status %> |
23 | <%= device.last_synced %> |
24 |
25 |
26 | <%= link "Show", to: device_path(@conn, :show, device), class: "btn btn-default btn-xs" %>
27 | <%= link "Edit", to: device_path(@conn, :edit, device), class: "btn btn-default btn-xs" %>
28 | <%= link "Delete", to: device_path(@conn, :delete, device), method: :delete, class: "btn btn-danger btn-xs" %>
29 | |
30 |
31 | <% end %>
32 |
33 |
34 | <%= link "New device", to: device_path(@conn, :new) %>
35 |
36 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/device/new.html.eex:
--------------------------------------------------------------------------------
1 | New device
2 |
3 | <%= render "form.html", changeset: @changeset,
4 | action: device_path(@conn, :create) %>
5 |
6 | <%= link "Back", to: device_path(@conn, :index) %>
7 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/device/show.html.eex:
--------------------------------------------------------------------------------
1 | Show device
2 |
3 |
4 |
5 | -
6 | Name:
7 | <%= @device.name %>
8 |
9 |
10 | -
11 | Client id:
12 | <%= @device.client_id %>
13 |
14 |
15 | -
16 | Access Token:
17 | <%= @device.access_token %>
18 |
19 |
20 | -
21 | Status:
22 | <%= @device.status %>
23 |
24 |
25 | -
26 | Last synced:
27 | <%= @device.last_synced %>
28 |
29 |
30 |
31 |
32 | <%= link "Edit", to: device_path(@conn, :edit, @device) %>
33 | <%= link "Back", to: device_path(@conn, :index) %>
34 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/email/login.html.eex:
--------------------------------------------------------------------------------
1 | Follow this link <%= link "link", to: session_url(IrisWeb.Endpoint, :login, t: @token) %> to login to your account.
2 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/email/login.text.eex:
--------------------------------------------------------------------------------
1 | Follow this link <%= session_url(IrisWeb.Endpoint, :login, t: @token) %> to login to your account.
2 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/email/welcome.html.eex:
--------------------------------------------------------------------------------
1 |
2 | Welcome to Iris!
3 |
4 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/email/welcome.text.eex:
--------------------------------------------------------------------------------
1 | Welcome to Iris!
2 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Iris
11 | ">
12 |
13 |
14 | <%= render "nav.html", assigns %>
15 |
16 | <%= render "messages.html", assigns %>
17 |
18 | <%= render @view_module, @view_template, assigns %>
19 |
20 | <%= render "footer.html", assigns %>
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/layout/email.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Hello!
9 | <%= render @view_module, @view_template, assigns %>
10 | - The Iris Team.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/layout/email.text.eex:
--------------------------------------------------------------------------------
1 | Hello!
2 |
3 | <%= render @view_module, @view_template, assigns %>
4 |
5 | - The Iris Team.
6 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/layout/footer.html.eex:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/layout/messages.html.eex:
--------------------------------------------------------------------------------
1 |
2 | <%= if info = get_flash(@conn, :info) do %>
3 |
4 |
5 |
6 |
Success!
7 |
<%= info %>
8 |
9 |
10 | <% end %>
11 | <% if error = get_flash(@conn, :error) do %>
12 |
13 |
14 |
15 |
Error!
16 |
<%= error %>
17 |
18 |
19 | <% end %>
20 |
21 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/layout/nav.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
Iris
8 |
9 | <%
10 | user = current_user(@conn)
11 | %>
12 |
13 | <%= if user do %>
14 | -
15 | <%= link "Message", to: page_path(@conn, :dashboard), class: "nav-link" %>
16 |
17 | -
18 | <%= link "Devices", to: device_path(@conn, :index), class: "nav-link" %>
19 |
20 | -
21 | <%= link "Applications", to: service_path(@conn, :index), class: "nav-link" %>
22 |
23 | <%= if current_user_is_admin?(@conn) do %>
24 | -
25 | <%= link "Users", to: user_path(@conn, :index), class: "nav-link" %>
26 |
27 | -
28 | <%= link "Settings", to: page_path(@conn, :settings), class: "nav-link" %>
29 |
30 | <% end %>
31 | -
32 | <%= link "Logout", to: session_path(@conn, :logout), class: "nav-link" %>
33 |
34 | <% else %>
35 | -
36 | <%= link "Login", to: session_path(@conn, :login), class: "nav-link" %>
37 |
38 | -
39 | <%= link "Signup", to: registration_path(@conn, :signup), class: "nav-link" %>
40 |
41 | <% end %>
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/page/dashboard.html.eex:
--------------------------------------------------------------------------------
1 |
2 | <%= render "notification_form.html" %>
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/page/devices.html.eex:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | # |
9 | Name |
10 | Status |
11 | Last Synced |
12 |
13 |
14 |
15 |
16 | 1 |
17 | blabla |
18 | disabled |
19 | about 13 hours ago |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | Download the android client here...
9 |
10 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/page/notification_form.html.eex:
--------------------------------------------------------------------------------
1 |
37 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/page/services.html.eex:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | # |
9 | Name |
10 | Description |
11 |
12 |
13 |
14 |
15 | 1 |
16 | SSH |
17 | open ssh |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/page/settings.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/registration/signup.html.eex:
--------------------------------------------------------------------------------
1 | Signup
2 |
3 | <%= form_for @conn, registration_path(@conn, :signup), [as: :user], fn f -> %>
4 |
5 | <%= label f, :email, class: "control-label" %>
6 | <%= text_input f, :email, class: "form-control", autofocus: true %>
7 | <%= error_tag f, :email %>
8 |
9 |
10 |
11 | <%= submit "Signup", class: "btn btn-primary" %>
12 |
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/session/login.html.eex:
--------------------------------------------------------------------------------
1 | Login
2 |
3 | <%= form_for @conn, session_path(@conn, :login), [as: :user], fn f -> %>
4 |
5 | <%= label f, :email, class: "control-label" %>
6 | <%= text_input f, :email, class: "form-control", autofocus: true %>
7 | <%= error_tag f, :email %>
8 |
9 |
10 |
11 | <%= submit "Login", class: "btn btn-primary" %>
12 |
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/user/edit.html.eex:
--------------------------------------------------------------------------------
1 | Edit user
2 |
3 | <%= render "form.html", changeset: @changeset,
4 | action: user_path(@conn, :update, @user) %>
5 |
6 | <%= link "Back", to: user_path(@conn, :index) %>
7 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/user/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
9 | <%= label f, :email, class: "control-label" %>
10 | <%= text_input f, :email, class: "form-control" %>
11 | <%= error_tag f, :email %>
12 |
13 |
14 |
15 | <%= label f, :disabled_at, class: "control-label" %>
16 | <%= datetime_select f, :disabled_at, class: "form-control" %>
17 | <%= error_tag f, :disabled_at %>
18 |
19 |
20 |
21 | <%= submit "Submit", class: "btn btn-primary" %>
22 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/user/index.html.eex:
--------------------------------------------------------------------------------
1 | Listing users
2 |
3 |
4 |
5 |
6 | Email |
7 | Disabled at |
8 |
9 | |
10 |
11 |
12 |
13 | <%= for user <- @users do %>
14 |
15 | <%= user.email %> |
16 | <%= user.disabled_at %> |
17 |
18 |
19 | <%= link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs" %>
20 | <%= link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs" %>
21 | <%= link "Delete", to: user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %>
22 | |
23 |
24 | <% end %>
25 |
26 |
27 |
28 | <%= link "New user", to: user_path(@conn, :new) %>
29 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/user/new.html.eex:
--------------------------------------------------------------------------------
1 | New user
2 |
3 | <%= render "form.html", changeset: @changeset,
4 | action: user_path(@conn, :create) %>
5 |
6 | <%= link "Back", to: user_path(@conn, :index) %>
7 |
--------------------------------------------------------------------------------
/lib/iris_web/templates/user/show.html.eex:
--------------------------------------------------------------------------------
1 | Show user
2 |
3 |
4 |
5 | -
6 | Email:
7 | <%= @user.email %>
8 |
9 |
10 | -
11 | Disabled at:
12 | <%= @user.disabled_at %>
13 |
14 |
15 |
16 |
17 | <%= link "Edit", to: user_path(@conn, :edit, @user) %>
18 | <%= link "Back", to: user_path(@conn, :index) %>
19 |
--------------------------------------------------------------------------------
/lib/iris_web/views/changeset_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ChangesetView do
2 | use IrisWeb, :view
3 |
4 | @doc """
5 | Traverses and translates changeset errors.
6 |
7 | See `Ecto.Changeset.traverse_errors/2` and
8 | `Iris.ErrorHelpers.translate_error/1` for more details.
9 | """
10 | def translate_errors(changeset) do
11 | Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
12 | end
13 |
14 | def render("error.json", %{changeset: changeset}) do
15 | # When encoded, the changeset returns its errors
16 | # as a JSON object. So we just pass it forward.
17 | %{errors: translate_errors(changeset)}
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/iris_web/views/device_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.DeviceView do
2 | use IrisWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/iris_web/views/email_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.EmailView do
2 | use IrisWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/iris_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | if error = form.errors[field] do
13 | content_tag :span, translate_error(error), class: "help-block"
14 | end
15 | end
16 |
17 | @doc """
18 | Translates an error message using gettext.
19 | """
20 | def translate_error({msg, opts}) do
21 | # Because error messages were defined within Ecto, we must
22 | # call the Gettext module passing our Gettext backend. We
23 | # also use the "errors" domain as translations are placed
24 | # in the errors.po file.
25 | # Ecto will pass the :count keyword if the error message is
26 | # meant to be pluralized.
27 | # On your own code and templates, depending on whether you
28 | # need the message to be pluralized or not, this could be
29 | # written simply as:
30 | #
31 | # dngettext "errors", "1 file", "%{count} files", count
32 | # dgettext "errors", "is invalid"
33 | #
34 | if count = opts[:count] do
35 | Gettext.dngettext(IrisWeb.Gettext, "errors", msg, msg, count, opts)
36 | else
37 | Gettext.dgettext(IrisWeb.Gettext, "errors", msg, opts)
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/iris_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ErrorView do
2 | use IrisWeb, :view
3 |
4 | def render("404.html", _assigns) do
5 | "Page not found"
6 | end
7 |
8 | def render("500.html", _assigns) do
9 | "Internal server error"
10 | end
11 |
12 | # In case no render clause matches or no
13 | # template is found, let's render it as 500
14 | def template_not_found(_template, assigns) do
15 | render "500.html", assigns
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/iris_web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.LayoutView do
2 | use IrisWeb, :view
3 |
4 | alias Iris.User
5 |
6 | def current_user(conn) do
7 | Plug.Conn.get_session(conn, :current_user)
8 | end
9 |
10 | def current_user_is_admin?(conn) do
11 | user = current_user(conn)
12 | User.is_admin?(user)
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/lib/iris_web/views/message_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.MessageView do
2 | use IrisWeb, :view
3 |
4 | def render("index.json", %{messages: messages}) do
5 | %{data: render_many(messages, IrisWeb.MessageView, "message.json")}
6 | end
7 |
8 | def render("show.json", %{message: message}) do
9 | render_one(message, IrisWeb.MessageView, "message.json")
10 | end
11 |
12 | def render("message.json", %{message: message}) do
13 | %{id: message.id,
14 | title: message.title,
15 | text: message.text,
16 | priority: message.priority,
17 | link: message.link,
18 | service_token: message.service_token}
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/iris_web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.PageView do
2 | use IrisWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/iris_web/views/registration_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.RegistrationView do
2 | use IrisWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/iris_web/views/service_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ServiceView do
2 | use IrisWeb, :view
3 |
4 | def render("index.json", %{services: services}) do
5 | %{data: render_many(services, IrisWeb.ServiceView, "service.json")}
6 | end
7 |
8 | def render("show.json", %{service: service}) do
9 | render_one(service, IrisWeb.ServiceView, "service.json")
10 | end
11 |
12 | def render("service.json", %{service: service}) do
13 | %{id: service.id,
14 | name: service.name,
15 | description: service.description,
16 | icon: service.icon,
17 | token: service.token,
18 | type: service.type}
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/iris_web/views/session_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.SessionView do
2 | use IrisWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/iris_web/views/user_view.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.UserView do
2 | use IrisWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/mqtt/messenger.ex:
--------------------------------------------------------------------------------
1 | defmodule Mqtt.Messenger do
2 | use GenServer
3 | alias Hulaaki.Connection
4 | alias Hulaaki.Message
5 | require Logger
6 |
7 | # Public API
8 |
9 | def start_link(state \\ %{}) do
10 | GenServer.start_link(__MODULE__, state, name: __MODULE__)
11 | end
12 |
13 | def ping, do: GenServer.call(__MODULE__, :ping)
14 | def publish(opts), do: GenServer.call(__MODULE__, {:publish, opts})
15 |
16 | ## GenServer API
17 |
18 | def init(%{} = state) do
19 | {:ok, conn_pid} = Connection.start_link(self())
20 | configuration = Application.get_env(:iris, Mqtt.Messenger)
21 | host = configuration |> Keyword.fetch!(:host)
22 | port = configuration |> Keyword.fetch!(:port)
23 | timeout = configuration |> Keyword.get(:timeout, 100)
24 |
25 | client_id = configuration |> Keyword.fetch!(:client_id)
26 | username = configuration |> Keyword.get(:username, "")
27 | password = configuration |> Keyword.get(:password, "")
28 | will_topic = configuration |> Keyword.get(:will_topic, "")
29 | will_message = configuration |> Keyword.get(:will_message, "")
30 | will_qos = configuration |> Keyword.get(:will_qos, 0)
31 | will_retain = configuration |> Keyword.get(:will_retain, 0)
32 | clean_session = configuration |> Keyword.get(:clean_session, 1)
33 | keep_alive = configuration |> Keyword.get(:keep_alive, 100)
34 |
35 | message = Message.connect(client_id, username, password,
36 | will_topic, will_message, will_qos,
37 | will_retain, clean_session, keep_alive)
38 |
39 | state = Map.merge(%{connection: conn_pid}, state)
40 |
41 | connect_opts = [host: host, port: port, timeout: timeout]
42 |
43 | case Connection.connect(state.connection, message, connect_opts) do
44 | :ok ->
45 | Logger.info "Connection to mqtt established"
46 | {:ok, state}
47 | {:error, _error} ->
48 | Logger.info "Cannot establish connection with mqtt"
49 | {:ok, state}
50 | end
51 | end
52 |
53 | def handle_call({:publish, opts}, _from, state) do
54 | topic = opts |> Keyword.fetch!(:topic)
55 | msg = opts |> Keyword.fetch!(:message)
56 | dup = opts |> Keyword.fetch!(:dup)
57 | qos = opts |> Keyword.fetch!(:qos)
58 | retain = opts |> Keyword.fetch!(:retain)
59 |
60 | message =
61 | case qos do
62 | 0 ->
63 | Message.publish(topic, msg, dup, qos, retain)
64 | _ ->
65 | id = opts |> Keyword.fetch!(:id)
66 | Message.publish(id, topic, msg, dup, qos, retain)
67 | end
68 |
69 | :ok = state.connection |> Connection.publish(message)
70 | {:reply, :ok, state}
71 | end
72 |
73 | def handle_call(:ping, _from, state) do
74 | :ok = state.connection |> Connection.ping
75 | {:reply, :ok, state}
76 | end
77 |
78 | def handle_info({:sent, %Message.Connect{} = message}, state) do
79 | {:noreply, state}
80 | end
81 |
82 | def handle_info({:received, %Message.ConnAck{} = message}, state) do
83 | {:noreply, state}
84 | end
85 |
86 | def handle_info({:sent, %Message.Publish{} = message}, state) do
87 | {:noreply, state}
88 | end
89 |
90 | def handle_info({:received, %Message.Publish{qos: qos} = message}, state) do
91 | case qos do
92 | 1 ->
93 | message = Message.publish_ack message.id
94 | :ok = state.connection |> Connection.publish_ack(message)
95 | _ ->
96 | :droped
97 | end
98 |
99 | {:noreply, state}
100 | end
101 |
102 | def handle_info({:sent, %Message.PubAck{} = message}, state) do
103 | {:noreply, state}
104 | end
105 |
106 | def handle_info({:received, %Message.PubRec{} = message}, state) do
107 | message = Message.publish_release message.id
108 | :ok = state.connection |> Connection.publish_release(message)
109 | {:noreply, state}
110 | end
111 |
112 | def handle_info({:sent, %Message.PubRel{} = message}, state) do
113 | {:noreply, state}
114 | end
115 |
116 | def handle_info({:received, %Message.PubComp{} = message}, state) do
117 | {:noreply, state}
118 | end
119 |
120 | def handle_info({:received, %Message.PubAck{} = message}, state) do
121 | {:noreply, state}
122 | end
123 |
124 | def handle_info({:sent, %Message.Subscribe{} = message}, state) do
125 | {:noreply, state}
126 | end
127 |
128 | def handle_info({:received, %Message.SubAck{} = message}, state) do
129 | {:noreply, state}
130 | end
131 |
132 | def handle_info({:sent, %Message.Unsubscribe{} = message}, state) do
133 | {:noreply, state}
134 | end
135 |
136 | def handle_info({:received, %Message.UnsubAck{} = message}, state) do
137 | {:noreply, state}
138 | end
139 |
140 | def handle_info({:sent, %Message.PingReq{} = message}, state) do
141 | {:noreply, state}
142 | end
143 |
144 | def handle_info({:received, %Message.PingResp{} = message}, state) do
145 | {:noreply, state}
146 | end
147 |
148 | end
149 |
--------------------------------------------------------------------------------
/lib/mqtt/supervisor.ex:
--------------------------------------------------------------------------------
1 | defmodule Mqtt.Supervisor do
2 | use Supervisor
3 |
4 | def start_link do
5 | Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
6 | end
7 |
8 | def init(:ok) do
9 | children = [
10 | worker(Mqtt.Messenger, [])
11 | ]
12 |
13 | supervise(children, strategy: :one_for_one)
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :iris,
6 | version: "0.0.1",
7 | elixir: "~> 1.5",
8 | elixirc_paths: elixirc_paths(Mix.env),
9 | compilers: [:phoenix, :gettext] ++ Mix.compilers,
10 | build_embedded: Mix.env == :prod,
11 | start_permanent: Mix.env == :prod,
12 | aliases: aliases(),
13 | deps: deps(),
14 | test_coverage: [tool: ExCoveralls],
15 | preferred_cli_env: ["coveralls": :test, "coveralls.detail": :test, "coveralls.post": :test, "coveralls.html": :test]]
16 | end
17 |
18 | # Configuration for the OTP application.
19 | #
20 | # Type `mix help compile.app` for more information.
21 | def application do
22 | [mod: {Iris.Application, []},
23 | applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
24 | :phoenix_ecto, :postgrex, :bamboo]]
25 | end
26 |
27 | # Specifies which paths to compile per environment.
28 | defp elixirc_paths(:test), do: ["lib", "test/support"]
29 | defp elixirc_paths(_), do: ["lib"]
30 |
31 | # Specifies your project dependencies.
32 | #
33 | # Type `mix help deps` for examples and options.
34 | defp deps do
35 | [{:phoenix, "~> 1.3.0"},
36 | {:phoenix_pubsub, "~> 1.0"},
37 | {:phoenix_ecto, "~> 3.2"},
38 | {:ecto_enum, "~> 1.0"},
39 | {:postgrex, ">= 0.0.0"},
40 | {:phoenix_html, "~> 2.10"},
41 | {:phoenix_live_reload, "~> 1.0", only: :dev},
42 | {:gettext, "~> 0.11"},
43 | {:cowboy, "~> 1.0"},
44 | {:excoveralls, "~> 0.6", only: :test},
45 | {:hulaaki, git: "git://github.com/suvash/hulaaki", ref: "298a5af"},
46 | {:bamboo, "~> 0.8"},
47 | {:atomic_map, "~> 0.8"}]
48 | end
49 |
50 | # Aliases are shortcuts or tasks specific to the current project.
51 | # For example, to create, migrate and run the seeds file at once:
52 | #
53 | # $ mix ecto.setup
54 | #
55 | # See the documentation for `Mix` for more info on aliases.
56 | defp aliases do
57 | ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
58 | "ecto.reset": ["ecto.drop", "ecto.setup"],
59 | "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"atomic_map": {:hex, :atomic_map, "0.9.2", "a3248fef5195788429aadb5968126fdaead2bc3010be620588b185a56f954192", [], [], "hexpm"},
2 | "bamboo": {:hex, :bamboo, "0.8.0", "573889a3efcb906bb9d25a1c4caa4ca22f479235e1b8cc3260d8b88dabeb4b14", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
3 | "certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], [], "hexpm"},
4 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
5 | "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
6 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
7 | "db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
8 | "decimal": {:hex, :decimal, "1.4.0", "fac965ce71a46aab53d3a6ce45662806bdd708a4a95a65cde8a12eb0124a1333", [:mix], [], "hexpm"},
9 | "ecto": {:hex, :ecto, "2.1.4", "d1ba932813ec0e0d9db481ef2c17777f1cefb11fc90fa7c142ff354972dfba7e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
10 | "ecto_enum": {:hex, :ecto_enum, "1.0.1", "7853cd36b2063c1e2ba2a2e2987dd4745afa1ab1e24b16f6dba3b6bdbd4c907b", [:mix], [{:ecto, "~> 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
11 | "excoveralls": {:hex, :excoveralls, "0.7.0", "05cb3332c2b0f799df3ab90eb7df1ae5a147c86776e91792848a12b7ed87242f", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
12 | "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
13 | "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"},
14 | "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], [], "hexpm"},
15 | "hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
16 | "hulaaki": {:git, "git://github.com/suvash/hulaaki", "298a5afba75f1af576846016cda36f2a3feae6e6", [ref: "298a5af"]},
17 | "idna": {:hex, :idna, "5.0.2", "ac203208ada855d95dc591a764b6e87259cb0e2a364218f215ad662daa8cd6b4", [:rebar3], [{:unicode_util_compat, "0.2.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
18 | "jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm"},
19 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
20 | "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"},
21 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
22 | "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
23 | "phoenix_ecto": {:hex, :phoenix_ecto, "3.2.3", "450c749876ff1de4a78fdb305a142a76817c77a1cd79aeca29e5fc9a6c630b26", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
24 | "phoenix_html": {:hex, :phoenix_html, "2.10.3", "432dec9e04372eeb7a6dbf59a778cf43eb44518441932217371fa535f2bcfd80", [], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
25 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
26 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"},
27 | "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
28 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
29 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
30 | "postgrex": {:hex, :postgrex, "0.13.3", "c277cfb2a9c5034d445a722494c13359e361d344ef6f25d604c2353185682bfc", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
31 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
32 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
33 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [:rebar3], [], "hexpm"},
34 | "uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [], [], "hexpm"}}
35 |
--------------------------------------------------------------------------------
/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
13 | ## From Ecto.Changeset.cast/4
14 | msgid "can't be blank"
15 | msgstr ""
16 |
17 | ## From Ecto.Changeset.unique_constraint/3
18 | msgid "has already been taken"
19 | msgstr ""
20 |
21 | ## From Ecto.Changeset.put_change/3
22 | msgid "is invalid"
23 | msgstr ""
24 |
25 | ## From Ecto.Changeset.validate_format/3
26 | msgid "has invalid format"
27 | msgstr ""
28 |
29 | ## From Ecto.Changeset.validate_subset/3
30 | msgid "has an invalid entry"
31 | msgstr ""
32 |
33 | ## From Ecto.Changeset.validate_exclusion/3
34 | msgid "is reserved"
35 | msgstr ""
36 |
37 | ## From Ecto.Changeset.validate_confirmation/3
38 | msgid "does not match confirmation"
39 | msgstr ""
40 |
41 | ## From Ecto.Changeset.no_assoc_constraint/3
42 | msgid "is still associated to this entry"
43 | msgstr ""
44 |
45 | msgid "are still associated to this entry"
46 | msgstr ""
47 |
48 | ## From Ecto.Changeset.validate_length/3
49 | msgid "should be %{count} character(s)"
50 | msgid_plural "should be %{count} character(s)"
51 | msgstr[0] ""
52 | msgstr[1] ""
53 |
54 | msgid "should have %{count} item(s)"
55 | msgid_plural "should have %{count} item(s)"
56 | msgstr[0] ""
57 | msgstr[1] ""
58 |
59 | msgid "should be at least %{count} character(s)"
60 | msgid_plural "should be at least %{count} character(s)"
61 | msgstr[0] ""
62 | msgstr[1] ""
63 |
64 | msgid "should have at least %{count} item(s)"
65 | msgid_plural "should have at least %{count} item(s)"
66 | msgstr[0] ""
67 | msgstr[1] ""
68 |
69 | msgid "should be at most %{count} character(s)"
70 | msgid_plural "should be at most %{count} character(s)"
71 | msgstr[0] ""
72 | msgstr[1] ""
73 |
74 | msgid "should have at most %{count} item(s)"
75 | msgid_plural "should have at most %{count} item(s)"
76 | msgstr[0] ""
77 | msgstr[1] ""
78 |
79 | ## From Ecto.Changeset.validate_number/3
80 | msgid "must be less than %{number}"
81 | msgstr ""
82 |
83 | msgid "must be greater than %{number}"
84 | msgstr ""
85 |
86 | msgid "must be less than or equal to %{number}"
87 | msgstr ""
88 |
89 | msgid "must be greater than or equal to %{number}"
90 | msgstr ""
91 |
92 | msgid "must be equal to %{number}"
93 | msgstr ""
94 |
--------------------------------------------------------------------------------
/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This file is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here as no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 | ## From Ecto.Changeset.cast/4
12 | msgid "can't be blank"
13 | msgstr ""
14 |
15 | ## From Ecto.Changeset.unique_constraint/3
16 | msgid "has already been taken"
17 | msgstr ""
18 |
19 | ## From Ecto.Changeset.put_change/3
20 | msgid "is invalid"
21 | msgstr ""
22 |
23 | ## From Ecto.Changeset.validate_format/3
24 | msgid "has invalid format"
25 | msgstr ""
26 |
27 | ## From Ecto.Changeset.validate_subset/3
28 | msgid "has an invalid entry"
29 | msgstr ""
30 |
31 | ## From Ecto.Changeset.validate_exclusion/3
32 | msgid "is reserved"
33 | msgstr ""
34 |
35 | ## From Ecto.Changeset.validate_confirmation/3
36 | msgid "does not match confirmation"
37 | msgstr ""
38 |
39 | ## From Ecto.Changeset.no_assoc_constraint/3
40 | msgid "is still associated to this entry"
41 | msgstr ""
42 |
43 | msgid "are still associated to this entry"
44 | msgstr ""
45 |
46 | ## From Ecto.Changeset.validate_length/3
47 | msgid "should be %{count} character(s)"
48 | msgid_plural "should be %{count} character(s)"
49 | msgstr[0] ""
50 | msgstr[1] ""
51 |
52 | msgid "should have %{count} item(s)"
53 | msgid_plural "should have %{count} item(s)"
54 | msgstr[0] ""
55 | msgstr[1] ""
56 |
57 | msgid "should be at least %{count} character(s)"
58 | msgid_plural "should be at least %{count} character(s)"
59 | msgstr[0] ""
60 | msgstr[1] ""
61 |
62 | msgid "should have at least %{count} item(s)"
63 | msgid_plural "should have at least %{count} item(s)"
64 | msgstr[0] ""
65 | msgstr[1] ""
66 |
67 | msgid "should be at most %{count} character(s)"
68 | msgid_plural "should be at most %{count} character(s)"
69 | msgstr[0] ""
70 | msgstr[1] ""
71 |
72 | msgid "should have at most %{count} item(s)"
73 | msgid_plural "should have at most %{count} item(s)"
74 | msgstr[0] ""
75 | msgstr[1] ""
76 |
77 | ## From Ecto.Changeset.validate_number/3
78 | msgid "must be less than %{number}"
79 | msgstr ""
80 |
81 | msgid "must be greater than %{number}"
82 | msgstr ""
83 |
84 | msgid "must be less than or equal to %{number}"
85 | msgstr ""
86 |
87 | msgid "must be greater than or equal to %{number}"
88 | msgstr ""
89 |
90 | msgid "must be equal to %{number}"
91 | msgstr ""
92 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170629190008_create_service.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.Repo.Migrations.CreateService do
2 | use Ecto.Migration
3 |
4 | def change do
5 |
6 | ServiceTypeEnum.create_type
7 | create table(:services) do
8 | add :name, :string
9 | add :description, :string
10 | add :icon, :string
11 | add :token, :string
12 | add :type, :service_type
13 |
14 | timestamps()
15 | end
16 |
17 | end
18 |
19 | def down do
20 | drop_if_exists table(:services)
21 | ServiceTypeEnum.drop_type
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170629195901_create_message.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.Repo.Migrations.CreateMessage do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:messages) do
6 | add :title, :string
7 | add :text, :string
8 | add :priority, :integer
9 | add :link, :string
10 | add :service_token, :string
11 |
12 | timestamps()
13 | end
14 |
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170702202723_create_role.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.Repo.Migrations.CreateRole do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:roles) do
6 | add :title, :string
7 | add :admin, :boolean, default: false, null: false
8 |
9 | timestamps()
10 | end
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170702202724_create_user.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.Repo.Migrations.CreateUser do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:users) do
6 | add :email, :string
7 | add :access_token, :string
8 | add :disabled_at, :utc_datetime, default: nil, null: true
9 | add :role_id, references(:roles)
10 |
11 | timestamps()
12 | end
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170704174839_create_token.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.Repo.Migrations.CreateToken do
2 | use Ecto.Migration
3 |
4 | def change do
5 | TokenTypeEnum.create_type
6 | create table(:tokens) do
7 | add :value, :string
8 | add :type, :token_type
9 | add :user_id, references(:users, on_delete: :nothing)
10 |
11 | timestamps()
12 | end
13 | create index(:tokens, [:user_id])
14 | create index(:tokens, [:type])
15 | create index(:tokens, [:value])
16 |
17 | end
18 |
19 | def down do
20 | drop_if_exists table(:tokens)
21 | TokenTypeEnum.drop_type
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170719054344_create_device.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.Repo.Migrations.CreateDevice do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:devices) do
6 | add :name, :string, null: false
7 | add :password, :string, null: false
8 | add :access_token, :string
9 | add :client_id, :string, null: false
10 | add :status, :boolean, default: false, null: false
11 | add :last_synced, :datetime
12 |
13 | add :user_id, references(:users, on_delete: :delete_all)
14 |
15 | timestamps()
16 | end
17 |
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/seeds.exs:
--------------------------------------------------------------------------------
1 | alias Iris.{Repo, Role, User, Device}
2 | import Ecto.Query, only: [from: 2]
3 |
4 | find_or_create_role = fn role_name, admin ->
5 | case Repo.all(from r in Role, where: r.title == ^role_name and r.admin == ^admin) do
6 | [] ->
7 | IO.puts "Role: #{role_name} does not exists, creating"
8 | %Role{}
9 | |> Role.changeset(%{title: role_name, admin: admin})
10 | |> Repo.insert!()
11 | [role] ->
12 | IO.puts "Role: #{role_name} already exists, skipping"
13 | role
14 | end
15 | end
16 |
17 | find_or_create_user = fn email, role ->
18 | case Repo.all(from u in User, where: u.email == ^email) do
19 | [] ->
20 | IO.puts "User: #{email} does not exists, creating"
21 | %User{}
22 | |> User.changeset(%{email: email, role_id: role.id})
23 | |> Repo.insert!()
24 | [user] ->
25 | IO.puts "User: #{email} already exists, skipping"
26 | user
27 | end
28 | end
29 |
30 | find_or_create_device = fn name, client_id, access_token, user ->
31 | case Repo.all(from u in Device, where: u.name == ^name) do
32 | [] ->
33 | IO.puts "Device: #{name} does not exists, creating"
34 | %Device{}
35 | |> Device.changeset(%{name: name, password: "sagapo", client_id: client_id, access_token: access_token, status: true, user_id: user.id})
36 | |> Repo.insert!()
37 | [user] ->
38 | IO.puts "Device: #{name} already exists, skipping"
39 | user
40 | end
41 | end
42 |
43 | user_role = find_or_create_role.("User Role", false)
44 | admin_role = find_or_create_role.("Admin Role", true)
45 | admin_user = find_or_create_user.("admin@dev.com", admin_role)
46 | simple_user = find_or_create_user.("user@dev.com", user_role)
47 | find_or_create_device.("iris_server", "iris_server", "access1", admin_user)
48 | find_or_create_device.("test_device", "client2", "access2", simple_user)
49 |
--------------------------------------------------------------------------------
/test/iris_web/controllers/device_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.DeviceControllerTest do
2 | use IrisWeb.ConnCase
3 |
4 | alias Iris.{Device, TestHelper, Support.AuthenticationToken, Repo, Accounts}
5 |
6 | @valid_attributes %{last_synced: %{day: 17, hour: 14, min: 0, month: 4, sec: 0, year: 2010}, name: "some content", status: true, access_token: "token", client_id: "client id"}
7 | @valid_attributes2 %{last_synced: %{day: 17, hour: 14, min: 0, month: 4, sec: 0, year: 2010}, name: "some content", status: true, access_token: "token", client_id: "client id2"}
8 | @invalid_attributes %{name: nil, client_id: nil}
9 |
10 | setup do
11 | {:ok, user_role} = TestHelper.create_role(%{title: "User Role", admin: false})
12 | {:ok, simple_user} = TestHelper.create_user(%{email: "test@test.com", role_id: user_role.id})
13 |
14 | simple_token = AuthenticationToken.create_token(simple_user)
15 | simple_conn = TestHelper.login_user(simple_token, @endpoint)
16 |
17 | {:ok, device} = Accounts.create_device(@valid_attributes, simple_user)
18 | {:ok, conn: simple_conn, user: simple_user, device: device}
19 | end
20 |
21 | test "lists all entries on index", %{conn: conn} do
22 | conn = get conn, device_path(conn, :index)
23 | assert html_response(conn, 200) =~ "Devices"
24 | end
25 |
26 | test "renders form for new devices", %{conn: conn} do
27 | conn = get conn, device_path(conn, :new)
28 | assert html_response(conn, 200) =~ "New device"
29 | end
30 |
31 | test "creates device and redirects when data is valid", %{conn: conn} do
32 | conn = post conn, device_path(conn, :create), device: @valid_attributes2
33 | assert redirected_to(conn) == device_path(conn, :index)
34 | assert Repo.get_by(Device, @valid_attributes2)
35 | end
36 |
37 | test "does not create device and renders errors when data is invalid", %{conn: conn} do
38 | conn = post conn, device_path(conn, :create), device: @invalid_attributes
39 | assert html_response(conn, 200) =~ "New device"
40 | end
41 |
42 | test "shows chosen device", %{conn: conn, device: device} do
43 | conn = get conn, device_path(conn, :show, device)
44 | assert html_response(conn, 200) =~ "Show device"
45 | end
46 |
47 | test "renders page not found when id is nonexistent", %{conn: conn} do
48 | assert_error_sent 404, fn ->
49 | get conn, device_path(conn, :show, -1)
50 | end
51 | end
52 |
53 | test "renders form for editing chosen device", %{conn: conn, device: device} do
54 | conn = get conn, device_path(conn, :edit, device)
55 | assert html_response(conn, 200) =~ "Edit device"
56 | end
57 |
58 | test "updates chosen device and redirects when data is valid", %{conn: conn, device: device} do
59 | conn = put conn, device_path(conn, :update, device), device: @valid_attributes
60 | assert redirected_to(conn) == device_path(conn, :show, device)
61 | assert Repo.get_by(Device, @valid_attributes)
62 | end
63 |
64 | test "does not update chosen device and renders errors when data is invalid", %{conn: conn, device: device} do
65 | conn = put conn, device_path(conn, :update, device), device: @invalid_attributes
66 | assert html_response(conn, 200) =~ "Edit device"
67 | end
68 |
69 | test "deletes chosen device", %{conn: conn, device: device} do
70 | conn = delete conn, device_path(conn, :delete, device)
71 | assert redirected_to(conn) == device_path(conn, :index)
72 | refute Repo.get(Device, device.id)
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/test/iris_web/controllers/message_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.MessageControllerTest do
2 | use IrisWeb.ConnCase
3 |
4 | alias Iris.{Message, Repo}
5 |
6 | @valid_attrs %{link: "some content", priority: 42, service_token: "some content", text: "some content", title: "some content"}
7 | @invalid_attrs %{}
8 |
9 | setup %{conn: conn} do
10 | {:ok, conn: put_req_header(conn, "accept", "application/json")}
11 | end
12 |
13 | test "lists all entries on index", %{conn: conn} do
14 | conn = get conn, message_path(conn, :index)
15 | assert json_response(conn, 200)["data"] == []
16 | end
17 |
18 | test "shows chosen resource", %{conn: conn} do
19 | message = Repo.insert! %Message{}
20 | conn = get conn, message_path(conn, :show, message)
21 | assert json_response(conn, 200) == %{"id" => message.id,
22 | "title" => message.title,
23 | "text" => message.text,
24 | "priority" => message.priority,
25 | "link" => message.link,
26 | "service_token" => message.service_token}
27 | end
28 |
29 | test "renders page not found when id is nonexistent", %{conn: conn} do
30 | assert_error_sent 404, fn ->
31 | get conn, message_path(conn, :show, -1)
32 | end
33 | end
34 |
35 | test "creates and renders resource when data is valid", %{conn: conn} do
36 | conn = post conn, message_path(conn, :create), @valid_attrs
37 | assert json_response(conn, 201)["id"]
38 | assert Repo.get_by(Message, @valid_attrs)
39 | end
40 |
41 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
42 | conn = post conn, message_path(conn, :create), @invalid_attrs
43 | assert json_response(conn, 422)["errors"] != %{}
44 | end
45 |
46 | test "updates and renders chosen resource when data is valid", %{conn: conn} do
47 | message = Repo.insert! %Message{}
48 | conn = put conn, message_path(conn, :update, message), @valid_attrs
49 | assert json_response(conn, 200)["id"]
50 | assert Repo.get_by(Message, @valid_attrs)
51 | end
52 |
53 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
54 | message = Repo.insert! %Message{}
55 | conn = put conn, message_path(conn, :update, message), @invalid_attrs
56 | assert json_response(conn, 422)["errors"] != %{}
57 | end
58 |
59 | test "deletes chosen resource", %{conn: conn} do
60 | message = Repo.insert! %Message{}
61 | conn = delete conn, message_path(conn, :delete, message)
62 | assert response(conn, 204)
63 | refute Repo.get(Message, message.id)
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/test/iris_web/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.PageControllerTest do
2 | use IrisWeb.ConnCase
3 |
4 | alias Iris.{TestHelper, Support.AuthenticationToken}
5 |
6 | setup do
7 | {:ok, user_role} = TestHelper.create_role(%{title: "User Role", admin: false})
8 | {:ok, simple_user} = TestHelper.create_user(%{email: "test@test.com", role_id: user_role.id})
9 |
10 | {:ok, admin_role} = TestHelper.create_role(%{title: "Admin Role", admin: true})
11 | {:ok, admin_user} = TestHelper.create_user(%{email: "admin@test.com", role_id: admin_role.id})
12 |
13 | simple_token = AuthenticationToken.create_token(simple_user)
14 | admin_token = AuthenticationToken.create_token(admin_user)
15 | admin_conn = TestHelper.login_user(admin_token, @endpoint)
16 | {:ok, conn: build_conn(), simple_token: simple_token, admin_token: admin_token, admin_conn: admin_conn}
17 | end
18 |
19 | test "GET /", %{conn: conn} do
20 | conn = get conn, "/"
21 | assert html_response(conn, 200) =~ "Iris"
22 | end
23 |
24 | test "GET /dashboard", %{conn: conn} do
25 | conn = get conn, "/dashboard"
26 | assert response(conn, 401) =~ "Unauthorized"
27 | end
28 |
29 | test "GET /settings", %{conn: conn} do
30 | conn = get conn, "/settings"
31 | assert response(conn, 401) =~ "Unauthorized"
32 | end
33 |
34 | @tag admin: true
35 | test "GET /settings with admin user", %{admin_conn: conn} do
36 | conn = get conn, "/settings"
37 | assert html_response(conn, 200) =~ "Settings"
38 | end
39 |
40 | @tag admin: true
41 | test "GET /dashboard with admin user", %{admin_conn: conn} do
42 | conn = get conn, "/dashboard"
43 | assert response(conn, 200) =~ "Send notification"
44 | end
45 |
46 | end
47 |
--------------------------------------------------------------------------------
/test/iris_web/controllers/registration_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.RegistrationControllerTest do
2 | use IrisWeb.ConnCase
3 | use Bamboo.Test
4 |
5 | alias Iris.TestHelper
6 | alias IrisWeb.Email
7 |
8 | @valid_post_attrs %{"user" => %{email: "test2@test.com"}}
9 | @existing_user %{"user" => %{email: "test@test.com"}}
10 | @invalid_post_attrs %{"user" => %{email: nil}}
11 |
12 | setup do
13 | {:ok, role} = TestHelper.create_role(%{title: "User Role", admin: false})
14 | {:ok, user} = TestHelper.create_user(%{email: "test@test.com", role_id: role.id})
15 | {:ok, user: user}
16 | end
17 |
18 | test "GET /signup", %{conn: conn} do
19 | conn = get conn, "/signup"
20 | assert html_response(conn, 200) =~ "Signup"
21 | end
22 |
23 | test "POST /signup", %{conn: conn} do
24 | conn = post conn, registration_path(conn, :signup), @valid_post_attrs
25 | assert get_flash(conn, :info) == "You signed up successfully."
26 | assert redirected_to(conn) == session_path(conn, :login)
27 | end
28 |
29 | test "POST /signup don't leak information", %{conn: conn} do
30 | conn = post conn, registration_path(conn, :signup), @existing_user
31 | assert get_flash(conn, :info) == "You signed up successfully."
32 | assert redirected_to(conn) == session_path(conn, :login)
33 | end
34 |
35 | test "POST /signup with empty email address", %{conn: conn} do
36 | conn = post conn, registration_path(conn, :signup), @invalid_post_attrs
37 | assert redirected_to(conn) == registration_path(conn, :signup)
38 | end
39 |
40 | test "welcome email", %{user: user} do
41 | email = Email.welcome(user)
42 |
43 | assert email.to == user.email
44 | assert email.subject == "Welcome"
45 | assert email.text_body =~ "Welcome"
46 | assert email.html_body =~ "Welcome"
47 | end
48 |
49 | end
50 |
--------------------------------------------------------------------------------
/test/iris_web/controllers/service_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ServiceControllerTest do
2 | use IrisWeb.ConnCase
3 |
4 | alias Iris.{Service, TestHelper, Support.AuthenticationToken, Repo}
5 | @valid_attrs %{description: "some content", icon: "some content", name: "some content", token: "some content", type: "P"}
6 | @invalid_attrs %{}
7 |
8 | setup do
9 | {:ok, user_role} = TestHelper.create_role(%{title: "User Role", admin: false})
10 | {:ok, simple_user} = TestHelper.create_user(%{email: "test@test.com", role_id: user_role.id})
11 |
12 | simple_token = AuthenticationToken.create_token(simple_user)
13 | simple_conn = TestHelper.login_user(simple_token, @endpoint)
14 |
15 | {:ok, conn: simple_conn}
16 | end
17 |
18 | test "lists all entries on index", %{conn: conn} do
19 | conn = get conn, service_path(conn, :index)
20 | assert json_response(conn, 200)["data"] == []
21 | end
22 |
23 | test "shows chosen resource", %{conn: conn} do
24 | service = Repo.insert! %Service{}
25 | conn = get conn, service_path(conn, :show, service)
26 | assert json_response(conn, 200) == %{"id" => service.id,
27 | "name" => service.name,
28 | "description" => service.description,
29 | "icon" => service.icon,
30 | "token" => service.token,
31 | "type" => service.type}
32 | end
33 |
34 | test "renders page not found when id is nonexistent", %{conn: conn} do
35 | assert_error_sent 404, fn ->
36 | get conn, service_path(conn, :show, -1)
37 | end
38 | end
39 |
40 | test "creates and renders resource when data is valid", %{conn: conn} do
41 | conn = post conn, service_path(conn, :create), @valid_attrs
42 | assert json_response(conn, 201)["id"]
43 | assert Repo.get_by(Service, @valid_attrs)
44 | end
45 |
46 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
47 | conn = post conn, service_path(conn, :create), @invalid_attrs
48 | assert json_response(conn, 422)["errors"] != %{}
49 | end
50 |
51 | test "updates and renders chosen resource when data is valid", %{conn: conn} do
52 | service = Repo.insert! %Service{}
53 | conn = put conn, service_path(conn, :update, service), @valid_attrs
54 | assert json_response(conn, 200)["id"]
55 | assert Repo.get_by(Service, @valid_attrs)
56 | end
57 |
58 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
59 | service = Repo.insert! %Service{}
60 | conn = put conn, service_path(conn, :update, service), @invalid_attrs
61 | assert json_response(conn, 422)["errors"] != %{}
62 | end
63 |
64 | test "deletes chosen resource", %{conn: conn} do
65 | service = Repo.insert! %Service{}
66 | conn = delete conn, service_path(conn, :delete, service)
67 | assert response(conn, 204)
68 | refute Repo.get(Service, service.id)
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/test/iris_web/controllers/session_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.SessionControllerTest do
2 | use IrisWeb.ConnCase
3 |
4 | alias Iris.{TestHelper, Support.AuthenticationToken}
5 |
6 | @post_attrs %{"user" => %{email: "test@test.com"}}
7 | @invalid_post_attrs %{"user" => %{email: nil}}
8 |
9 | setup do
10 | {:ok, user_role} = TestHelper.create_role(%{title: "User Role", admin: false})
11 | {:ok, simple_user} = TestHelper.create_user(%{email: "test@test.com", role_id: user_role.id})
12 |
13 | {:ok, admin_role} = TestHelper.create_role(%{title: "Admin Role", admin: true})
14 | {:ok, admin_user} = TestHelper.create_user(%{email: "admin@test.com", role_id: admin_role.id})
15 |
16 | simple_token = AuthenticationToken.create_token(simple_user)
17 | admin_token = AuthenticationToken.create_token(admin_user)
18 | {:ok, simple_token: simple_token, admin_token: admin_token}
19 | end
20 |
21 | test "GET /login", %{conn: conn} do
22 | conn = get conn, "/login"
23 | assert html_response(conn, 200) =~ "Login"
24 | end
25 |
26 | test "POST /login with nil email address", %{conn: conn} do
27 | conn = post conn, session_path(conn, :login), @invalid_post_attrs
28 | assert get_flash(conn, :info) == "You have to provide an email address."
29 | assert redirected_to(conn) == session_path(conn, :login)
30 | end
31 |
32 | test "POST /login with nil", %{conn: conn} do
33 | conn = post conn, session_path(conn, :login), @post_attrs
34 | assert get_flash(conn, :info) == "Check your inbox."
35 | assert redirected_to(conn) == page_path(conn, :index)
36 | end
37 |
38 | @tag admin: true
39 | test "GET /login with a valid admin token", %{conn: conn, admin_token: token} do
40 | conn = get conn, session_path(conn, :login), %{t: token}
41 | current_user = Plug.Conn.get_session(conn, :current_user)
42 | assert current_user.role.id != nil
43 | assert get_flash(conn, :info) == "You signed in successfully."
44 | assert redirected_to(conn) == page_path(conn, :dashboard)
45 | end
46 |
47 | test "GET /login with a valid user token", %{conn: conn, simple_token: token} do
48 | conn = get conn, session_path(conn, :login), %{t: token}
49 | current_user = Plug.Conn.get_session(conn, :current_user)
50 | assert current_user.role.id != nil
51 | assert get_flash(conn, :info) == "You signed in successfully."
52 | assert redirected_to(conn) == page_path(conn, :dashboard)
53 | end
54 |
55 | test "GET /login with an invalid token", %{conn: conn} do
56 | conn = get conn, session_path(conn, :login), %{t: "meow"}
57 | assert get_flash(conn, :error) == "Invalid token."
58 | assert redirected_to(conn) == session_path(conn, :login)
59 | end
60 |
61 | test "GET /logout", %{simple_token: simple_token} do
62 | conn = TestHelper.login_user(simple_token, @endpoint)
63 | conn = get conn, session_path(conn, :logout)
64 | current_user = Plug.Conn.get_session(conn, :current_user)
65 | assert redirected_to(conn) == page_path(conn, :index)
66 | assert current_user == nil
67 | end
68 |
69 | end
70 |
--------------------------------------------------------------------------------
/test/iris_web/controllers/user_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.UserControllerTest do
2 | use IrisWeb.ConnCase
3 |
4 | alias Iris.{User, TestHelper, Support.AuthenticationToken, Repo}
5 | @valid_attributes %{email: "test@test.com"}
6 | @valid_attributes_new_user %{email: "test2@test.com"}
7 | @invalid_attributes %{}
8 |
9 | setup do
10 | {:ok, user_role} = TestHelper.create_role(%{title: "User Role", admin: false})
11 | {:ok, simple_user} = TestHelper.create_user(%{email: "test@test.com", role_id: user_role.id})
12 | {:ok, admin_role} = TestHelper.create_role(%{title: "Admin Role", admin: true})
13 | {:ok, admin_user} = TestHelper.create_user(%{email: "admin@test.com", role_id: admin_role.id})
14 | admin_token = AuthenticationToken.create_token(admin_user)
15 | admin_conn = TestHelper.login_user(admin_token, @endpoint)
16 | {:ok, conn: admin_conn, simple_user: simple_user}
17 | end
18 |
19 | test "lists all users on index", %{conn: conn} do
20 | conn = get conn, user_path(conn, :index)
21 | assert html_response(conn, 200) =~ "Listing users"
22 | end
23 |
24 | test "renders form for a new user", %{conn: conn} do
25 | conn = get conn, user_path(conn, :new)
26 | assert html_response(conn, 200) =~ "New user"
27 | end
28 |
29 | test "creates a user and redirects when data is valid", %{conn: conn} do
30 | conn = post conn, user_path(conn, :create), user: @valid_attributes_new_user
31 | assert redirected_to(conn) == user_path(conn, :index)
32 | assert Repo.get_by(User, @valid_attributes)
33 | end
34 |
35 | test "does not create a user and renders errors when data is invalid", %{conn: conn} do
36 | conn = post conn, user_path(conn, :create), user: @invalid_attributes
37 | assert html_response(conn, 200) =~ "New user"
38 | end
39 |
40 | test "shows chosen user", %{conn: conn} do
41 | user = Repo.insert! %User{}
42 | conn = get conn, user_path(conn, :show, user)
43 | assert html_response(conn, 200) =~ "Show user"
44 | end
45 |
46 | test "renders page not found when id is nonexistent", %{conn: conn} do
47 | assert_error_sent 404, fn ->
48 | get conn, user_path(conn, :show, -1)
49 | end
50 | end
51 |
52 | test "renders form for editing chosen user", %{conn: conn, simple_user: simple_user} do
53 | conn = get conn, user_path(conn, :edit, simple_user)
54 | assert html_response(conn, 200) =~ "Edit user"
55 | end
56 |
57 | test "updates chosen user and redirects when data is valid", %{conn: conn, simple_user: simple_user} do
58 | conn = put conn, user_path(conn, :update, simple_user), user: @valid_attributes
59 | assert redirected_to(conn) == user_path(conn, :show, simple_user)
60 | assert Repo.get_by(User, @valid_attributes)
61 | end
62 |
63 | test "does not update chosen user and renders errors when data is invalid", %{conn: conn} do
64 | user = Repo.insert! %User{}
65 | conn = put conn, user_path(conn, :update, user), user: @invalid_attributes
66 | assert html_response(conn, 200) =~ "Edit user"
67 | end
68 |
69 | test "deletes chosen user", %{conn: conn} do
70 | user = Repo.insert! %User{}
71 | conn = delete conn, user_path(conn, :delete, user)
72 | assert redirected_to(conn) == user_path(conn, :index)
73 | refute Repo.get(User, user.id)
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/test/iris_web/models/device_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.DeviceTest do
2 | use Iris.DataCase
3 |
4 | alias Iris.Device
5 |
6 | @valid_attributes %{last_synced: %{day: 17, hour: 14, min: 0, month: 4, sec: 0, year: 2010}, name: "some content", status: true, access_token: "some content", client_id: "client id", user_id: 2, password: "sagapo"}
7 | @invalid_attributes %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Device.changeset(%Device{}, @valid_attributes)
11 | assert changeset.valid?()
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Device.changeset(%Device{}, @invalid_attributes)
16 | refute changeset.valid?()
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/iris_web/models/message_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.MessageTest do
2 | use Iris.DataCase
3 |
4 | alias Iris.Message
5 |
6 | @valid_attrs %{link: "some content", priority: 42, service_token: "some content", text: "some content", title: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Message.changeset(%Message{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Message.changeset(%Message{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/iris_web/models/role_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.RoleTest do
2 | use Iris.DataCase
3 |
4 | alias Iris.Role
5 |
6 | @valid_attrs %{admin: true, title: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Role.changeset(%Role{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Role.changeset(%Role{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/iris_web/models/service_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.ServiceTest do
2 | use Iris.DataCase
3 |
4 | alias Iris.Service
5 |
6 | @valid_attrs %{description: "some content", icon: "some content", name: "some content", token: "some content", type: "P"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Service.changeset(%Service{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Service.changeset(%Service{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/iris_web/models/token_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.TokenTest do
2 | use Iris.DataCase
3 |
4 | alias Iris.{Token, TestHelper}
5 |
6 | setup do
7 | {:ok, role} = TestHelper.create_role(%{title: "User Role", admin: false})
8 | {:ok, user} = TestHelper.create_user(%{email: "test@test.com", role_id: role.id})
9 | {:ok, user: user}
10 | end
11 |
12 | test "changeset with valid attributes", %{user: user} do
13 | changeset = Token.create_auth_token(%Token{}, user)
14 | assert changeset.valid?
15 | end
16 |
17 | test "changeset with invalid attributes" do
18 | changeset = Token.create_auth_token(%Token{}, nil)
19 | refute changeset.valid?
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/test/iris_web/models/user_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.UserTest do
2 | use Iris.DataCase
3 |
4 | alias Iris.User
5 |
6 | @valid_attrs %{disabled_at: nil, email: "some@emal.com", role_id: 1}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = User.changeset(%User{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = User.changeset(%User{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/iris_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.ErrorViewTest do
2 | use IrisWeb.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(IrisWeb.ErrorView, "404.html", []) ==
9 | "Page not found"
10 | end
11 |
12 | test "render 500.html" do
13 | assert render_to_string(IrisWeb.ErrorView, "500.html", []) ==
14 | "Internal server error"
15 | end
16 |
17 | test "render any other" do
18 | assert render_to_string(IrisWeb.ErrorView, "505.html", []) ==
19 | "Internal server error"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/test/iris_web/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.LayoutViewTest do
2 | use IrisWeb.ConnCase, async: true
3 |
4 | alias Iris.{TestHelper, Support.AuthenticationToken}
5 | alias IrisWeb.LayoutView
6 |
7 | setup do
8 | {:ok, user_role} = TestHelper.create_role(%{title: "User Role", admin: false})
9 | {:ok, simple_user} = TestHelper.create_user(%{email: "test@test.com", role_id: user_role.id})
10 |
11 | {:ok, admin_role} = TestHelper.create_role(%{title: "User Role", admin: true})
12 | {:ok, admin_user} = TestHelper.create_user(%{email: "admin@test.com", role_id: admin_role.id})
13 |
14 | simple_token = AuthenticationToken.create_token(simple_user)
15 | admin_token = AuthenticationToken.create_token(admin_user)
16 |
17 | simple_conn = TestHelper.login_user(simple_token, @endpoint)
18 | admin_conn = TestHelper.login_user(admin_token, @endpoint)
19 | {:ok, admin_conn: admin_conn, simple_conn: simple_conn}
20 | end
21 |
22 | test "current user returns the user in the session", %{simple_conn: conn} do
23 | assert LayoutView.current_user(conn)
24 | end
25 |
26 | test "current user returns nothing if there is no user in the session", %{simple_conn: conn} do
27 | conn = get conn, session_path(conn, :logout)
28 | refute LayoutView.current_user(conn)
29 | end
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/test/iris_web/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Iris.PageViewTest do
2 | use IrisWeb.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | import other functionality to make it easier
8 | to build common datastructures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | # The default endpoint for testing
24 | @endpoint IrisWeb.Endpoint
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(Iris.Repo)
30 | unless tags[:async] do
31 | Ecto.Adapters.SQL.Sandbox.mode(Iris.Repo, {:shared, self()})
32 | end
33 | :ok
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule IrisWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common datastructures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with connections
21 | use Phoenix.ConnTest
22 | import IrisWeb.Router.Helpers
23 |
24 | # The default endpoint for testing
25 | @endpoint IrisWeb.Endpoint
26 | end
27 | end
28 |
29 | setup tags do
30 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(Iris.Repo)
31 | unless tags[:async] do
32 | Ecto.Adapters.SQL.Sandbox.mode(Iris.Repo, {:shared, self()})
33 | end
34 | {:ok, conn: Phoenix.ConnTest.build_conn()}
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/test/support/data_case.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.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 Iris.Repo
20 |
21 | import Ecto
22 | import Ecto.Changeset
23 | import Ecto.Query
24 | import Iris.DataCase
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(Iris.Repo)
30 |
31 | unless tags[:async] do
32 | Ecto.Adapters.SQL.Sandbox.mode(Iris.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 | assert {:error, changeset} = Accounts.create_user(%{password: "short"})
42 | assert "password is too short" in errors_on(changeset).password
43 | assert %{password: ["password is too short"]} = errors_on(changeset)
44 |
45 | """
46 | def errors_on(changeset) do
47 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
48 | Enum.reduce(opts, message, fn {key, value}, acc ->
49 | String.replace(acc, "%{#{key}}", to_string(value))
50 | end)
51 | end)
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/test/support/test_helper.ex:
--------------------------------------------------------------------------------
1 | defmodule Iris.TestHelper do
2 |
3 | import Phoenix.ConnTest, only: [dispatch: 5, build_conn: 0]
4 |
5 | alias Iris.{Repo, Role, User, Accounts}
6 | alias IrisWeb.Router.Helpers
7 |
8 | def login_user(token, endpoint) do
9 | conn = build_conn()
10 | dispatch(conn, endpoint, :get, Helpers.session_path(conn, :login), t: token)
11 | end
12 |
13 | def create_role(%{title: title, admin: admin}) do
14 | Role.changeset(%Role{}, %{title: title, admin: admin})
15 | |> Repo.insert
16 | end
17 |
18 | def create_user(attributes) do
19 | email = Map.get(attributes, :email)
20 | if user = Repo.get_by(User, email: email) do
21 | Repo.delete(user)
22 | end
23 | Accounts.create_user(attributes)
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
3 | Ecto.Adapters.SQL.Sandbox.mode(Iris.Repo, :manual)
4 |
--------------------------------------------------------------------------------