Compare commits

...

3 Commits

Author SHA1 Message Date
Shadowfacts 7a1b277ea7 Add sentry 2024-04-12 16:38:55 -04:00
Shadowfacts 97a06e7691 Remove database 2024-04-12 16:16:46 -04:00
Shadowfacts 8024e27ebe Remove transaction ID 2024-04-11 11:55:26 -04:00
23 changed files with 86 additions and 414 deletions

View File

@ -1,5 +1,5 @@
[
import_deps: [:ecto, :ecto_sql, :phoenix],
subdirectories: ["priv/*/migrations"],
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}", "priv/*/seeds.exs"]
import_deps: [:phoenix],
subdirectories: [],
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
]

View File

@ -8,7 +8,6 @@
import Config
config :tusker_push,
ecto_repos: [TuskerPush.Repo],
generators: [timestamp_type: :utc_datetime]
# Configures the endpoint
@ -30,6 +29,12 @@ config :logger, :console,
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
config :sentry,
environment_name: Mix.env(),
enable_source_code_context: true,
root_source_code_paths: [File.cwd!()],
client: TuskerPush.SentryFinchClient
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

View File

@ -1,15 +1,5 @@
import Config
# Configure your database
config :tusker_push, TuskerPush.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "tusker_push_dev",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10
# For development, we disable any cache and enable
# debugging and code reloading.
#

View File

@ -21,21 +21,6 @@ if System.get_env("PHX_SERVER") do
end
if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
config :tusker_push, TuskerPush.Repo,
# ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
socket_options: maybe_ipv6
# The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you
# want to use a different value for prod and you most likely don't want
@ -96,4 +81,6 @@ if config_env() == :prod do
# force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.
config :sentry, dsn: System.fetch_env!("SENTRY_DSN")
end

View File

@ -1,18 +1,5 @@
import Config
# Configure your database
#
# The MIX_TEST_PARTITION environment variable can be used
# to provide built-in test partitioning in CI environment.
# Run `mix help test` for more information.
config :tusker_push, TuskerPush.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "tusker_push_test#{System.get_env("MIX_TEST_PARTITION")}",
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: System.schedulers_online() * 2
# We don't run a server during test. If one is required,
# you can enable the server option below.
config :tusker_push, TuskerPushWeb.Endpoint,

View File

@ -6,56 +6,4 @@ defmodule TuskerPush do
Contexts are also responsible for managing your data, regardless
if it comes from the database, an external API or others.
"""
alias TuskerPush.Registration
alias TuskerPush.Repo
@spec register(Map.t()) ::
{:ok, Registration.t()} | {:error, Ecto.Changeset.t()}
def register(params) do
params
|> Registration.create_changeset()
|> Repo.insert()
end
@spec update_registration(Registration.t(), Map.t()) ::
{:ok, Registration.t()} | {:error, Ecto.Changeset.t()}
def update_registration(registration, params) do
registration
|> Registration.update_changeset(params)
|> Repo.update()
end
@spec unregister(String.t()) :: :ok | {:error, :no_registration | Ecto.Changeset.t()}
def unregister(id) when is_binary(id) do
with registration when not is_nil(registration) <- Repo.get(Registration, id),
{:ok, _} <- Repo.delete(registration) do
:ok
else
nil ->
{:error, :no_registration}
{:error, reason} ->
{:error, reason}
end
end
@spec unregister(Registration.t()) :: :ok | {:error, Ecto.Changeset.t()}
def unregister(%Registration{} = registration) do
case Repo.delete(registration) do
{:ok, _} -> :ok
{:error, reason} -> {:error, reason}
end
end
@spec get_registration(String.t()) :: Registration.t() | nil
def get_registration(id) do
Repo.get(Registration, id)
end
@spec check_registration_expired(Registration.t()) :: :ok | {:expired, Registration.t()}
def check_registration_expired(_registration) do
# TODO: expiration & grace period
:ok
end
end

View File

@ -1,37 +1,37 @@
defmodule TuskerPush.Apns do
alias TuskerPush.Registration
require Logger
@spec send(Registration.t(), Map.t()) :: :ok | {:error, term()}
def send(registration, payload) do
@type environment() :: :development | :production
@spec send(environment(), String.t(), Map.t()) :: :ok | {:error, term()}
def send(apns_env, apns_device_token, payload) do
with {:ok, body} <- Jason.encode(payload, pretty_print: false),
req <- make_request(registration, body),
req <- make_request(apns_env, apns_device_token, body),
{:ok, resp} <- Finch.request(req, TuskerPush.Finch) do
handle_response(resp, registration, payload)
handle_response(resp, apns_env, apns_device_token, payload)
else
{:error, %Finch.Error{reason: :connection_closed}} ->
Logger.warning("Apns Finch connection_closed, retrying in 1s")
Process.sleep(1000)
__MODULE__.send(registration, payload)
__MODULE__.send(apns_env, apns_device_token, payload)
{:error, %Finch.Error{reason: :disconnected}} ->
Logger.warning("Apns Finch disconnected, retrying in 1s")
Process.sleep(1000)
__MODULE__.send(registration, payload)
__MODULE__.send(apns_env, apns_device_token, payload)
{:error, %Mint.TransportError{reason: :closed}} ->
Logger.warning("Apns Mint transport closed, retrying in 1s")
Process.sleep(1000)
__MODULE__.send(registration, payload)
__MODULE__.send(apns_env, apns_device_token, payload)
{:error, reason} ->
{:error, reason}
end
end
@spec make_request(Registration.t(), binary()) :: Finch.Request.t()
defp make_request(registration, body) do
@spec make_request(environment(), String.t(), binary()) :: Finch.Request.t()
defp make_request(apns_env, apns_device_token, body) do
bundle_id = Application.fetch_env!(:tusker_push, :apns)[:bundle_id]
headers = [
@ -45,16 +45,17 @@ defmodule TuskerPush.Apns do
Finch.build(
:post,
"https://#{host(registration.apns_environment)}/3/device/#{registration.apns_device_token}",
"https://#{host(apns_env)}/3/device/#{apns_device_token}",
headers,
body
)
end
@spec handle_response(Finch.Response.t(), Registration.t(), Map.t()) :: :ok | {:error, term()}
@spec handle_response(Finch.Response.t(), environment(), String.t(), Map.t()) ::
:ok | {:error, term()}
defp handle_response(resp, registration, payload) do
maybe_log_unique_id(resp, registration.apns_environment)
defp handle_response(resp, apns_env, apns_device_token, payload) do
maybe_log_unique_id(resp, apns_env)
if resp.status in 200..299 do
:ok
@ -71,7 +72,7 @@ defmodule TuskerPush.Apns do
case {resp.status, info} do
{403, %{reason: "ExpiredProviderToken"}} ->
Logger.warning("Expired provider token, retrying")
__MODULE__.send(registration, payload)
__MODULE__.send(apns_env, apns_device_token, payload)
{410, %{reason: "Unregistered"}} ->
Logger.warning("Device token unregistered")
@ -106,7 +107,7 @@ defmodule TuskerPush.Apns do
defp maybe_log_unique_id(_resp, :production), do: :ok
@spec host(:development | :production) :: String.t()
@spec host(environment()) :: String.t()
def host(:development) do
"api.sandbox.push.apple.com"

View File

@ -7,9 +7,12 @@ defmodule TuskerPush.Application do
@impl true
def start(_type, _args) do
:logger.add_handler(:tusker_push_sentry_handler, Sentry.LoggerHandler, %{
config: %{metadata: [:file, :line]}
})
children = [
TuskerPushWeb.Telemetry,
TuskerPush.Repo,
{DNSCluster, query: Application.get_env(:tusker_push, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: TuskerPush.PubSub},
# Start a worker by calling: TuskerPush.Worker.start_link(arg)

View File

@ -1,12 +1,18 @@
defmodule TuskerPush.Forwarder do
alias TuskerPush.Apns
alias TuskerPush.Registration
require Logger
@spec forward(Registration.t(), binary(), String.t(), String.t(), String.t() | nil) ::
@spec forward(
Apns.environment(),
String.t(),
binary(),
String.t(),
String.t(),
String.t() | nil
) ::
:ok | {:error, term()}
def forward(%Registration{push_version: 1} = registration, body, salt, key, context) do
def forward(apns_env, apns_device_token, body, salt, key, context) do
payload = %{
"aps" => %{
"alert" => %{
@ -29,6 +35,6 @@ defmodule TuskerPush.Forwarder do
Logger.debug("Sending #{inspect(payload)}")
Apns.send(registration, payload)
Apns.send(apns_env, apns_device_token, payload)
end
end

View File

@ -1,54 +0,0 @@
defmodule TuskerPush.Registration do
use Ecto.Schema
import Ecto.Changeset
@type t() :: %__MODULE__{
id: Ecto.UUID.t(),
storekit_original_transaction_id: String.t(),
apns_environment: String.t(),
apns_device_token: String.t(),
push_version: integer(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@primary_key {:id, Ecto.UUID, autogenerate: true}
schema "registrations" do
field :storekit_original_transaction_id, :string
field :apns_environment, Ecto.Enum, values: [:production, :development]
# hex-encoded
field :apns_device_token, :string
field :push_version, :integer
timestamps()
end
@create_fields [
:storekit_original_transaction_id,
:apns_environment,
:apns_device_token,
:push_version
]
def create_changeset(registration \\ %__MODULE__{}, params) do
registration
|> cast(params, @create_fields)
|> validate_required(@create_fields)
end
@update_fields [
:apns_environment,
:apns_device_token,
:push_version
]
def update_changeset(registration, params) do
registration
|> cast(params, @update_fields)
|> validate_required(@update_fields)
end
end

View File

@ -1,5 +0,0 @@
defmodule TuskerPush.Repo do
use Ecto.Repo,
otp_app: :tusker_push,
adapter: Ecto.Adapters.Postgres
end

View File

@ -0,0 +1,21 @@
defmodule TuskerPush.SentryFinchClient do
@behaviour Sentry.HTTPClient
@impl true
def child_spec do
Supervisor.child_spec({Finch, name: __MODULE__}, id: __MODULE__)
end
@impl true
def post(url, headers, body) do
request = Finch.build(:post, url, headers, body)
case Finch.request(request, __MODULE__) do
{:ok, %Finch.Response{status: status, headers: resp_headers, body: resp_body}} ->
{:ok, status, resp_headers, resp_body}
{:error, error} ->
{:error, error}
end
end
end

View File

@ -1,108 +0,0 @@
defmodule TuskerPushWeb.AppRegistrationsController do
alias TuskerPush.Registration
alias Ecto.Changeset
use TuskerPushWeb, :controller
require Logger
def create(conn, %{
"transaction_id" => transaction_id,
"environment" => env,
"device_token" => token,
"push_version" => version
}) do
with {:ok, %Registration{id: id}} <-
TuskerPush.register(%{
storekit_original_transaction_id: transaction_id,
apns_environment: env,
apns_device_token: token,
push_version: version
}) do
conn
|> json(%{
id: id,
endpoint: url(~p"/mastodon/v1/push/#{id}"),
device_token: token
})
else
{:error, %Changeset{valid?: false} = changeset} ->
errors =
changeset.errors
|> Enum.map(fn {k, {reason, _}} -> %{key: k, reason: reason} end)
conn
|> put_status(400)
|> json(%{error: "validation failed", fields: errors})
{:error, reason} ->
Logger.error("Failed creating registration: #{inspect(reason)}")
conn
|> put_status(500)
|> json(%{error: "unknown error", fields: []})
end
end
def create(conn, _) do
conn
|> put_status(400)
|> json(%{error: "missing required parameters"})
end
def update(conn, %{
"id" => id,
"environment" => env,
"device_token" => token,
"push_version" => version
}) do
params = %{
apns_environment: env,
apns_device_token: token,
push_version: version
}
with {:registration, registration} when not is_nil(registration) <-
{:registration, TuskerPush.get_registration(id)},
{:ok, _} <- TuskerPush.update_registration(registration, params) do
conn
|> json(%{
id: id,
endpoint: url(~p"/mastodon/v1/push/#{id}"),
device_token: token
})
else
{:registration, nil} ->
conn
|> put_status(404)
|> json(%{error: "not found", fields: []})
{:error, %Changeset{valid?: false} = changeset} ->
errors =
changeset.errors
|> Enum.map(fn {k, {reason, _}} -> %{key: k, reason: reason} end)
conn
|> put_status(400)
|> json(%{error: "validation failed", fields: errors})
{:error, reason} ->
Logger.error("Failed updating registration: #{inspect(reason)}")
conn
|> put_status(500)
|> json(%{error: "unknown error", fields: []})
end
end
def update(conn, _) do
conn
|> put_status(400)
|> json(%{error: "missing required parameters"})
end
def delete(conn, %{"id" => id}) do
TuskerPush.unregister(id)
json(conn, %{status: "ok"})
end
end

View File

@ -4,24 +4,19 @@ defmodule TuskerPushWeb.PushController do
require Logger
def push(conn, %{"id" => id} = params) do
with {:registration, registration} when not is_nil(registration) <-
{:registration, TuskerPush.get_registration(id)},
:ok <- TuskerPush.check_registration_expired(registration),
def push(conn, %{"env" => env, "apns_device_token" => apns_device_token, "ctx" => context}) do
with {:apns_env, apns_env} <- {:apns_env, get_apns_env(env)},
{:encoding, ["aesgcm"]} <- {:encoding, get_req_header(conn, "content-encoding")},
{:body, {:ok, body, conn}} <- {:body, read_body(conn)},
{:salt, salt} when not is_nil(salt) <- get_salt(conn),
{:key, key} when not is_nil(key) <- get_key(conn),
context <- Map.get(params, "ctx"),
{:forward, :ok} <- {:forward, Forwarder.forward(registration, body, salt, key, context)} do
{:forward, :ok} <-
{:forward, Forwarder.forward(apns_env, apns_device_token, body, salt, key, context)} do
send_resp(conn, 200, "ok")
else
{:registration, nil} ->
send_resp(conn, 400, "unregistered")
{:expired, registration} ->
TuskerPush.unregister(registration)
send_resp(conn, 400, "expired")
{:apns_env, nil} ->
Logger.error("Bad environment: #{env}")
send_resp(conn, 400, "bad env")
{:encoding, encoding} ->
Logger.warning("Unexpected encoding: #{inspect(encoding)}")
@ -45,7 +40,6 @@ defmodule TuskerPushWeb.PushController do
{:forward, {:error, :device_token_unregistered}} ->
Logger.debug("APNS device token unregistered, removing registration")
TuskerPush.unregister(id)
send_resp(conn, 400, "apns unregistered")
{:forward, {:error, reason}} ->
@ -54,6 +48,10 @@ defmodule TuskerPushWeb.PushController do
end
end
defp get_apns_env("development"), do: :development
defp get_apns_env("production"), do: :production
defp get_apns_env(_), do: nil
defp get_salt(conn) do
conn
|> get_req_header("encryption")

View File

@ -1,4 +1,5 @@
defmodule TuskerPushWeb.Endpoint do
use Sentry.PlugCapture
use Phoenix.Endpoint, otp_app: :tusker_push
# The session will be stored in the cookie and signed,
@ -29,7 +30,6 @@ defmodule TuskerPushWeb.Endpoint do
# :code_reloader configuration of your endpoint.
if code_reloading? do
plug Phoenix.CodeReloader
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :tusker_push
end
plug Plug.RequestId
@ -40,6 +40,8 @@ defmodule TuskerPushWeb.Endpoint do
pass: ["*/*"],
json_decoder: Phoenix.json_library()
plug Sentry.PlugContext
plug Plug.MethodOverride
plug Plug.Head
plug Plug.Session, @session_options

View File

@ -5,19 +5,11 @@ defmodule TuskerPushWeb.Router do
plug :accepts, ["json"]
end
scope "/app", TuskerPushWeb do
scope "/push", TuskerPushWeb do
pipe_through :api
scope "/v1" do
resources "/registrations", AppRegistrationsController, only: [:create, :update, :delete]
end
end
scope "/mastodon", TuskerPushWeb do
pipe_through :api
scope "/v1" do
post "/push/:id", PushController, :push
post "/:env/:apns_device_token/:ctx", PushController, :push
end
end
end

12
mix.exs
View File

@ -33,9 +33,6 @@ defmodule TuskerPush.MixProject do
defp deps do
[
{:phoenix, "~> 1.7.11"},
{:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.10"},
{:postgrex, ">= 0.0.0"},
{:telemetry_metrics, "~> 0.6"},
{:telemetry_poller, "~> 1.0"},
{:jason, "~> 1.2"},
@ -43,7 +40,8 @@ defmodule TuskerPush.MixProject do
{:bandit, "~> 1.2"},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:finch, "~> 0.18"},
{:jose, "~> 1.11"}
{:jose, "~> 1.11"},
{:sentry, "~> 10.3.0"}
]
end
@ -55,10 +53,8 @@ defmodule TuskerPush.MixProject do
# See the documentation for `Mix` for more info on aliases.
defp aliases do
[
setup: ["deps.get", "ecto.setup"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
setup: ["deps.get"],
release: ["sentry.package_source_code", "release"]
]
end
end

View File

@ -1,29 +1,24 @@
%{
"bandit": {:hex, :bandit, "1.4.2", "a1475c8dcbffd1f43002797f99487a64c8444753ff2b282b52409e279488e1f5", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3db8bacea631bd926cc62ccad58edfee4252d1b4c5cccbbad9825df2722b884f"},
"castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"},
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"phoenix": {:hex, :phoenix, "1.7.11", "1d88fc6b05ab0c735b250932c4e6e33bfa1c186f76dcf623d8dd52f07d6379c7", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "b1ec57f2e40316b306708fe59b92a16b9f6f4bf50ccfa41aa8c7feb79e0ec02a"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
"sentry": {:hex, :sentry, "10.3.0", "4b7543dfea5e59f3be6db28a032427884d55fbc828173b23115064e75dcb1eed", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c1c08ba57f0634b7fda92adb0818ea0677e043e2d28ea4464351a0e4e8e142e5"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},

View File

@ -1,4 +0,0 @@
[
import_deps: [:ecto_sql],
inputs: ["*.exs"]
]

View File

@ -1,18 +0,0 @@
defmodule TuskerPush.Repo.Migrations.CreateRegistrations do
use Ecto.Migration
def change do
create table(:registrations, primary_key: false) do
add :id, :uuid, primary_key: true, null: false
add :storekit_original_transaction_id, :string, null: false
add :apns_environment, :string, null: false
add :apns_device_token, :string, null: false
add :push_version, :integer, null: false
timestamps()
end
end
end

View File

@ -1,11 +0,0 @@
# Script for populating the database. You can run it as:
#
# mix run priv/repo/seeds.exs
#
# Inside the script, you can read and write to any of your
# repositories directly:
#
# TuskerPush.Repo.insert!(%TuskerPush.SomeSchema{})
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.

View File

@ -1,58 +0,0 @@
defmodule TuskerPush.DataCase do
@moduledoc """
This module defines the setup for tests requiring
access to the application's data layer.
You may define functions here to be used as helpers in
your tests.
Finally, if the test case interacts with the database,
we enable the SQL sandbox, so changes done to the database
are reverted at the end of every test. If you are using
PostgreSQL, you can even run database tests asynchronously
by setting `use TuskerPush.DataCase, async: true`, although
this option is not recommended for other databases.
"""
use ExUnit.CaseTemplate
using do
quote do
alias TuskerPush.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import TuskerPush.DataCase
end
end
setup tags do
TuskerPush.DataCase.setup_sandbox(tags)
:ok
end
@doc """
Sets up the sandbox based on the test tags.
"""
def setup_sandbox(tags) do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(TuskerPush.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
end
@doc """
A helper that transforms changeset errors into a map of messages.
assert {:error, changeset} = Accounts.create_user(%{password: "short"})
assert "password is too short" in errors_on(changeset).password
assert %{password: ["password is too short"]} = errors_on(changeset)
"""
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
end
end

View File

@ -1,2 +1 @@
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(TuskerPush.Repo, :manual)