Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation #1

Merged
merged 20 commits into from
Feb 17, 2017
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
129 changes: 129 additions & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# This file contains the configuration for Credo and you are probably reading
# this after creating it with `mix credo.gen.config`.
#
# If you find anything wrong or unclear in this file, please report an
# issue on GitHub: https://github.com/rrrene/credo/issues
#
%{
#
# You can have as many configs as you like in the `configs:` field.
configs: [
%{
#
# Run any config using `mix credo -C <name>`. If no config name is given
# "default" is used.
name: "default",
#
# These are the files included in the analysis:
files: %{
#
# You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used.
included: ["lib/", "src/", "web/", "apps/"],
excluded: [~r"/_build/", ~r"/deps/"]
},
#
# If you create your own checks, you must specify the source files for
# them here, so they can be loaded by Credo before running the analysis.
requires: [],
#
# Credo automatically checks for updates, like e.g. Hex does.
# You can disable this behaviour below:
check_for_updates: true,
#
# If you want to enforce a style guide and need a more traditional linting
# experience, you can change `strict` to `true` below:
strict: true,
#
# If you want to use uncolored output by default, you can change `color`
# to `false` below:
color: true,
#
# You can customize the parameters of any check by adding a second element
# to the tuple.
#
# To disable a check put `false` as second element:
#
# {Credo.Check.Design.DuplicatedCode, false}
#
checks: [
{Credo.Check.Consistency.ExceptionNames},
{Credo.Check.Consistency.LineEndings},
{Credo.Check.Consistency.MultiAliasImportRequireUse},
{Credo.Check.Consistency.ParameterPatternMatching},
{Credo.Check.Consistency.SpaceAroundOperators},
{Credo.Check.Consistency.SpaceInParentheses},
{Credo.Check.Consistency.TabsOrSpaces},

# For some checks, like AliasUsage, you can only customize the priority
# Priority values are: `low, normal, high, higher`
{Credo.Check.Design.AliasUsage, priority: :low},

# For others you can set parameters

# If you don't want the `setup` and `test` macro calls in ExUnit tests
# or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just
# set the `excluded_macros` parameter to `[:schema, :setup, :test]`.
{Credo.Check.Design.DuplicatedCode, excluded_macros: []},

# You can also customize the exit_status of each check.
# If you don't want TODO comments to cause `mix credo` to fail, just
# set this value to 0 (zero).
{Credo.Check.Design.TagTODO, exit_status: 2},
{Credo.Check.Design.TagFIXME},

{Credo.Check.Readability.FunctionNames},
{Credo.Check.Readability.LargeNumbers},
{Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
{Credo.Check.Readability.ModuleAttributeNames},
{Credo.Check.Readability.ModuleDoc},
{Credo.Check.Readability.ModuleNames},
{Credo.Check.Readability.NoParenthesesWhenZeroArity},
{Credo.Check.Readability.ParenthesesInCondition},
{Credo.Check.Readability.PredicateFunctionNames},
{Credo.Check.Readability.PreferImplicitTry},
{Credo.Check.Readability.RedundantBlankLines},
{Credo.Check.Readability.Specs},
{Credo.Check.Readability.StringSigils},
{Credo.Check.Readability.TrailingBlankLine},
{Credo.Check.Readability.TrailingWhiteSpace},
{Credo.Check.Readability.VariableNames},
{Credo.Check.Refactor.DoubleBooleanNegation},

# {Credo.Check.Refactor.CaseTrivialMatches}, # deprecated in 0.4.0
{Credo.Check.Refactor.ABCSize},
{Credo.Check.Refactor.CondStatements},
{Credo.Check.Refactor.CyclomaticComplexity},
{Credo.Check.Refactor.FunctionArity},
{Credo.Check.Refactor.MatchInCondition},
{Credo.Check.Refactor.NegatedConditionsInUnless},
{Credo.Check.Refactor.NegatedConditionsWithElse},
{Credo.Check.Refactor.Nesting},
{Credo.Check.Refactor.PipeChainStart},
{Credo.Check.Refactor.UnlessWithElse},
{Credo.Check.Refactor.VariableRebinding},

{Credo.Check.Warning.BoolOperationOnSameValues},
{Credo.Check.Warning.IExPry},
{Credo.Check.Warning.IoInspect},
{Credo.Check.Warning.NameRedeclarationByAssignment},
{Credo.Check.Warning.NameRedeclarationByCase},
{Credo.Check.Warning.NameRedeclarationByDef},
{Credo.Check.Warning.NameRedeclarationByFn},
{Credo.Check.Warning.OperationOnSameValues},
{Credo.Check.Warning.OperationWithConstantResult},
{Credo.Check.Warning.UnusedEnumOperation},
{Credo.Check.Warning.UnusedFileOperation},
{Credo.Check.Warning.UnusedKeywordOperation},
{Credo.Check.Warning.UnusedListOperation},
{Credo.Check.Warning.UnusedPathOperation},
{Credo.Check.Warning.UnusedRegexOperation},
{Credo.Check.Warning.UnusedStringOperation},
{Credo.Check.Warning.UnusedTupleOperation},

# Custom checks can be created using `mix credo.gen.check`.
#
]
}
]
}
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
language: elixir
elixir:
- 1.4.1
otp_release:
- 19.2

sudo: required
services:
- docker

before_script:
- tools/travis-setup.sh

env:
- PRESET=exunit MIX_ENV=test
- PRESET=credo MIX_ENV=test
- PRESET=dialyzer MIX=test

script: tools/travis-test.sh
145 changes: 135 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,144 @@
# MongoosePush

**TODO: Add description**
[![Build Status](https://travis-ci.org/esl/MongoosePush.svg?branch=initial_implementation)](https://travis-ci.org/esl/MongoosePush) [![Coverage Status](https://coveralls.io/repos/github/esl/MongoosePush/badge.svg?branch=initial_implementation)](https://coveralls.io/github/esl/MongoosePush?branch=initial_implementation)

## Installation
**MongoosePush** is simple (seriously) **REST** service written in **Elixir** providing ability to **send push
notification** to `FCM` (Firebase Cloud Messaging) and/or
`APNS` (Apple Push Notification Service) via their `HTTP/2` API.

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `mongoose_push` to your list of dependencies in `mix.exs`:
## Quick start

### Docker

Soon :)

### Local build

#### Perquisites

* Elixir 1.4+ (http://elixir-lang.org/install.html)
* Rebar3 (just enter ```mix local.rebar```)

#### Build and run

Build step is really easy. Just type in root of the repository:
```bash
MIX_ENV=prod mix do certs.dev, deps.get, compile, release
```

After this step you may try to run the service via:
```bash
_build/prod/rel/mongoose_push/bin/mongoose_push console
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to be serious, we really need to support releases. I won't accept an OS PR that has on support for releases :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate? What do you mean by support for releases exactly? This README.md shows how to run this project from the release.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn...how could I have missed that? Sorry, of course you do support releases with distillery. Awesome :)


Yeah, I know... It crashed. Running this service is fast and simple but unfortunately you can't have push notifications without properly configured `FCM` and/or `APNS` service. So, lets configure it!

## Configuration

The whole configuration is contained in file `config/{prod|dev|test}.exs` depending on which `MIX_ENV` you will be using. You should use `MIX_ENV=prod` for production installations and `MIX_ENV=dev` for your development. Anyway, lets take a look on `config/prod.exs`, part by part.

### REST API configuration

```elixir
config :maru, MongoosePush.Router,
versioning: [
using: :path
],
https: [
ip: {127, 0, 0, 1},
port: 8443,
keyfile: "priv/ssl/fake_key.pem",
certfile: "priv/ssl/fake_cert.pem",
otp_app: :mongoose_push
]
```

This part of configuration relates only to `REST` endpoints that `MongoosePush` exposes. Here you can set bind IP adress (option: `ip`), port and paths to you `HTTP` `TLS` certificates. You should ignore other options unless you know what you're doing or you're going to get to know by reading [maru's documentation](https://maru.readme.io/docs).

You may entirely skip the `maru` config entry to disable `REST` API and just use this project as `Elixir` library.

### FCM configuration
Lets take a look at sample `FCM` service configuration:
```elixir
def deps do
[{:mongoose_push, "~> 0.1.0"}]
end
config :mongoose_push, fcm: [
default: [
key: "fake_app_key",
pool_size: 5,
mode: :prod
]
]
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/mongoose_push](https://hexdocs.pm/mongoose_push).
Here we can see definition of a pool. Each pool has a name and its configuration. You may have several named pools of different sizes and with different configurations. Currently the only reason you may want to do this is that, the `REST` client may switch between them by specifying matching `:mode` in their push request.

Each `FCM` pool may be configured by setting the following fields:
* **key** (*required*) - you `FCM` Application Key for using Googles API
* **pool_size** (*required*) - maximum number of used `HTTP/2` connections to google's service
* **mode** (*either `:prod` or `:dev`*) - pool's mode. `REST` client may select pool used to push his notification by specifying matching option in his request
* **endpoint** (*optional*) - URL override for `FCM` service. Useful mainly in tests

You may entirely skip the `FCM` config entry to disable `FCM` support.

### APNS configuration

Lets take a look at sample `APNS` service configuration:
```elixir
config :mongoose_push, apns: [
dev: [
cert: "priv/apns/dev_cert.pem",
key: "priv/apns/dev_key.pem",
mode: :dev,
use_2197: false,
pool_size: 5
],
prod: [
cert: "priv/apns/prod_cert.pem",
key: "priv/apns/prod_key.pem",
mode: :prod,
use_2197: false,
pool_size: 5
]
]
```
Analogically to `FCM` configuration, at top level we may specify named pools that have different configurations. For `APNS` this is specifically useful since Apple delivers different APS certificated for development and production use. As in `FCM`, `REST` client may select named pool by providing matching `:mode` in his `REST` request.

Each `APNS` pool may be configured by setting the following fields:
* **cert** (*required*) - relative path to `APNS` `PEM` certificate issued by Apple. This certificate have to be somewhere in `priv` directory
* **key** (*required*) - relative path to `PEM` private key for `APNS` certificate issued by Apple. This file have to be somewhere in `priv` directory
* **pool_size** (*required*) - maximum number of used `HTTP/2` connections to google's service
* **mode** (*either `:prod` or `:dev`*) - pool's mode. `REST` client may select pool used to push his notification by specifying matching option in his request
* **endpoint** (*optional*) - URL override for `APNS` service. Useful mainly in tests
* **use_2197** (*optional `true` or `false`*) - whether use alternative port for `APNS`: 2197

You may entirely skip the `APNS` config entry to disable `APNS` support.

## REST API

### Swagger

If for some reason you need `Swagger` spec for this `REST` service, after compiling and running this project with `MIX_ENV=dev`, there is swagger endpoint available at `REST` path `/swagger.json`

### Just tell me what to send already

There is only one endpoint at this moment:
* `POST /v1/notification/{device_id}`

As you can imagine, `{device_id}` should be replaced with device ID/Token generated by your push notification provider (`FCM` or `APNS`). The notification should be sent as `JSON` payload of this request. Minimal `JSON` request could be like this:

```json
{
"service": "apns",
"body": "notification's text body",
"title": "notification's title"
}
```

The full list of options contains the following:
* **service** (*required*, `apns` or `fcm`) - push notifications provider to be used for this notification
* **body** (*required*) - text body of notification
* **title** (*required*) - short title of notification
* **mode** (*optional*, `prod` (default) or `dev`) - allows for selecting named pool configured in `MongoosePush`
* **click_action** (*optional*) - for `FCM` its `activity` to run when notification is clicked. For `APNS` its `category` to invoke. Please refer to Android/iOS documentation for more details about this action
* **tag** (*optional*, `FCM` specific) - notifications aggregation key
* **badge** (*optional*, `APNS` specific) - unread notifications count
* **topic** (*optional*, `APNS` specific) - if APNS certificate configured in `MongoosePush` allows for multiple applications, this field selects the application. Please refer to `APNS` documentation for more datails
8 changes: 1 addition & 7 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,4 @@ use Mix.Config
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
import_config "#{Mix.env}.exs"
38 changes: 38 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use Mix.Config

config :maru, MongoosePush.Router,
versioning: [
using: :path
],
https: [
ip: {127, 0, 0, 1},
port: 8443,
keyfile: "priv/ssl/fake_key.pem",
certfile: "priv/ssl/fake_cert.pem",
otp_app: :mongoose_push
]

config :mongoose_push, fcm: [
default: [
key: "fake_app_key",
pool_size: 5,
mode: :prod
]
]

config :mongoose_push, apns: [
dev: [
cert: "priv/apns/dev_cert.pem",
key: "priv/apns/dev_key.pem",
mode: :dev,
use_2197: false,
pool_size: 5
],
prod: [
cert: "priv/apns/prod_cert.pem",
key: "priv/apns/prod_key.pem",
mode: :prod,
use_2197: false,
pool_size: 5
]
]
38 changes: 38 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use Mix.Config

config :maru, MongoosePush.Router,
versioning: [
using: :path
],
https: [
ip: {127, 0, 0, 1},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about {0, 0, 0, 0} by default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think its a good idea. In many cases this project will be used on same host as MongooseIM. Also if somebody uses it on different host, he will notice that he has to change configuration because REST API won't work without it. But with {0, 0, 0, 0} user may not notice that his service is open for attacks, because it will always work this way, even if such configuration is not needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks for the explanation.

port: 8443,
keyfile: "priv/ssl/fake_key.pem",
certfile: "priv/ssl/fake_cert.pem",
otp_app: :mongoose_push
]

config :mongoose_push, fcm: [
prod: [
key: "fake_app_key",
pool_size: 5,
mode: :prod
]
]

config :mongoose_push, apns: [
dev: [
cert: "priv/apns/dev_cert.pem",
key: "priv/apns/dev_key.pem",
mode: :dev,
use_2197: false,
pool_size: 5
],
prod: [
cert: "priv/apns/prod_cert.pem",
key: "priv/apns/prod_key.pem",
mode: :prod,
use_2197: false,
pool_size: 5
]
]