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
Conversation
fd2316c
to
b2865c3
Compare
fc57e8e
to
502aa2a
Compare
cd2fe3c
to
5cf4941
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is simple, indeed :) I have some comments to this PR.
Also in a following PR I think we should add some metrics counting number of requests from clients, number of failed and succeeded requests to FCM
and APNS
. Elapsed time measurements for the whole request and specific requests to providers may prove useful on production as well.
using: :path | ||
], | ||
https: [ | ||
ip: {127, 0, 0, 1}, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@@ -29,6 +42,19 @@ defmodule MongoosePush.Mixfile do | |||
# | |||
# Type "mix help deps" for more examples and options | |||
defp deps do | |||
[] | |||
[ | |||
{:pigeon, git: "https://github.com/rslota/pigeon.git", tag: "6d1e4e3"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see rslota/pigeon
is a fork from cstar/pigeon
. Are you @rslota going to send a PR to upstream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can try, but there are some changes that the author may not like. Author of this project had much different plan when comes to pooling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try to convince him :)
priv/apns/dev_cert.pem
Outdated
@@ -0,0 +1,23 @@ | |||
-----BEGIN CERTIFICATE----- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can all these *.pem
files be generated on demand? For example when the project is first compiled or run?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can be generated but it adds one and only one additional step to building the project. If I leave those example certs in repo, there not need to have openssl while running the project and there not need for Makefile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe openssl is required in order to provide HTTPs from Elixir node. This probably means that openssl will have to be installed anyway. My biggest concern is with such files included in the repo, users may start using them without regenerating. For such a simple project which most probably we'll be used internally fresh certs and keys may not be mission critical. It is still sth which lights a warning in my brain, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, you can now generate those certs via mix certs.dev
.
tools/travis-setup.sh
Outdated
# Skip this setup for jobs that don't run exunit | ||
test "${PRESET}" == "exunit" || exit 0 | ||
|
||
docker run -d -p 2197:2197 mobify/apns-http2-mock-server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any chance for a specific version here and in the next container?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want the latest. Always. If the API changes we need to adapt, because our code needs to be compatible with newest APNS / FCM API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough :)
ad7818b
to
2c61d6b
Compare
545f104
to
1dae070
Compare
Changes Unknown when pulling 1dae070 on initial_implementation into ** on master**. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will continue the review from lib/mongoose_push/api/v1.ex tomorrow.
README.md
Outdated
|
||
#### Perquisites | ||
|
||
* Elixir (http://elixir-lang.org/install.html) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which version of Elixir?
README.md
Outdated
|
||
## Configuration | ||
|
||
The whole configuration is contained in file `config/{prod|dev|test}.exs` depending on with `MIX_ENV` you will be using. You should use `MIX_ENV=prod` for production installations and `MIX_ENV=dev` for you development. Anyway, lets take a look on `config/prod.exs`, part by part. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on witch
-> on which
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for you development
-> ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
your* :)
After this step you may try to run the service via: | ||
```bash | ||
_build/prod/rel/mongoose_push/bin/mongoose_push console | ||
``` |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
README.md
Outdated
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). | ||
The first level keyword list is a pool definition. 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"The first level keyword list is a pool definition." - this is not very understandable. Maybe start with "to setup a pool...".
lib/mix/task_certs_dev.ex
Outdated
use Mix.Task | ||
|
||
@shortdoc "Generate fake certs (placeholders) for HTTPS endpoint and APNS" | ||
@moduledoc """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@moduledoc is usually put at the very top.
lib/mix/task_certs_dev.ex
Outdated
end | ||
|
||
defp maybe_gen_cert(cert_file, key_file, common_name) do | ||
unless File.exists?(cert_file) and File.exists?(key_file) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is not readable. If I need to think what it means it has to be structured differently. One option would be:
case {File.exists?(cert_file), File.exists?(key_file)} do
{false, false} -> ...
_ -> ...
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that readability is very subjective, especially in this case (huh, case
:)). This case
pattern may be easier to read for erlang
devs since in erlang
this is the only sane way to write such condition. Elixir makes it easier and in my opinion more readable, so I wanted to exploit that.
But anyway, this is so minor that I'm just gonna change it, I don't really like unless
that much :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you can simply write if File.exists? ... and ... do ok else do_stuff() end
lib/mongoose_push.ex
Outdated
@doc """ | ||
Hello world. | ||
require Logger | ||
import MongoosePush.Application |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I really discourage these import. Because of that it takes you time to find functions like pools_by_name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats true, alias
seems to be a better idea.
lib/mongoose_push.ex
Outdated
@spec push(String.t, request) :: :ok | {:error, term} | ||
def push(device_id, %{:service => service} = request) do | ||
mode = Map.get(request, :mode, :prod) | ||
[pool | _] = pools_by_mode(service, mode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the functions on lines 44 and 45 are consructing a worker name. For me this level of abstraction doesn't belong to this function.
lib/mongoose_push.ex
Outdated
|
||
@spec push(service, atom | pid, String.t, request) :: :ok | {:error, term} | ||
defp push(:apns, worker, device_id, request) do | ||
raw_notification = prepare_notification(:apns, request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type of code can be rewritten as:
raw_notification =
prepare_notification(:apns, request)
|> APNS.Notification.new(device_id, request[:topic])
|> APNS.push([name: worker])
|> normalize_response(:apns, device_id)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I did write it this way, but credo
does not agree with you. Its says that each pipe operator has to start with variable, not expression.
lib/mongoose_push.ex
Outdated
"category" => request[:click_action] | ||
} | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, this module should not distinguish between the two types of the backends further than :fcm and :apns. In other words, I believe that the two deserve their own modules like MongoosePush.FCM
and MongoosePush.APNS
and the specifics of these services should sit in them. Maybe the next PR would address that but I simply don't like it.
4443b46
to
a4a88a5
Compare
Changes Unknown when pulling a4a88a5 on initial_implementation into ** on master**. |
Changes Unknown when pulling 2dbe39d on initial_implementation into ** on master**. |
lib/mongoose_push/api/v1.ex
Outdated
{:error, reason} -> | ||
conn | ||
|> put_status(500) | ||
|> json(%{:details => inspect reason}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module is beautiful. Simple and readable. One issue though, the inspect
from the Kernel
module is not to be used to format data that you return to the client. You have to format you data differently, without inspect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
lib/mongoose_push/application.ex
Outdated
config = env(service) | ||
|
||
config | ||
|> Enum.group_by(&(mode(elem(&1, 1))), &(elem &1, 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really readable.
lib/mongoose_push/application.ex
Outdated
end) | ||
|
||
workers | ||
|> List.flatten |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of flattening the list afterwards, you could use Enum.flat_map/2
.
true -> :error | ||
false -> :info | ||
end | ||
Logger.log log_level, inspect e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, you shouldn't be using inspect to format your output data.
@@ -0,0 +1,9 @@ | |||
defmodule MongoosePush.Service do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool :)
|
||
defp get(path) do | ||
%Response{status_code: status_code} = | ||
HTTPoison.get!("https://localhost:8443" <> path, [], hackney: [:insecure]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you using the insecure
hackney option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this is connection to HTTPS
endpoint that is running on self-signed certs generated for tests. Without this option connection would fail due to unknown_ca
error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
lib/mix/task_certs_dev.ex
Outdated
end | ||
|
||
defp maybe_gen_cert(cert_file, key_file, common_name) do | ||
unless File.exists?(cert_file) and File.exists?(key_file) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you can simply write if File.exists? ... and ... do ok else do_stuff() end
lib/mongoose_push.ex
Outdated
@doc """ | ||
Hello world. | ||
require Logger | ||
alias MongoosePush.Application |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this alias. It's misleading as at first glance you don't know if it refers to Elixir.Application
or to Elixir.MngoosePush.Application
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, you're right.
@@ -4,19 +4,126 @@ defmodule MongoosePush.Application do | |||
@moduledoc false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, the whole pools management doesn't belong to this module. If would prefer just having it separated out to sth like MongoosePush.Pools which would provide some API to get workers names to start then under supervisors and then the other one to select a woker. Even better, I believe, is a solution in which you have a separate supervisors for each type of pool and it knows more details and the the "pools" module that know even more details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pools have been refactored. Please review new implementation :)
lib/mongoose_push/application.ex
Outdated
pool_config = translate_worker_config(:fcm, pool_config) | ||
|
||
Enum.map(1..pool_size(:fcm, pool_name), fn(id) -> | ||
worker_name = worker_name(:fcm, pool_name, id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To deep... doesn't credo complain about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it does not. This is only 2 level in, so I guess its maximum allowed.
823f545
to
ea9383e
Compare
ea9383e
to
fdef967
Compare
Changes Unknown when pulling fdef967 on initial_implementation into ** on master**. |
Changes Unknown when pulling fdef967 on initial_implementation into ** on master**. |
@@ -0,0 +1,44 @@ | |||
defmodule MongoosePush.Pools do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now this is awesome
setup do | ||
# Validate config/text.exs that is need for this test suite | ||
apns_pools = Keyword.keys(Application.get_env(:mongoose_push, :apns)) | ||
4 = length(apns_pools) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could be reduced to [:prod1, :prod2, :dev1, :dev2] = apns_pools
But is so minor that you don't have to bother yourself.
Changes Unknown when pulling aa07594 on initial_implementation into ** on master**. |
This PR introduces... well, this project's implementation. This is, I would say, the first draft. It works and it has integration tests (100% coverage, and I hope it will stay this way). There are also some docs (mostly README.md) and several handy integrations (travis, credo, coveralls.io etc.).
Even though there is
Dockerfile
on my TODO list, I think I'll skip it and we may add it later on. It's not needed, just very convenient.TODO: