Compare commits

..

5 commits
qonto ... main

Author SHA1 Message Date
Pierre de Lacroix
b06cf0b98a
add docker release 2026-01-23 00:08:41 +01:00
Pierre de Lacroix
6f80299dbc
add cors_plug 2026-01-23 00:08:18 +01:00
66a45b0fb1 Merge pull request 'Connection with Grist' (#2) from grist into main
Reviewed-on: #2
2026-01-22 18:37:46 +00:00
Pierre de Lacroix
c95d877e68
add fundraising API 2026-01-22 19:36:18 +01:00
Pierre de Lacroix
d2ec7b12a9
add gauge API 2026-01-22 19:34:39 +01:00
27 changed files with 247 additions and 597 deletions

46
.dockerignore Normal file
View file

@ -0,0 +1,46 @@
# This file excludes paths from the Docker build context.
#
# By default, Docker's build context includes all files (and folders) in the
# current directory. Even if a file isn't copied into the container it is still sent to
# the Docker daemon.
#
# There are multiple reasons to exclude files from the build context:
#
# 1. Prevent nested folders from being copied into the container (ex: exclude
# /assets/node_modules when copying /assets)
# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc)
# 3. Avoid sending files containing sensitive information
#
# More information on using .dockerignore is available here:
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
.dockerignore
# Ignore git, but keep git HEAD and refs to access current commit hash if needed:
#
# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat
# d0b8727759e1e0e7aa3d41707d12376e373d5ecc
.git
!.git/HEAD
!.git/refs
# Common development/test artifacts
/cover/
/doc/
/test/
/tmp/
.elixir_ls
# Mix artifacts
/_build/
/deps/
*.ez
# Generated on crash by the VM
erl_crash.dump
# Static artifacts - These should be fetched and built inside the Docker image
# https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Release.html#module-docker
/assets/node_modules/
/priv/static/assets/
/priv/static/cache_manifest.json

94
Dockerfile Normal file
View file

@ -0,0 +1,94 @@
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian
# instead of Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?name=ubuntu
# https://hub.docker.com/_/ubuntu/tags
#
# This file is based on these images:
#
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
# - https://hub.docker.com/_/debian/tags?name=trixie-20260112-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: docker.io/hexpm/elixir:1.16.2-erlang-26.1.1-debian-trixie-20260112-slim
#
ARG ELIXIR_VERSION=1.16.2
ARG OTP_VERSION=26.1.1
ARG DEBIAN_VERSION=trixie-20260112-slim
ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}"
FROM ${BUILDER_IMAGE} AS builder
# install build dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential git \
&& rm -rf /var/lib/apt/lists/*
# prepare build dir
WORKDIR /app
# install hex + rebar
RUN mix local.hex --force \
&& mix local.rebar --force
# set build ENV
ENV MIX_ENV="prod"
# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config
# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile
COPY priv priv
COPY lib lib
# Compile the release
RUN mix compile
# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/
COPY rel rel
RUN mix release
# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE} AS final
RUN apt-get update \
&& apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \
&& locale-gen
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
WORKDIR "/app"
RUN chown nobody /app
# set runner ENV
ENV MIX_ENV="prod"
# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/camp_api ./
USER nobody
# If using an environment that doesn't automatically reap zombie processes, it is
# advised to add an init process such as tini via `apt-get install`
# above and adding an entrypoint. See https://github.com/krallin/tini for details
# ENTRYPOINT ["/tini", "--"]
CMD ["/app/bin/server"]

View file

@ -29,9 +29,6 @@ config :logger, :default_formatter,
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
config :oauth2, adapter: Tesla.Adapter.Mint
config :oauth2, middleware: [{Tesla.Middleware.FollowRedirects, max_redirects: 3}, {Tesla.Middleware.Logger, debug: true}]
# 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

@ -42,6 +42,8 @@ config :camp_api, CampApiWeb.Endpoint,
# Enable dev routes for dashboard and mailbox
config :camp_api, dev_routes: true
config :camp_api, :grist, api_key: System.get_env("GRIST_API_KEY")
# Do not include metadata nor timestamps in development logs
config :logger, :default_formatter, format: "[$level] $message\n"
@ -51,9 +53,3 @@ config :phoenix, :stacktrace_depth, 20
# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime
config :oauth2, debug: true
config :camp_api, :qonto_url,
staging: true,
base_url: "https://thirdparty-sandbox.staging.qonto.co"

View file

@ -23,6 +23,8 @@ end
config :camp_api, CampApiWeb.Endpoint,
http: [port: String.to_integer(System.get_env("PORT", "4000"))]
config :camp_api, :grist, api_key: System.get_env("GRIST_API_KEY")
if config_env() == :prod do
# 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

View file

@ -14,8 +14,7 @@ defmodule CampApi.Application do
# Start a worker by calling: CampApi.Worker.start_link(arg)
# {CampApi.Worker, arg},
# Start to serve requests, typically the last entry
CampApiWeb.Endpoint,
CampApi.PaymentLinks.Qonto
CampApiWeb.Endpoint
]
# See https://hexdocs.pm/elixir/Supervisor.html

21
lib/camp_api/grist.ex Normal file
View file

@ -0,0 +1,21 @@
defmodule CampApi.Grist do
@moduledoc false
@api_url "https://grist.interhacker.space/o/camp"
def get_records(doc, table) do
url = "#{@api_url}/api/docs/#{doc}/tables/#{table}/records"
request(:get, url)
end
defp request(method, url) do
Req.request(
method: method,
url: url,
headers: [
{"authorization", "Bearer " <> Application.fetch_env!(:camp_api, :grist)[:api_key]}
]
)
end
end

View file

@ -0,0 +1,14 @@
defmodule CampApi.Grist.Fundraising do
@moduledoc false
alias CampApi.Grist
@gauge_doc "wWacvVWE9QtQ"
@gauge_table "Dons"
def get() do
{:ok, %{body: %{"records" => records}}} = Grist.get_records(@gauge_doc, @gauge_table)
records
end
end

View file

@ -0,0 +1,14 @@
defmodule CampApi.Grist.Gauge do
@moduledoc false
alias CampApi.Grist
@gauge_doc "wpwHRjzfdR9A"
@gauge_table "Infos"
def get() do
{:ok, %{body: %{"records" => records}}} = Grist.get_records(@gauge_doc, @gauge_table)
length(records)
end
end

View file

@ -1,110 +0,0 @@
defmodule CampApi.PaymentLinks do
@moduledoc """
The PaymentLinks context.
"""
# import Ecto.Query, warn: false
alias CampApi.Repo
alias CampApi.PaymentLinks.Link
alias CampApi.PaymentLinks.Qonto
def connect() do
body = %{
"partner_callback_url" => "http://localhost:4000",
"user_bank_account_id" => "01982d1e-6813-7bdf-a96f-0c583e6fa063",
"user_phone_number" => "+33612345678",
"user_website_url" => "http://localhost:4000",
"business_description" => "This needs a long long long description. This needs a long long long description."
}
Qonto.request(:post, "/v2/payment_links/connections", body)
end
@doc """
Returns the list of links.
## Examples
iex> list_links()
[%Link{}, ...]
"""
def list_links do
raise "TODO"
end
@doc """
Gets a single link.
Raises if the Link does not exist.
## Examples
iex> get_link!(123)
%Link{}
"""
def get_link!(id), do: raise "TODO"
@doc """
Creates a link.
## Examples
iex> create_link(%{field: value})
{:ok, %Link{}}
iex> create_link(%{field: bad_value})
{:error, ...}
"""
def create_link(attrs) do
raise "TODO"
end
@doc """
Updates a link.
## Examples
iex> update_link(link, %{field: new_value})
{:ok, %Link{}}
iex> update_link(link, %{field: bad_value})
{:error, ...}
"""
def update_link(%Link{} = link, attrs) do
raise "TODO"
end
@doc """
Deletes a Link.
## Examples
iex> delete_link(link)
{:ok, %Link{}}
iex> delete_link(link)
{:error, ...}
"""
def delete_link(%Link{} = link) do
raise "TODO"
end
@doc """
Returns a data structure for tracking link changes.
## Examples
iex> change_link(link)
%Todo{...}
"""
def change_link(%Link{} = link, _attrs \\ %{}) do
raise "TODO"
end
end

View file

@ -1,3 +0,0 @@
defmodule CampApi.PaymentLinks.Link do
defstruct name: "John", age: 27
end

View file

@ -1,151 +0,0 @@
defmodule CampApi.PaymentLinks.Qonto do
use GenServer
use CampApiWeb, :verified_routes
def base_url() do
if Application.get_env(CampApi, :qonto, :staging) do
"https://thirdparty-sandbox.staging.qonto.co"
else
""
end
end
def oauth_url() do
if Application.get_env(CampApi, :qonto, :staging) do
"https://oauth-sandbox.staging.qonto.co"
else
"https://oauth.qonto.com"
end
end
def client_id() do
if Application.get_env(CampApi, :qonto, :staging) do
"d78920aa-e35f-4bfd-97df-e457f8337e2d"
else
""
end
end
def client_secret() do
if Application.get_env(CampApi, :qonto, :staging) do
"EqrS29dZYPy1Nfg8V8Bt6aRR1u"
else
""
end
end
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
def request(method, url, body) do
GenServer.call(__MODULE__, {:request, method, url, body})
end
def init(%{}) do
oauth_client =
OAuth2.Client.new(
strategy: OAuth2.Strategy.AuthCode,
client_id: client_id(),
client_secret: client_secret(),
site: oauth_url(),
redirect_uri: url(~p"/api/qonto_auth"),
authorize_url: "/oauth2/auth",
token_url: "/oauth2/token"
)
|> OAuth2.Client.put_serializer("application/json;charset=UTF-8", Jason)
|> OAuth2.Client.put_serializer("application/json", Jason)
{:ok, %{status: :disconnected, auth_state: nil, oauth_client: oauth_client}, {:continue, nil}}
end
def handle_continue(_, state) do
auth_state = :crypto.strong_rand_bytes(24) |> Base.url_encode64(padding: false)
# auth_url = %URI{
# # scheme: "https",
# # host: oauth_url(),
# URI.parse(oauth_url()) |
# path: "/oauth2/auth",
# query:
# URI.encode_query(%{
# client_id: client_id(),
# redirect_uri: url(~p"/api/qonto_auth"),
# scope: "payment_link.write payment_link.read",
# response_type: "code",
# state: auth_state
# # organization_id: TODO
# })
# }
auth_url = OAuth2.Client.authorize_url!(state.oauth_client, state: auth_state, scope: "payment_link.write payment_link.read")
IO.puts("Plz go to the following URL:")
IO.puts(auth_url)
{:noreply, %{state | status: :pending, auth_state: auth_state}}
end
def create_token(code, auth_state) do
GenServer.call(__MODULE__, {:create_token, code, auth_state})
end
def handle_call({:create_token, code, auth_state}, _from, state) do
# with state.status == :pending && state.auth_state == auth_state,
# {:ok, %{body: %{"json" => %{"access_token" => access_token, "refresh_token" => refresh_token}}}} <- Req.post(base_url: oauth_url(), url: "/outh2/token",
# {:reply, :ok, %{state | status: :connected,
#
headers =
if Application.get_env(CampApi, :qonto, :staging) do
[
{"x-qonto-staging-token", "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w="},
{"authorization", ""}
]
else
[]
end
with state.status == :pending && state.auth_state == auth_state,
{:ok, oauth_client} <- OAuth2.Client.get_token(state.oauth_client, [code: code, client_secret: state.oauth_client.client_secret], headers) do
{:reply, :ok, %{state | status: :connected, oauth_client: oauth_client}}
else
_ ->
{:reply, :error, state}
end
end
# def handle_call({:request, method, url, body}, _from, state) do
# access_token =
# headers =
# if Application.get_env(CampApi, :qonto, :staging) do
# [
# authorization: "Bearer #{state.access_token}",
# x_qonto_staging_token: "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w="
# ]
# else
# []
# end
#
# Req.request(method: method, base_url: base_url(), url: url, headers: headers, json: body)
# end
def handle_call({:request, method, url, body}, _from, state) do
url = base_url() <> url
headers =
if Application.get_env(CampApi, :qonto, :staging) do
[
# x_qonto_staging_token: "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w="
{"x-qonto-staging-token", "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w="}
]
else
[]
end
case OAuth2.Request.request(method, state.oauth_client, url, Jason.encode!(body), headers, []) do
{:ok, resp} -> {:reply, {:ok, resp}, state}
_ -> {:reply, :error, state}
end
end
end

View file

@ -1,25 +0,0 @@
defmodule CampApiWeb.ChangesetJSON do
@doc """
Renders changeset errors.
"""
def error(%{changeset: changeset}) do
# When encoded, the changeset returns its errors
# as a JSON object. So we just pass it forward.
%{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)}
end
defp translate_error({msg, opts}) do
# You can make use of gettext to translate error messages by
# uncommenting and adjusting the following code:
# if count = opts[:count] do
# Gettext.dngettext(CampApiWeb.Gettext, "errors", msg, msg, count, opts)
# else
# Gettext.dgettext(CampApiWeb.Gettext, "errors", msg, opts)
# end
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
end)
end
end

View file

@ -1,16 +0,0 @@
defmodule CampApiWeb.FallbackController do
@moduledoc """
Translates controller action results into valid `Plug.Conn` responses.
See `Phoenix.Controller.action_fallback/1` for more details.
"""
use CampApiWeb, :controller
# This clause is an example of how to handle resources that cannot be found.
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> put_view(html: CampApiWeb.ErrorHTML, json: CampApiWeb.ErrorJSON)
|> render(:"404")
end
end

View file

@ -0,0 +1,21 @@
defmodule CampApiWeb.GristController do
use CampApiWeb, :controller
alias CampApi.Grist
alias CampApi.Grist.Gauge
alias CampApi.Grist.Fundraising
action_fallback CampApiWeb.FallbackController
def gauge(conn, _params) do
gauge = Gauge.get()
render(conn, :gauge, gauge: gauge)
end
def fundraising(conn, _params) do
fundraising = Fundraising.get()
render(conn, :fundraising, fundraising: fundraising)
end
end

View file

@ -0,0 +1,16 @@
defmodule CampApiWeb.GristJSON do
alias CampApi.Grist.Gauge
def gauge(%{gauge: gauge}) do
%{gauge: gauge}
end
def fundraising(%{fundraising: fundraising}) do
total =
fundraising
|> Enum.map(fn record -> get_in(record, ["fields", "Montant"]) end)
|> Enum.sum()
%{total: total}
end
end

View file

@ -1,58 +0,0 @@
defmodule CampApiWeb.PaymentLinkController do
use CampApiWeb, :controller
alias CampApi.PaymentLinks
alias CampApi.PaymentLinks.Link
alias CampApi.PaymentLinks.Qonto
action_fallback CampApiWeb.FallbackController
def qonto_auth(conn, %{"code" => code, "state" => state}) do
case Qonto.create_token(code, state) do
:ok ->
conn
# |> put_resp_content_type
|> send_resp(200, "connected")
:error ->
conn
# |> put_resp_content_type
|> send_resp(500, "error")
end
end
# def index(conn, _params) do
# links = PaymentLinks.list_links()
# render(conn, :index, links: links)
# end
def create(conn, %{"link" => link_params}) do
with {:ok, %Link{} = link} <- PaymentLinks.create_link(link_params) do
conn
|> put_status(:created)
|> put_resp_header("location", ~p"/api/links/#{link}")
|> render(:show, link: link)
end
end
# def show(conn, %{"id" => id}) do
# link = PaymentLinks.get_link!(id)
# render(conn, :show, link: link)
# end
# def update(conn, %{"id" => id, "link" => link_params}) do
# link = PaymentLinks.get_link!(id)
#
# with {:ok, %Link{} = link} <- PaymentLinks.update_link(link, link_params) do
# render(conn, :show, link: link)
# end
# end
# def delete(conn, %{"id" => id}) do
# link = PaymentLinks.get_link!(id)
#
# with {:ok, %Link{}} <- PaymentLinks.delete_link(link) do
# send_resp(conn, :no_content, "")
# end
# end
end

View file

@ -1,24 +0,0 @@
defmodule CampApiWeb.PaymentLinkJSON do
alias CampApi.PaymentLinks.Link
@doc """
Renders a list of links.
"""
def index(%{links: links}) do
%{data: for(link <- links, do: data(link))}
end
@doc """
Renders a single link.
"""
def show(%{link: link}) do
%{data: data(link)}
end
defp data(%Link{} = link) do
%{
id: link.id,
price: link.price
}
end
end

View file

@ -1,16 +1,17 @@
defmodule CampApiWeb.Router do
use CampApiWeb, :router
pipeline :api do
pipeline :public_api do
plug :accepts, ["json"]
plug CORSPlug, origin: ["*"]
end
scope "/api", CampApiWeb do
pipe_through :api
pipe_through :public_api
resources "/payment_links", PaymentLinkController, only: [:create]
get "/gauge", GristController, :gauge
get "/qonto_auth", PaymentLinkController, :qonto_auth
get "/fundraising", GristController, :fundraising
end
# Enable LiveDashboard in development

View file

@ -47,7 +47,7 @@ defmodule CampApi.MixProject do
{:dns_cluster, "~> 0.2.0"},
{:bandit, "~> 1.5"},
{:req, "~> 0.5.0"},
{:oauth2, "~> 2.0"}
{:cors_plug, "~> 3.0"}
]
end

View file

@ -1,14 +1,14 @@
%{
"bandit": {:hex, :bandit, "1.9.0", "6dc1ff2c30948dfecf32db574cc3447c7b9d70e0b61140098df3818870b01b76", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [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", "2538aaa1663b40ca9cbd8ca1f8a540cb49e5baf34c6ffef068369cc45f9146f2"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
"finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"oauth2": {:hex, :oauth2, "2.1.0", "beb657f393814a3a7a8a15bd5e5776ecae341fd344df425342a3b6f1904c2989", [:mix], [{:tesla, "~> 1.5", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "8ac07f85b3307dd1acfeb0ec852f64161b22f57d0ce0c15e616a1dfc8ebe2b41"},
"phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {: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", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"},
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
@ -17,11 +17,10 @@
"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.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [: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", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"},
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
"telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
"tesla": {:hex, :tesla, "1.15.3", "3a2b5c37f09629b8dcf5d028fbafc9143c0099753559d7fe567eaabfbd9b8663", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98bb3d4558abc67b92fb7be4cd31bb57ca8d80792de26870d362974b58caeda7"},
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},

5
rel/overlays/bin/server Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
set -eu
cd -P -- "$(dirname -- "$0")"
PHX_SERVER=true exec ./camp_api start

2
rel/overlays/bin/server.bat Executable file
View file

@ -0,0 +1,2 @@
set PHX_SERVER=true
call "%~dp0\camp_api" start

View file

@ -1,27 +0,0 @@
curl --request POST \
--url https://thirdparty-sandbox.staging.qonto.co/v2/payment_links \
--header 'Authorization: Bearer ory_at_l82B_-dFKvmqmw2wZ3qVuDb85M2jKDUmcX9qUIBu1Bw.SxFi-0Cs2L-kjN5pg9_VeEhHUNo52s3bcqaDsnq2gqs' \
--header 'Content-Type: application/json' \
--header 'X-Qonto-Staging-Token: 0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w=' \
--data '
{
"payment_link": {
"items": [{
"title": "Participation",
"quantity": 1,
"unit_price": {
"value": "10.99",
"currency": "EUR"
},
"vat_rate": "20.0",
"type": "service",
"description": "Participation",
"measure_unit": "unit"
}],
"potential_payment_methods": [
"credit_card"
],
"reusable": false
}
}
'

View file

@ -1,59 +0,0 @@
defmodule CampApi.PaymentLinksTest do
use CampApi.DataCase
alias CampApi.PaymentLinks
describe "links" do
alias CampApi.PaymentLinks.Link
import CampApi.PaymentLinksFixtures
@invalid_attrs %{price: nil}
test "list_links/0 returns all links" do
link = link_fixture()
assert PaymentLinks.list_links() == [link]
end
test "get_link!/1 returns the link with given id" do
link = link_fixture()
assert PaymentLinks.get_link!(link.id) == link
end
test "create_link/1 with valid data creates a link" do
valid_attrs = %{price: "some price"}
assert {:ok, %Link{} = link} = PaymentLinks.create_link(valid_attrs)
assert link.price == "some price"
end
test "create_link/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = PaymentLinks.create_link(@invalid_attrs)
end
test "update_link/2 with valid data updates the link" do
link = link_fixture()
update_attrs = %{price: "some updated price"}
assert {:ok, %Link{} = link} = PaymentLinks.update_link(link, update_attrs)
assert link.price == "some updated price"
end
test "update_link/2 with invalid data returns error changeset" do
link = link_fixture()
assert {:error, %Ecto.Changeset{}} = PaymentLinks.update_link(link, @invalid_attrs)
assert link == PaymentLinks.get_link!(link.id)
end
test "delete_link/1 deletes the link" do
link = link_fixture()
assert {:ok, %Link{}} = PaymentLinks.delete_link(link)
assert_raise Ecto.NoResultsError, fn -> PaymentLinks.get_link!(link.id) end
end
test "change_link/1 returns a link changeset" do
link = link_fixture()
assert %Ecto.Changeset{} = PaymentLinks.change_link(link)
end
end
end

View file

@ -1,84 +0,0 @@
defmodule CampApiWeb.LinkControllerTest do
use CampApiWeb.ConnCase
import CampApi.PaymentLinksFixtures
alias CampApi.PaymentLinks.Link
@create_attrs %{
price: "some price"
}
@update_attrs %{
price: "some updated price"
}
@invalid_attrs %{price: nil}
setup %{conn: conn} do
{:ok, conn: put_req_header(conn, "accept", "application/json")}
end
describe "index" do
test "lists all links", %{conn: conn} do
conn = get(conn, ~p"/api/links")
assert json_response(conn, 200)["data"] == []
end
end
describe "create link" do
test "renders link when data is valid", %{conn: conn} do
conn = post(conn, ~p"/api/links", link: @create_attrs)
assert %{"id" => id} = json_response(conn, 201)["data"]
conn = get(conn, ~p"/api/links/#{id}")
assert %{
"id" => ^id,
"price" => "some price"
} = json_response(conn, 200)["data"]
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post(conn, ~p"/api/links", link: @invalid_attrs)
assert json_response(conn, 422)["errors"] != %{}
end
end
describe "update link" do
setup [:create_link]
test "renders link when data is valid", %{conn: conn, link: %Link{id: id} = link} do
conn = put(conn, ~p"/api/links/#{link}", link: @update_attrs)
assert %{"id" => ^id} = json_response(conn, 200)["data"]
conn = get(conn, ~p"/api/links/#{id}")
assert %{
"id" => ^id,
"price" => "some updated price"
} = json_response(conn, 200)["data"]
end
test "renders errors when data is invalid", %{conn: conn, link: link} do
conn = put(conn, ~p"/api/links/#{link}", link: @invalid_attrs)
assert json_response(conn, 422)["errors"] != %{}
end
end
describe "delete link" do
setup [:create_link]
test "deletes chosen link", %{conn: conn, link: link} do
conn = delete(conn, ~p"/api/links/#{link}")
assert response(conn, 204)
assert_error_sent 404, fn ->
get(conn, ~p"/api/links/#{link}")
end
end
end
defp create_link(_) do
link = link_fixture()
%{link: link}
end
end

View file

@ -1,20 +0,0 @@
defmodule CampApi.PaymentLinksFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `CampApi.PaymentLinks` context.
"""
@doc """
Generate a link.
"""
def link_fixture(attrs \\ %{}) do
{:ok, link} =
attrs
|> Enum.into(%{
price: "some price"
})
|> CampApi.PaymentLinks.create_link()
link
end
end