This commit is contained in:
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
||||||
68
.gitea/workflows/build-and-publish.yaml
Normal file
68
.gitea/workflows/build-and-publish.yaml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
name: Build and Publish
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
env:
|
||||||
|
CHART_NAME: ${{ github.event.repository.name }}
|
||||||
|
IMAGE_NAME: ${{ github.event.repository.name }}
|
||||||
|
jobs:
|
||||||
|
build-release:
|
||||||
|
runs-on: nix
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build Docker Image via Nix Flake
|
||||||
|
run: |
|
||||||
|
nix build .#dockerImage --print-build-logs
|
||||||
|
docker load < result
|
||||||
|
|
||||||
|
- name: Log in to Gitea Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ github.server_url }}
|
||||||
|
username: ${{ secrets.CI_USER }}
|
||||||
|
password: ${{ secrets.CI_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Tag and Push Docker Image
|
||||||
|
run: |
|
||||||
|
VERSION=${{ github.run_number }}
|
||||||
|
|
||||||
|
# Strip https from server URL
|
||||||
|
REGISTRY=${GITHUB_SERVER_URL#https://}
|
||||||
|
|
||||||
|
TARGET_IMAGE=$REGISTRY/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
# Auto-detect the built image name (better version)
|
||||||
|
SOURCE_IMAGE=$(docker load < result | awk '{print $3}')
|
||||||
|
|
||||||
|
docker tag $SOURCE_IMAGE $TARGET_IMAGE:$VERSION
|
||||||
|
docker tag $SOURCE_IMAGE $TARGET_IMAGE:latest
|
||||||
|
docker push $TARGET_IMAGE:$VERSION
|
||||||
|
docker push $TARGET_IMAGE:latest
|
||||||
|
|
||||||
|
- name: Setup Helm
|
||||||
|
uses: azure/setup-helm@v4
|
||||||
|
with:
|
||||||
|
version: v3.14.0
|
||||||
|
|
||||||
|
- name: Package Helm Chart
|
||||||
|
run: |
|
||||||
|
VERSION=${{ github.run_number }}
|
||||||
|
helm repo add bjw-s https://bjw-s-labs.github.io/helm-charts
|
||||||
|
helm dependency build ops/chart
|
||||||
|
helm package ops/chart --version $VERSION --app-version $VERSION
|
||||||
|
|
||||||
|
- name: Push Helm Chart to Gitea Registry
|
||||||
|
run: |
|
||||||
|
VERSION=${{ github.run_number }}
|
||||||
|
CHART_FILE=${{ env.CHART_NAME }}-${VERSION}.tgz
|
||||||
|
|
||||||
|
curl -f --user "${{ secrets.CI_USER }}:${{ secrets.CI_PASSWORD }}" \
|
||||||
|
-X POST \
|
||||||
|
--upload-file ./$CHART_FILE \
|
||||||
|
"${{ github.server_url }}/api/packages/${{ github.repository_owner }}/helm/api/charts"
|
||||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where third-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
provider_service-*.tar
|
||||||
|
|
||||||
|
# Temporary files, for example, from tests.
|
||||||
|
/tmp/
|
||||||
21
README.md
Normal file
21
README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# ProviderService
|
||||||
|
|
||||||
|
**TODO: Add description**
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||||
|
by adding `provider_service` to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
def deps do
|
||||||
|
[
|
||||||
|
{:provider_service, "~> 0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
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/provider_service>.
|
||||||
|
|
||||||
50
config/config.exs
Normal file
50
config/config.exs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import Config
|
||||||
|
|
||||||
|
# This file is responsible for configuring your application
|
||||||
|
# and its dependencies with the aid of the Config module.
|
||||||
|
#
|
||||||
|
# This configuration file is loaded before any dependency and
|
||||||
|
# is restricted to this project.
|
||||||
|
|
||||||
|
# General application configuration
|
||||||
|
config :provider_service,
|
||||||
|
ecto_repos: [ProviderService.Repo],
|
||||||
|
event_stores: [ProviderService.EventStore]
|
||||||
|
|
||||||
|
config :provider_service, ProviderServiceWeb.Endpoint,
|
||||||
|
url: [host: "localhost"],
|
||||||
|
adapter: Bandit.PhoenixAdapter,
|
||||||
|
render_errors: [
|
||||||
|
formats: [json: ProviderServiceWeb.ErrorJSON],
|
||||||
|
layout: false
|
||||||
|
],
|
||||||
|
pubsub_server: ProviderService.PubSub,
|
||||||
|
live_view: [signing_salt: "zPStCmh9"]
|
||||||
|
|
||||||
|
# Configure Elixir's Logger
|
||||||
|
config :logger, :default_formatter,
|
||||||
|
format: "$time $metadata[$level] $message\n",
|
||||||
|
metadata: [:request_id]
|
||||||
|
|
||||||
|
# Use Jason for JSON parsing in Phoenix
|
||||||
|
config :phoenix, :json_library, Jason
|
||||||
|
|
||||||
|
config :provider_service, ProviderService.CommandedApp,
|
||||||
|
event_store: [
|
||||||
|
adapter: Commanded.EventStore.Adapters.EventStore,
|
||||||
|
event_store: ProviderService.EventStore
|
||||||
|
],
|
||||||
|
pub_sub: :local,
|
||||||
|
registry: :local
|
||||||
|
|
||||||
|
config :commanded,
|
||||||
|
event_store_adapter: Commanded.EventStore.Adapters.EventStore
|
||||||
|
|
||||||
|
config :commanded_ecto_projections,
|
||||||
|
repo: ProviderService.Repo
|
||||||
|
|
||||||
|
config :flop, repo: ProviderService.Repo
|
||||||
|
|
||||||
|
# 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"
|
||||||
41
config/dev.exs
Normal file
41
config/dev.exs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import Config
|
||||||
|
|
||||||
|
config :provider_service, ProviderService.Repo,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
hostname: "localhost",
|
||||||
|
database: "provider_service_dev",
|
||||||
|
stacktrace: true,
|
||||||
|
show_sensitive_data_on_connection_error: true,
|
||||||
|
pool_size: 10
|
||||||
|
|
||||||
|
config :provider_service, ProviderServiceWeb.Endpoint,
|
||||||
|
http: [ip: {127, 0, 0, 1}, port: 4002],
|
||||||
|
adapter: Bandit.PhoenixAdapter,
|
||||||
|
check_origin: false,
|
||||||
|
code_reloader: true,
|
||||||
|
debug_errors: true,
|
||||||
|
secret_key_base: "localdevsecretkeybase1234567890localdevsecretkeybase1234567890xx"
|
||||||
|
|
||||||
|
config :ex_aws,
|
||||||
|
access_key_id: "minioadmin",
|
||||||
|
secret_access_key: "minioadmin",
|
||||||
|
region: "us-east-1"
|
||||||
|
|
||||||
|
config :ex_aws, :s3,
|
||||||
|
scheme: "http://",
|
||||||
|
host: "localhost",
|
||||||
|
port: 9000
|
||||||
|
|
||||||
|
config :provider_service, ProviderService.EventStore,
|
||||||
|
serializer: Commanded.Serialization.JsonSerializer,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
database: "provider_service_eventstore_dev",
|
||||||
|
hostname: "localhost",
|
||||||
|
pool_size: 10
|
||||||
|
|
||||||
|
config :provider_service, :s3_bucket, "policy-bucket"
|
||||||
|
|
||||||
|
config :provider_service,
|
||||||
|
solicitation_service_url: "http://localhost:8081"
|
||||||
3
config/prod.exs
Normal file
3
config/prod.exs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import Config
|
||||||
|
|
||||||
|
config :logger, level: :debug
|
||||||
80
config/runtime.exs
Normal file
80
config/runtime.exs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import Config
|
||||||
|
|
||||||
|
logger_level =
|
||||||
|
case System.get_env("LOG_LEVEL", "info") do
|
||||||
|
"debug" -> :debug
|
||||||
|
"info" -> :info
|
||||||
|
"warn" -> :warning
|
||||||
|
"error" -> :error
|
||||||
|
val when val in ["warning", "error"] -> :error
|
||||||
|
_ -> :info
|
||||||
|
end
|
||||||
|
|
||||||
|
config :logger, level: logger_level
|
||||||
|
|
||||||
|
config :logger, :console, format: {Logger.Formatter, :format}
|
||||||
|
|
||||||
|
s3_host = System.get_env("S3_HOST", "dev.s3.corredorconect.com")
|
||||||
|
s3_port = System.get_env("S3_PORT", "443")
|
||||||
|
|
||||||
|
config :ex_aws,
|
||||||
|
access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
|
||||||
|
secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"),
|
||||||
|
region: System.get_env("AWS_REGION", "us-east-1")
|
||||||
|
|
||||||
|
config :ex_aws, :s3,
|
||||||
|
scheme: "https://",
|
||||||
|
host: s3_host,
|
||||||
|
port: s3_port
|
||||||
|
|
||||||
|
config :provider_service, :s3_bucket, System.get_env("S3_BUCKET")
|
||||||
|
|
||||||
|
if System.get_env("PHX_SERVER") do
|
||||||
|
config :provider_service, ProviderServiceWeb.Endpoint, server: true
|
||||||
|
end
|
||||||
|
|
||||||
|
if cookie = System.get_env("RELEASE_COOKIE") do
|
||||||
|
config :elixir, :cookie, cookie
|
||||||
|
end
|
||||||
|
|
||||||
|
config :provider_service, ProviderServiceWeb.Endpoint,
|
||||||
|
http: [port: String.to_integer(System.get_env("PORT", "8080"))]
|
||||||
|
|
||||||
|
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 :provider_service, ProviderService.Repo,
|
||||||
|
url: database_url,
|
||||||
|
pool_size: 1,
|
||||||
|
socket_options: maybe_ipv6
|
||||||
|
|
||||||
|
config :provider_service, ProviderService.EventStore,
|
||||||
|
serializer: Commanded.Serialization.JsonSerializer,
|
||||||
|
url: database_url,
|
||||||
|
pool_size: 1
|
||||||
|
|
||||||
|
secret_key_base =
|
||||||
|
System.get_env("SECRET_KEY_BASE") ||
|
||||||
|
raise """
|
||||||
|
environment variable SECRET_KEY_BASE is missing.
|
||||||
|
You can generate one by calling: mix phx.gen.secret
|
||||||
|
"""
|
||||||
|
|
||||||
|
host = System.get_env("PHX_HOST") || "example.com"
|
||||||
|
|
||||||
|
config :provider_service, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
|
||||||
|
|
||||||
|
config :provider_service, ProviderServiceWeb.Endpoint,
|
||||||
|
url: [host: host, port: 80, scheme: "http"],
|
||||||
|
http: [
|
||||||
|
ip: {0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
|
],
|
||||||
|
secret_key_base: secret_key_base
|
||||||
|
end
|
||||||
27
config/test.exs
Normal file
27
config/test.exs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import Config
|
||||||
|
|
||||||
|
config :provider_service, ProviderService.Repo,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
hostname: "localhost",
|
||||||
|
database: "provider_service_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||||
|
pool: Ecto.Adapters.SQL.Sandbox,
|
||||||
|
pool_size: System.schedulers_online() * 2
|
||||||
|
|
||||||
|
config :provider_service, ProviderServiceWeb.Endpoint,
|
||||||
|
http: [ip: {127, 0, 0, 1}, port: 4002],
|
||||||
|
secret_key_base: "mf3rysPsftCpSAdQfBIFEKFpjA1e9tGi9+jbxhNTs5qC3pC9LSn6P/kWlZatl9a0",
|
||||||
|
server: false
|
||||||
|
|
||||||
|
config :logger, level: :warning
|
||||||
|
|
||||||
|
config :phoenix, :plug_init_mode, :runtime
|
||||||
|
|
||||||
|
config :phoenix,
|
||||||
|
sort_verified_routes_query_params: true
|
||||||
|
|
||||||
|
config :provider_service, ProviderService.Application,
|
||||||
|
event_store: [
|
||||||
|
adapter: Commanded.EventStore.Adapters.InMemory,
|
||||||
|
serializer: Commanded.Serialization.JsonSerializer
|
||||||
|
]
|
||||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776169885,
|
||||||
|
"narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
55
flake.nix
Normal file
55
flake.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
description = "Provider Service - Phoenix/Commanded CQRS/ES service";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
beamPackages = pkgs.beamPackages;
|
||||||
|
pname = "provider_service";
|
||||||
|
version = "1.0.0";
|
||||||
|
mixFodDeps = beamPackages.fetchMixDeps {
|
||||||
|
inherit pname version;
|
||||||
|
src = pkgs.lib.cleanSource ./.;
|
||||||
|
sha256 = "sha256-t6D0qjPNnAsYtHbwOCbuNBUwcrkvmmGf4/LeOIWgjyw=";
|
||||||
|
};
|
||||||
|
package = beamPackages.mixRelease {
|
||||||
|
inherit pname version mixFodDeps;
|
||||||
|
src = pkgs.lib.cleanSource ./.;
|
||||||
|
meta = {
|
||||||
|
mainProgram = "provider_service";
|
||||||
|
};
|
||||||
|
removeCookie = false;
|
||||||
|
};
|
||||||
|
dockerImage = pkgs.dockerTools.buildLayeredImage {
|
||||||
|
name = "provider_service";
|
||||||
|
contents = [ package pkgs.bashInteractive pkgs.busybox pkgs.shadow ];
|
||||||
|
config = {
|
||||||
|
Cmd = [ "${package}/bin/provider_service" "start" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages.default = package;
|
||||||
|
packages.dockerImage = dockerImage;
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
elixir
|
||||||
|
elixir-ls
|
||||||
|
kubernetes-helm
|
||||||
|
git
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
341
lib/provider_service/aggregates/provider.ex
Normal file
341
lib/provider_service/aggregates/provider.ex
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
defmodule ProviderService.Aggregates.Provider do
|
||||||
|
defstruct [
|
||||||
|
:provider_id,
|
||||||
|
:name,
|
||||||
|
:email,
|
||||||
|
:phone,
|
||||||
|
:contact_name,
|
||||||
|
:ruc,
|
||||||
|
:address,
|
||||||
|
:active,
|
||||||
|
templates: %{},
|
||||||
|
default_templates: %{}
|
||||||
|
]
|
||||||
|
|
||||||
|
alias ProviderService.Commands.{
|
||||||
|
RegisterProvider,
|
||||||
|
UpdateProvider,
|
||||||
|
DeactivateProvider,
|
||||||
|
ReactivateProvider,
|
||||||
|
AddProviderTemplate,
|
||||||
|
ActivateProviderTemplate,
|
||||||
|
DeactivateProviderTemplate,
|
||||||
|
SetDefaultProviderTemplate,
|
||||||
|
RemoveProviderTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
alias ProviderService.Events.{
|
||||||
|
ProviderRegistered,
|
||||||
|
ProviderUpdated,
|
||||||
|
ProviderDeactivated,
|
||||||
|
ProviderReactivated,
|
||||||
|
ProviderTemplateAdded,
|
||||||
|
ProviderTemplateActivated,
|
||||||
|
ProviderTemplateDeactivated,
|
||||||
|
ProviderTemplateDefaultSet,
|
||||||
|
ProviderTemplateRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Execute — Provider
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def execute(%__MODULE__{provider_id: nil}, %RegisterProvider{} = cmd) do
|
||||||
|
%ProviderRegistered{
|
||||||
|
provider_id: cmd.provider_id,
|
||||||
|
name: cmd.name,
|
||||||
|
email: cmd.email,
|
||||||
|
phone: cmd.phone,
|
||||||
|
contact_name: cmd.contact_name,
|
||||||
|
ruc: cmd.ruc,
|
||||||
|
address: cmd.address,
|
||||||
|
registered_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(%__MODULE__{active: false}, %UpdateProvider{}),
|
||||||
|
do: {:error, :provider_inactive}
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %UpdateProvider{} = cmd) do
|
||||||
|
%ProviderUpdated{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
name: cmd.name,
|
||||||
|
email: cmd.email,
|
||||||
|
phone: cmd.phone,
|
||||||
|
contact_name: cmd.contact_name,
|
||||||
|
ruc: cmd.ruc,
|
||||||
|
address: cmd.address,
|
||||||
|
updated_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(%__MODULE__{active: false}, %DeactivateProvider{}),
|
||||||
|
do: {:error, :already_inactive}
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %DeactivateProvider{} = cmd) do
|
||||||
|
%ProviderDeactivated{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
deactivated_by: cmd.deactivated_by,
|
||||||
|
deactivated_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(%__MODULE__{active: true}, %ReactivateProvider{}),
|
||||||
|
do: {:error, :already_active}
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %ReactivateProvider{} = cmd) do
|
||||||
|
%ProviderReactivated{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
reactivated_by: cmd.reactivated_by,
|
||||||
|
reactivated_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Execute — Templates
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def execute(%__MODULE__{active: false}, %AddProviderTemplate{}),
|
||||||
|
do: {:error, :provider_inactive}
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %AddProviderTemplate{} = cmd) do
|
||||||
|
existing = get_in(agg.templates, [cmd.policy_type, cmd.client_type]) || []
|
||||||
|
version = length(existing) + 1
|
||||||
|
|
||||||
|
%ProviderTemplateAdded{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
template_id: cmd.template_id,
|
||||||
|
policy_type: cmd.policy_type,
|
||||||
|
client_type: cmd.client_type,
|
||||||
|
s3_key: cmd.s3_key,
|
||||||
|
fields: cmd.fields,
|
||||||
|
version: version,
|
||||||
|
added_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %ActivateProviderTemplate{} = cmd) do
|
||||||
|
case find_template(agg, cmd.policy_type, cmd.client_type, cmd.template_id) do
|
||||||
|
nil ->
|
||||||
|
{:error, :template_not_found}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
%ProviderTemplateActivated{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
template_id: cmd.template_id,
|
||||||
|
policy_type: cmd.policy_type,
|
||||||
|
client_type: cmd.client_type,
|
||||||
|
activated_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %DeactivateProviderTemplate{} = cmd) do
|
||||||
|
case find_template(agg, cmd.policy_type, cmd.client_type, cmd.template_id) do
|
||||||
|
nil ->
|
||||||
|
{:error, :template_not_found}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
%ProviderTemplateDeactivated{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
template_id: cmd.template_id,
|
||||||
|
policy_type: cmd.policy_type,
|
||||||
|
client_type: cmd.client_type,
|
||||||
|
deactivated_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %SetDefaultProviderTemplate{} = cmd) do
|
||||||
|
case find_template(agg, cmd.policy_type, cmd.client_type, cmd.template_id) do
|
||||||
|
nil ->
|
||||||
|
{:error, :template_not_found}
|
||||||
|
|
||||||
|
%{active: false} ->
|
||||||
|
{:error, :template_not_active}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
%ProviderTemplateDefaultSet{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
template_id: cmd.template_id,
|
||||||
|
policy_type: cmd.policy_type,
|
||||||
|
client_type: cmd.client_type,
|
||||||
|
set_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(%__MODULE__{} = agg, %RemoveProviderTemplate{} = cmd) do
|
||||||
|
case find_template(agg, cmd.policy_type, cmd.client_type, cmd.template_id) do
|
||||||
|
nil ->
|
||||||
|
{:error, :template_not_found}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
%ProviderTemplateRemoved{
|
||||||
|
provider_id: agg.provider_id,
|
||||||
|
template_id: cmd.template_id,
|
||||||
|
policy_type: cmd.policy_type,
|
||||||
|
client_type: cmd.client_type,
|
||||||
|
removed_at: DateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Apply — Provider
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderRegistered{} = e) do
|
||||||
|
%__MODULE__{
|
||||||
|
agg
|
||||||
|
| provider_id: e.provider_id,
|
||||||
|
name: e.name,
|
||||||
|
email: e.email,
|
||||||
|
phone: e.phone,
|
||||||
|
contact_name: e.contact_name,
|
||||||
|
ruc: e.ruc,
|
||||||
|
address: e.address,
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderUpdated{} = e) do
|
||||||
|
%__MODULE__{
|
||||||
|
agg
|
||||||
|
| name: e.name,
|
||||||
|
email: e.email,
|
||||||
|
phone: e.phone,
|
||||||
|
contact_name: e.contact_name,
|
||||||
|
ruc: e.ruc,
|
||||||
|
address: e.address
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderDeactivated{}),
|
||||||
|
do: %__MODULE__{agg | active: false}
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderReactivated{}),
|
||||||
|
do: %__MODULE__{agg | active: true}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Apply — Templates
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderTemplateAdded{} = e) do
|
||||||
|
existing = get_in(agg.templates, [e.policy_type, e.client_type]) || []
|
||||||
|
|
||||||
|
templates =
|
||||||
|
agg.templates
|
||||||
|
|> Map.update(e.policy_type, %{e.client_type => []}, fn inner ->
|
||||||
|
Map.update(inner, e.client_type, [], fn list -> list end)
|
||||||
|
end)
|
||||||
|
|> put_in(
|
||||||
|
[e.policy_type, e.client_type],
|
||||||
|
existing ++
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
template_id: e.template_id,
|
||||||
|
s3_key: e.s3_key,
|
||||||
|
fields: e.fields,
|
||||||
|
version: e.version,
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
%__MODULE__{agg | templates: templates}
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderTemplateActivated{} = e) do
|
||||||
|
templates =
|
||||||
|
update_template(
|
||||||
|
agg.templates,
|
||||||
|
e.policy_type,
|
||||||
|
e.client_type,
|
||||||
|
e.template_id,
|
||||||
|
&Map.put(&1, :active, true)
|
||||||
|
)
|
||||||
|
|
||||||
|
%__MODULE__{agg | templates: templates}
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderTemplateDeactivated{} = e) do
|
||||||
|
templates =
|
||||||
|
update_template(
|
||||||
|
agg.templates,
|
||||||
|
e.policy_type,
|
||||||
|
e.client_type,
|
||||||
|
e.template_id,
|
||||||
|
&Map.put(&1, :active, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
template_id = e.template_id
|
||||||
|
# Clear default if the deactivated template was the default
|
||||||
|
default_templates =
|
||||||
|
case get_in(agg.default_templates, [e.policy_type, e.client_type]) do
|
||||||
|
^template_id ->
|
||||||
|
update_in(agg.default_templates, [e.policy_type], &Map.delete(&1, e.client_type))
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
agg.default_templates
|
||||||
|
end
|
||||||
|
|
||||||
|
%__MODULE__{agg | templates: templates, default_templates: default_templates}
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderTemplateDefaultSet{} = e) do
|
||||||
|
default_templates =
|
||||||
|
agg.default_templates
|
||||||
|
|> Map.update(e.policy_type, %{e.client_type => e.template_id}, fn inner ->
|
||||||
|
Map.put(inner, e.client_type, e.template_id)
|
||||||
|
end)
|
||||||
|
|
||||||
|
%__MODULE__{agg | default_templates: default_templates}
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(%__MODULE__{} = agg, %ProviderTemplateRemoved{} = e) do
|
||||||
|
templates =
|
||||||
|
agg.templates
|
||||||
|
|> update_in([e.policy_type, e.client_type], fn list ->
|
||||||
|
Enum.reject(list || [], &(&1.template_id == e.template_id))
|
||||||
|
end)
|
||||||
|
|
||||||
|
template_id = e.template_id
|
||||||
|
# Clear default if the removed template was the default
|
||||||
|
default_templates =
|
||||||
|
case get_in(agg.default_templates, [e.policy_type, e.client_type]) do
|
||||||
|
^template_id ->
|
||||||
|
update_in(agg.default_templates, [e.policy_type], &Map.delete(&1, e.client_type))
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
agg.default_templates
|
||||||
|
end
|
||||||
|
|
||||||
|
%__MODULE__{agg | templates: templates, default_templates: default_templates}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# templates structure: %{ policy_type => %{ client_type => [%{template_id, ...}] } }
|
||||||
|
# default_templates: %{ policy_type => %{ client_type => template_id } }
|
||||||
|
|
||||||
|
defp find_template(agg, policy_type, client_type, template_id) do
|
||||||
|
agg.templates
|
||||||
|
|> get_in([policy_type, client_type])
|
||||||
|
|> List.wrap()
|
||||||
|
|> Enum.find(&(&1.template_id == template_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_template(templates, policy_type, client_type, template_id, fun) do
|
||||||
|
templates
|
||||||
|
|> Map.update(policy_type, %{}, fn inner ->
|
||||||
|
Map.update(inner, client_type, [], fn list ->
|
||||||
|
Enum.map(list, fn t ->
|
||||||
|
if t.template_id == template_id, do: fun.(t), else: t
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
15
lib/provider_service/application.ex
Normal file
15
lib/provider_service/application.ex
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
defmodule ProviderService.Application do
|
||||||
|
use Application
|
||||||
|
|
||||||
|
def start(_type, _args) do
|
||||||
|
children = [
|
||||||
|
ProviderService.Repo,
|
||||||
|
ProviderService.CommandedApp,
|
||||||
|
ProviderService.Projections.ProviderProjection,
|
||||||
|
ProviderServiceWeb.Endpoint
|
||||||
|
]
|
||||||
|
|
||||||
|
opts = [strategy: :one_for_one, name: ProviderService.Supervisor]
|
||||||
|
Supervisor.start_link(children, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
40
lib/provider_service/commanded_app.ex
Normal file
40
lib/provider_service/commanded_app.ex
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
defmodule ProviderService.Router do
|
||||||
|
use Commanded.Commands.Router
|
||||||
|
|
||||||
|
alias ProviderService.Aggregates.Provider
|
||||||
|
|
||||||
|
alias ProviderService.Commands.{
|
||||||
|
RegisterProvider,
|
||||||
|
UpdateProvider,
|
||||||
|
DeactivateProvider,
|
||||||
|
ReactivateProvider,
|
||||||
|
AddProviderTemplate,
|
||||||
|
ActivateProviderTemplate,
|
||||||
|
DeactivateProviderTemplate,
|
||||||
|
SetDefaultProviderTemplate,
|
||||||
|
RemoveProviderTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
identify(Provider, by: :provider_id)
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
[
|
||||||
|
RegisterProvider,
|
||||||
|
UpdateProvider,
|
||||||
|
DeactivateProvider,
|
||||||
|
ReactivateProvider,
|
||||||
|
AddProviderTemplate,
|
||||||
|
ActivateProviderTemplate,
|
||||||
|
DeactivateProviderTemplate,
|
||||||
|
SetDefaultProviderTemplate,
|
||||||
|
RemoveProviderTemplate
|
||||||
|
],
|
||||||
|
to: Provider
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderService.CommandedApp do
|
||||||
|
use Commanded.Application, otp_app: :provider_service
|
||||||
|
|
||||||
|
router(ProviderService.Router)
|
||||||
|
end
|
||||||
37
lib/provider_service/commands/provider.ex
Normal file
37
lib/provider_service/commands/provider.ex
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
defmodule ProviderService.Commands do
|
||||||
|
defmodule RegisterProvider do
|
||||||
|
defstruct [:provider_id, :name, :email, :phone, :contact_name, :ruc, :address]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule UpdateProvider do
|
||||||
|
defstruct [:provider_id, :name, :email, :phone, :contact_name, :ruc, :address]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule DeactivateProvider do
|
||||||
|
defstruct [:provider_id, :deactivated_by]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ReactivateProvider do
|
||||||
|
defstruct [:provider_id, :reactivated_by]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule AddProviderTemplate do
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :s3_key, :fields, :client_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ActivateProviderTemplate do
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :client_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule DeactivateProviderTemplate do
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :client_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule SetDefaultProviderTemplate do
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :client_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule RemoveProviderTemplate do
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :client_type]
|
||||||
|
end
|
||||||
|
end
|
||||||
3
lib/provider_service/event_store.ex
Normal file
3
lib/provider_service/event_store.ex
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
defmodule ProviderService.EventStore do
|
||||||
|
use EventStore, otp_app: :provider_service
|
||||||
|
end
|
||||||
55
lib/provider_service/events/provider.ex
Normal file
55
lib/provider_service/events/provider.ex
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
defmodule ProviderService.Events do
|
||||||
|
defmodule ProviderRegistered do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :name, :email, :phone, :contact_name, :ruc, :address, :registered_at]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderUpdated do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :name, :email, :phone, :contact_name, :ruc, :address, :updated_at]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderDeactivated do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :deactivated_by, :deactivated_at]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderReactivated do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :reactivated_by, :reactivated_at]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderTemplateAdded do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [
|
||||||
|
:provider_id,
|
||||||
|
:template_id,
|
||||||
|
:policy_type,
|
||||||
|
:s3_key,
|
||||||
|
:fields,
|
||||||
|
:version,
|
||||||
|
:added_at,
|
||||||
|
:client_type
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderTemplateActivated do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :activated_at, :client_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderTemplateDeactivated do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :deactivated_at, :client_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderTemplateDefaultSet do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :set_at, :client_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderTemplateRemoved do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct [:provider_id, :template_id, :policy_type, :removed_at, :client_type]
|
||||||
|
end
|
||||||
|
end
|
||||||
34
lib/provider_service/projections/provider.ex
Normal file
34
lib/provider_service/projections/provider.ex
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
defmodule ProviderService.Projections.Provider do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
@derive {
|
||||||
|
Flop.Schema,
|
||||||
|
filterable: [:active, :search],
|
||||||
|
sortable: [:name, :inserted_at],
|
||||||
|
default_limit: 20,
|
||||||
|
max_limit: 100,
|
||||||
|
custom_fields: [
|
||||||
|
search: [
|
||||||
|
filter: {ProviderService.Projections.ProviderFilters, :search, []},
|
||||||
|
ecto_type: :string,
|
||||||
|
operators: [:==]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@primary_key {:provider_id, :string, autogenerate: false}
|
||||||
|
|
||||||
|
schema "providers" do
|
||||||
|
field(:name, :string)
|
||||||
|
field(:email, :string)
|
||||||
|
field(:phone, :string)
|
||||||
|
field(:contact_name, :string)
|
||||||
|
field(:ruc, :string)
|
||||||
|
field(:address, :string)
|
||||||
|
field(:active, :boolean, default: true)
|
||||||
|
field(:templates, :map, default: %{})
|
||||||
|
field(:default_templates, :map, default: %{})
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime_usec)
|
||||||
|
end
|
||||||
|
end
|
||||||
16
lib/provider_service/projections/provider_filters.ex
Normal file
16
lib/provider_service/projections/provider_filters.ex
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
defmodule ProviderService.Projections.ProviderFilters do
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def search(query, %Flop.Filter{value: value}, _opts) do
|
||||||
|
term = "%#{value}%"
|
||||||
|
|
||||||
|
where(
|
||||||
|
query,
|
||||||
|
[p],
|
||||||
|
ilike(p.name, ^term) or
|
||||||
|
ilike(p.email, ^term) or
|
||||||
|
ilike(p.contact_name, ^term) or
|
||||||
|
ilike(p.ruc, ^term)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
196
lib/provider_service/projections/provider_projection.ex
Normal file
196
lib/provider_service/projections/provider_projection.ex
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
defmodule ProviderService.Projections.ProviderProjection do
|
||||||
|
use Commanded.Projections.Ecto,
|
||||||
|
application: ProviderService.CommandedApp,
|
||||||
|
repo: ProviderService.Repo,
|
||||||
|
name: "ProviderProjection",
|
||||||
|
consistency: :strong
|
||||||
|
|
||||||
|
alias ProviderService.Events.{
|
||||||
|
ProviderRegistered,
|
||||||
|
ProviderUpdated,
|
||||||
|
ProviderDeactivated,
|
||||||
|
ProviderReactivated,
|
||||||
|
ProviderTemplateAdded,
|
||||||
|
ProviderTemplateActivated,
|
||||||
|
ProviderTemplateDeactivated,
|
||||||
|
ProviderTemplateDefaultSet,
|
||||||
|
ProviderTemplateRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
alias ProviderService.Projections.Provider
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
project(%ProviderRegistered{} = e, _meta, fn multi ->
|
||||||
|
Ecto.Multi.insert(multi, :provider, %Provider{
|
||||||
|
provider_id: e.provider_id,
|
||||||
|
name: e.name,
|
||||||
|
email: e.email,
|
||||||
|
phone: e.phone,
|
||||||
|
contact_name: e.contact_name,
|
||||||
|
ruc: e.ruc,
|
||||||
|
address: e.address,
|
||||||
|
active: true,
|
||||||
|
templates: %{},
|
||||||
|
default_templates: %{}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
project(%ProviderUpdated{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
Ecto.Changeset.change(p,
|
||||||
|
name: e.name,
|
||||||
|
email: e.email,
|
||||||
|
phone: e.phone,
|
||||||
|
contact_name: e.contact_name,
|
||||||
|
ruc: e.ruc,
|
||||||
|
address: e.address
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
project(%ProviderDeactivated{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
Ecto.Changeset.change(p, active: false)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
project(%ProviderReactivated{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
Ecto.Changeset.change(p, active: true)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
# templates: %{ policy_type => %{ client_type => [template] } }
|
||||||
|
# default_templates: %{ policy_type => %{ client_type => template_id } }
|
||||||
|
|
||||||
|
project(%ProviderTemplateAdded{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
template = %{
|
||||||
|
"template_id" => e.template_id,
|
||||||
|
"client_type" => e.client_type,
|
||||||
|
"s3_key" => e.s3_key,
|
||||||
|
"fields" => e.fields || [],
|
||||||
|
"version" => e.version,
|
||||||
|
"active" => false
|
||||||
|
}
|
||||||
|
|
||||||
|
updated =
|
||||||
|
p.templates
|
||||||
|
|> Map.update(e.policy_type, %{e.client_type => [template]}, fn inner ->
|
||||||
|
Map.update(inner, e.client_type, [template], fn list -> list ++ [template] end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Ecto.Changeset.change(p, templates: updated)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
project(%ProviderTemplateActivated{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
updated =
|
||||||
|
update_template_field(
|
||||||
|
p.templates,
|
||||||
|
e.policy_type,
|
||||||
|
e.client_type,
|
||||||
|
e.template_id,
|
||||||
|
"active",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
Ecto.Changeset.change(p, templates: updated)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
project(%ProviderTemplateDeactivated{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
updated =
|
||||||
|
update_template_field(
|
||||||
|
p.templates,
|
||||||
|
e.policy_type,
|
||||||
|
e.client_type,
|
||||||
|
e.template_id,
|
||||||
|
"active",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
template_id = e.template_id
|
||||||
|
|
||||||
|
default_templates =
|
||||||
|
case get_in(p.default_templates, [e.policy_type, e.client_type]) do
|
||||||
|
^template_id ->
|
||||||
|
Map.update(p.default_templates, e.policy_type, %{}, &Map.delete(&1, e.client_type))
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
p.default_templates
|
||||||
|
end
|
||||||
|
|
||||||
|
Ecto.Changeset.change(p, templates: updated, default_templates: default_templates)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
project(%ProviderTemplateDefaultSet{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
default_templates =
|
||||||
|
p.default_templates
|
||||||
|
|> Map.update(e.policy_type, %{e.client_type => e.template_id}, fn inner ->
|
||||||
|
Map.put(inner, e.client_type, e.template_id)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Ecto.Changeset.change(p, default_templates: default_templates)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
project(%ProviderTemplateRemoved{} = e, _meta, fn multi ->
|
||||||
|
multi
|
||||||
|
|> Ecto.Multi.run(:fetch, fn repo, _ -> {:ok, repo.get!(Provider, e.provider_id)} end)
|
||||||
|
|> Ecto.Multi.update(:provider, fn %{fetch: p} ->
|
||||||
|
updated =
|
||||||
|
p.templates
|
||||||
|
|> Map.update(e.policy_type, %{}, fn inner ->
|
||||||
|
Map.update(inner, e.client_type, [], fn list ->
|
||||||
|
Enum.reject(list, &(&1["template_id"] == e.template_id))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
template_id = e.template_id
|
||||||
|
|
||||||
|
default_templates =
|
||||||
|
case get_in(p.default_templates, [e.policy_type, e.client_type]) do
|
||||||
|
^template_id ->
|
||||||
|
Map.update(p.default_templates, e.policy_type, %{}, &Map.delete(&1, e.client_type))
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
p.default_templates
|
||||||
|
end
|
||||||
|
|
||||||
|
Ecto.Changeset.change(p, templates: updated, default_templates: default_templates)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
defp update_template_field(templates, policy_type, client_type, template_id, field, value) do
|
||||||
|
Map.update(templates, policy_type, %{}, fn inner ->
|
||||||
|
Map.update(inner, client_type, [], fn list ->
|
||||||
|
Enum.map(list, fn t ->
|
||||||
|
if t["template_id"] == template_id, do: Map.put(t, field, value), else: t
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
18
lib/provider_service/provider_service.ex
Normal file
18
lib/provider_service/provider_service.ex
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
defmodule ProviderService do
|
||||||
|
@moduledoc """
|
||||||
|
Documentation for `ProviderService`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Hello world.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> ProviderService.hello()
|
||||||
|
:world
|
||||||
|
|
||||||
|
"""
|
||||||
|
def hello do
|
||||||
|
:world
|
||||||
|
end
|
||||||
|
end
|
||||||
34
lib/provider_service/queries/provider_queries.ex
Normal file
34
lib/provider_service/queries/provider_queries.ex
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
defmodule ProviderService.Queries.ProviderQueries do
|
||||||
|
alias ProviderService.Projections.Provider
|
||||||
|
alias ProviderService.Repo
|
||||||
|
|
||||||
|
def list_providers(params \\ %{}) do
|
||||||
|
Flop.validate_and_run(Provider, params, for: Provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_provider(provider_id) do
|
||||||
|
case Repo.get(Provider, provider_id) do
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
provider -> {:ok, provider}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_active_template(provider_id, policy_type) do
|
||||||
|
with {:ok, provider} <- get_provider(provider_id) do
|
||||||
|
default_id = Map.get(provider.default_templates, policy_type)
|
||||||
|
templates = Map.get(provider.templates, policy_type, [])
|
||||||
|
|
||||||
|
result =
|
||||||
|
if default_id do
|
||||||
|
Enum.find(templates, &(&1["template_id"] == default_id))
|
||||||
|
else
|
||||||
|
Enum.find(templates, &(&1["active"] == true))
|
||||||
|
end
|
||||||
|
|
||||||
|
case result do
|
||||||
|
nil -> {:error, :no_active_template}
|
||||||
|
template -> {:ok, template}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
5
lib/provider_service/repo.ex
Normal file
5
lib/provider_service/repo.ex
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
defmodule ProviderService.Repo do
|
||||||
|
use Ecto.Repo,
|
||||||
|
otp_app: :provider_service,
|
||||||
|
adapter: Ecto.Adapters.Postgres
|
||||||
|
end
|
||||||
38
lib/provider_service/s3.ex
Normal file
38
lib/provider_service/s3.ex
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
defmodule ProviderService.S3 do
|
||||||
|
@bucket Application.compile_env(:provider_service, :s3_bucket, "policy-bucket")
|
||||||
|
|
||||||
|
def presigned_upload_url(s3_key) do
|
||||||
|
{:ok, url} =
|
||||||
|
ExAws.Config.new(:s3)
|
||||||
|
|> ExAws.S3.presigned_url(:put, @bucket, s3_key,
|
||||||
|
expires_in: 900,
|
||||||
|
query_params: [{"Content-Type", "application/pdf"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
|
def presigned_download_url(s3_key) do
|
||||||
|
{:ok, url} =
|
||||||
|
ExAws.Config.new(:s3)
|
||||||
|
|> ExAws.S3.presigned_url(:get, @bucket, s3_key, expires_in: 3600)
|
||||||
|
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(s3_key) do
|
||||||
|
ExAws.S3.delete_object(@bucket, s3_key)
|
||||||
|
|> ExAws.request()
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload(local_path, s3_key) do
|
||||||
|
local_path
|
||||||
|
|> File.read!()
|
||||||
|
|> then(&ExAws.S3.put_object(@bucket, s3_key, &1, content_type: "application/pdf"))
|
||||||
|
|> ExAws.request()
|
||||||
|
|> case do
|
||||||
|
{:ok, _} -> :ok
|
||||||
|
{:error, e} -> {:error, inspect(e)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
25
lib/provider_service_web/api_spec.ex
Normal file
25
lib/provider_service_web/api_spec.ex
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
defmodule ProviderServiceWeb.ApiSpec do
|
||||||
|
alias OpenApiSpex.{OpenApi, Info, Server}
|
||||||
|
alias OpenApiSpex.{Info, OpenApi, Paths, Server}
|
||||||
|
alias ProviderServiceWeb.{Endpoint, Router}
|
||||||
|
|
||||||
|
@behaviour OpenApiSpex.OpenApi
|
||||||
|
|
||||||
|
@impl OpenApi
|
||||||
|
def spec do
|
||||||
|
%OpenApi{
|
||||||
|
servers: [
|
||||||
|
# Populate the Server info from a phoenix endpoint
|
||||||
|
Server.from_endpoint(Endpoint)
|
||||||
|
],
|
||||||
|
info: %Info{
|
||||||
|
title: "Provider Service",
|
||||||
|
version: "1.0"
|
||||||
|
},
|
||||||
|
# Populate the paths from a phoenix router
|
||||||
|
paths: Paths.from_router(Router)
|
||||||
|
}
|
||||||
|
# Discover request/response schemas from path specs
|
||||||
|
|> OpenApiSpex.resolve_schema_modules()
|
||||||
|
end
|
||||||
|
end
|
||||||
207
lib/provider_service_web/controllers/provider_controller.ex
Normal file
207
lib/provider_service_web/controllers/provider_controller.ex
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
defmodule ProviderServiceWeb.ProviderController do
|
||||||
|
use ProviderServiceWeb, :controller
|
||||||
|
use OpenApiSpex.ControllerSpecs
|
||||||
|
|
||||||
|
alias ProviderService.CommandedApp
|
||||||
|
alias ProviderService.Queries.ProviderQueries
|
||||||
|
|
||||||
|
alias ProviderService.Commands.{
|
||||||
|
RegisterProvider,
|
||||||
|
UpdateProvider,
|
||||||
|
DeactivateProvider,
|
||||||
|
ReactivateProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
alias ProviderServiceWeb.Schemas.Provider, as: PS
|
||||||
|
|
||||||
|
operation(:index,
|
||||||
|
summary: "List providers",
|
||||||
|
parameters: [
|
||||||
|
"page[number]": [in: :query, type: :integer, required: false],
|
||||||
|
"page[size]": [in: :query, type: :integer, required: false],
|
||||||
|
"filters[0][field]": [in: :query, type: :string, required: false],
|
||||||
|
"filters[0][op]": [in: :query, type: :string, required: false],
|
||||||
|
"filters[0][value]": [in: :query, type: :string, required: false]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Provider list", "application/json", PS.ProviderListResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def index(conn, params) do
|
||||||
|
case ProviderQueries.list_providers(params) do
|
||||||
|
{:ok, {providers, meta}} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:ok)
|
||||||
|
|> json(%{
|
||||||
|
data: Enum.map(providers, &provider_json/1),
|
||||||
|
meta: meta_json(meta)
|
||||||
|
})
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
conn |> put_status(:bad_request) |> json(%{error: "invalid parameters"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:show,
|
||||||
|
summary: "Get provider",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Provider", "application/json", PS.ProviderResponse},
|
||||||
|
not_found: {"Not found", "application/json", %OpenApiSpex.Schema{type: :object}}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def show(conn, %{"provider_id" => provider_id}) do
|
||||||
|
case ProviderQueries.get_provider(provider_id) do
|
||||||
|
{:ok, provider} ->
|
||||||
|
conn |> put_status(:ok) |> json(%{data: provider_json(provider)})
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
conn |> put_status(:not_found) |> json(%{error: "not found"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:create,
|
||||||
|
summary: "Register provider",
|
||||||
|
request_body: {"Provider data", "application/json", PS.RegisterProvider, required: true},
|
||||||
|
responses: [
|
||||||
|
created: {"Provider registered", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def create(conn, params) do
|
||||||
|
provider_id = params["provider_id"]
|
||||||
|
|
||||||
|
command = %RegisterProvider{
|
||||||
|
provider_id: provider_id,
|
||||||
|
name: params["name"],
|
||||||
|
email: params["email"],
|
||||||
|
phone: params["phone"],
|
||||||
|
contact_name: params["contact_name"],
|
||||||
|
ruc: params["ruc"],
|
||||||
|
address: params["address"]
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandedApp.dispatch(command, consistency: :strong) do
|
||||||
|
:ok ->
|
||||||
|
{:ok, provider} = ProviderQueries.get_provider(provider_id)
|
||||||
|
conn |> put_status(:created) |> json(%{data: provider_json(provider)})
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:update,
|
||||||
|
summary: "Update provider",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
request_body: {"Provider data", "application/json", PS.UpdateProvider, required: true},
|
||||||
|
responses: [
|
||||||
|
ok: {"Provider updated", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(conn, %{"provider_id" => provider_id} = params) do
|
||||||
|
command = %UpdateProvider{
|
||||||
|
provider_id: provider_id,
|
||||||
|
name: params["name"],
|
||||||
|
email: params["email"],
|
||||||
|
phone: params["phone"],
|
||||||
|
contact_name: params["contact_name"],
|
||||||
|
ruc: params["ruc"],
|
||||||
|
address: params["address"]
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandedApp.dispatch(command, consistency: :strong) do
|
||||||
|
:ok ->
|
||||||
|
{:ok, provider} = ProviderQueries.get_provider(provider_id)
|
||||||
|
conn |> put_status(:ok) |> json(%{data: provider_json(provider)})
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:deactivate,
|
||||||
|
summary: "Deactivate provider",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Provider deactivated", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def deactivate(conn, %{"provider_id" => provider_id}) do
|
||||||
|
command = %DeactivateProvider{
|
||||||
|
provider_id: provider_id,
|
||||||
|
deactivated_by: "system"
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandedApp.dispatch(command, consistency: :strong) do
|
||||||
|
:ok ->
|
||||||
|
{:ok, provider} = ProviderQueries.get_provider(provider_id)
|
||||||
|
conn |> put_status(:ok) |> json(%{data: provider_json(provider)})
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:reactivate,
|
||||||
|
summary: "Reactivate provider",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Provider reactivated", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def reactivate(conn, %{"provider_id" => provider_id}) do
|
||||||
|
command = %ReactivateProvider{
|
||||||
|
provider_id: provider_id,
|
||||||
|
reactivated_by: "system"
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandedApp.dispatch(command, consistency: :strong) do
|
||||||
|
:ok ->
|
||||||
|
{:ok, provider} = ProviderQueries.get_provider(provider_id)
|
||||||
|
conn |> put_status(:ok) |> json(%{data: provider_json(provider)})
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp provider_json(p) do
|
||||||
|
%{
|
||||||
|
provider_id: p.provider_id,
|
||||||
|
name: p.name,
|
||||||
|
email: p.email,
|
||||||
|
phone: p.phone,
|
||||||
|
contact_name: p.contact_name,
|
||||||
|
ruc: p.ruc,
|
||||||
|
address: p.address,
|
||||||
|
active: p.active,
|
||||||
|
templates: p.templates,
|
||||||
|
default_templates: p.default_templates
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp meta_json(meta) do
|
||||||
|
%{
|
||||||
|
total_count: meta.total_count,
|
||||||
|
total_pages: meta.total_pages,
|
||||||
|
current_page: meta.current_page,
|
||||||
|
page_size: meta.page_size,
|
||||||
|
has_next: meta.has_next_page?,
|
||||||
|
has_prev: meta.has_previous_page?
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
215
lib/provider_service_web/controllers/template_controller.ex
Normal file
215
lib/provider_service_web/controllers/template_controller.ex
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
defmodule ProviderServiceWeb.TemplateController do
|
||||||
|
use ProviderServiceWeb, :controller
|
||||||
|
use OpenApiSpex.ControllerSpecs
|
||||||
|
|
||||||
|
alias ProviderService.CommandedApp
|
||||||
|
alias ProviderService.Queries.ProviderQueries
|
||||||
|
|
||||||
|
alias ProviderService.Commands.{
|
||||||
|
AddProviderTemplate,
|
||||||
|
ActivateProviderTemplate,
|
||||||
|
DeactivateProviderTemplate,
|
||||||
|
SetDefaultProviderTemplate,
|
||||||
|
RemoveProviderTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
alias ProviderService.S3
|
||||||
|
alias ProviderServiceWeb.Schemas.Provider, as: PS
|
||||||
|
|
||||||
|
operation(:index,
|
||||||
|
summary: "List templates for provider",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Templates", "application/json", %OpenApiSpex.Schema{type: :object}}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def index(conn, %{"provider_id" => provider_id}) do
|
||||||
|
case ProviderQueries.get_provider(provider_id) do
|
||||||
|
{:ok, provider} ->
|
||||||
|
conn |> put_status(:ok) |> json(%{data: provider.templates})
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
conn |> put_status(:not_found) |> json(%{error: "provider not found"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:upload_template,
|
||||||
|
summary: "Upload solicitation template",
|
||||||
|
description: "Upload a fillable PDF. Fields are auto-discovered via solicitation_service.",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
request_body:
|
||||||
|
{"Multipart PDF upload", "multipart/form-data", PS.UploadTemplateRequest, required: true},
|
||||||
|
responses: [
|
||||||
|
created: {"Template registered", "application/json", PS.UploadTemplateResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def upload_template(conn, %{"provider_id" => provider_id} = params) do
|
||||||
|
# %Plug.Upload{}
|
||||||
|
upload = params["file"]
|
||||||
|
policy_type = params["policy_type"]
|
||||||
|
client_type = params["client_type"]
|
||||||
|
|
||||||
|
template_id = Ecto.UUID.generate()
|
||||||
|
s3_key = "templates/#{provider_id}/#{policy_type}/#{client_type}/#{template_id}.pdf"
|
||||||
|
|
||||||
|
# Upload to S3/MinIO
|
||||||
|
case S3.upload(upload.path, s3_key) do
|
||||||
|
:ok ->
|
||||||
|
# Discover AcroForm fields via solicitation_service
|
||||||
|
fields = discover_fields(s3_key)
|
||||||
|
|
||||||
|
cmd = %AddProviderTemplate{
|
||||||
|
provider_id: provider_id,
|
||||||
|
template_id: template_id,
|
||||||
|
client_type: client_type,
|
||||||
|
policy_type: policy_type,
|
||||||
|
s3_key: s3_key,
|
||||||
|
fields: fields
|
||||||
|
}
|
||||||
|
|
||||||
|
case ProviderService.CommandedApp.dispatch(cmd) do
|
||||||
|
:ok ->
|
||||||
|
conn
|
||||||
|
|> put_status(:created)
|
||||||
|
|> json(%{template_id: template_id, s3_key: s3_key, fields: fields})
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
conn |> put_status(:unprocessable_entity) |> json(%{error: reason})
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:internal_server_error)
|
||||||
|
|> json(%{error: "S3 upload failed: #{reason}"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp discover_fields(s3_key) do
|
||||||
|
url = Application.get_env(:provider_service, :solicitation_service_url)
|
||||||
|
|
||||||
|
case Req.get("#{url}/api/solicitations/templates/fields", params: [s3_key: s3_key]) do
|
||||||
|
{:ok, %{status: 200, body: %{"fields" => fields}}} -> fields
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:activate,
|
||||||
|
summary: "Activate a template",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true],
|
||||||
|
template_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Template activated", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def activate(conn, %{"provider_id" => provider_id, "template_id" => template_id} = params) do
|
||||||
|
command = %ActivateProviderTemplate{
|
||||||
|
provider_id: provider_id,
|
||||||
|
template_id: template_id,
|
||||||
|
policy_type: params["policy_type"],
|
||||||
|
client_type: params["client_type"]
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_and_respond(conn, command, provider_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:deactivate,
|
||||||
|
summary: "Deactivate a template",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true],
|
||||||
|
template_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Template deactivated", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def deactivate(conn, %{"provider_id" => provider_id, "template_id" => template_id} = params) do
|
||||||
|
command = %DeactivateProviderTemplate{
|
||||||
|
provider_id: provider_id,
|
||||||
|
template_id: template_id,
|
||||||
|
policy_type: params["policy_type"],
|
||||||
|
client_type: params["client_type"]
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_and_respond(conn, command, provider_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:set_default,
|
||||||
|
summary: "Set default template for a policy type",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true],
|
||||||
|
template_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Default template set", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_default(conn, %{"provider_id" => provider_id, "template_id" => template_id} = params) do
|
||||||
|
command = %SetDefaultProviderTemplate{
|
||||||
|
provider_id: provider_id,
|
||||||
|
template_id: template_id,
|
||||||
|
policy_type: params["policy_type"],
|
||||||
|
client_type: params["client_type"]
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_and_respond(conn, command, provider_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
operation(:remove,
|
||||||
|
summary: "Remove a template",
|
||||||
|
parameters: [
|
||||||
|
provider_id: [in: :path, type: :string, required: true],
|
||||||
|
template_id: [in: :path, type: :string, required: true]
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
ok: {"Template removed", "application/json", PS.ProviderResponse}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def remove(conn, %{"provider_id" => provider_id, "template_id" => template_id} = params) do
|
||||||
|
command = %RemoveProviderTemplate{
|
||||||
|
provider_id: provider_id,
|
||||||
|
template_id: template_id,
|
||||||
|
policy_type: params["policy_type"],
|
||||||
|
client_type: params["client_type"]
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_and_respond(conn, command, provider_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp dispatch_and_respond(conn, command, provider_id) do
|
||||||
|
case CommandedApp.dispatch(command, consistency: :strong) do
|
||||||
|
:ok ->
|
||||||
|
{:ok, provider} = ProviderQueries.get_provider(provider_id)
|
||||||
|
conn |> put_status(:ok) |> json(%{data: provider_json(provider)})
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp provider_json(p) do
|
||||||
|
%{
|
||||||
|
provider_id: p.provider_id,
|
||||||
|
name: p.name,
|
||||||
|
email: p.email,
|
||||||
|
phone: p.phone,
|
||||||
|
contact_name: p.contact_name,
|
||||||
|
ruc: p.ruc,
|
||||||
|
address: p.address,
|
||||||
|
active: p.active,
|
||||||
|
templates: p.templates,
|
||||||
|
default_templates: p.default_templates
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
25
lib/provider_service_web/endpoint.ex
Normal file
25
lib/provider_service_web/endpoint.ex
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
defmodule ProviderServiceWeb.Endpoint do
|
||||||
|
use Phoenix.Endpoint, otp_app: :provider_service
|
||||||
|
|
||||||
|
@session_options [
|
||||||
|
store: :cookie,
|
||||||
|
key: "_provider_service_key",
|
||||||
|
signing_salt: "somesalt",
|
||||||
|
same_site: "Lax"
|
||||||
|
]
|
||||||
|
|
||||||
|
plug(Plug.RequestId)
|
||||||
|
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
|
||||||
|
|
||||||
|
plug(Plug.Parsers,
|
||||||
|
parsers: [:urlencoded, :multipart, :json],
|
||||||
|
pass: ["*/*"],
|
||||||
|
json_decoder: Phoenix.json_library()
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(Plug.MethodOverride)
|
||||||
|
plug(Plug.Head)
|
||||||
|
plug(Plug.Session, @session_options)
|
||||||
|
plug(CORSPlug, origin: ["http://localhost:3000"])
|
||||||
|
plug(ProviderServiceWeb.Router)
|
||||||
|
end
|
||||||
20
lib/provider_service_web/provider_service_web.ex
Normal file
20
lib/provider_service_web/provider_service_web.ex
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
defmodule ProviderServiceWeb do
|
||||||
|
def controller do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Controller, formats: [:json]
|
||||||
|
import Plug.Conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def router do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Router, helpers: false
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.Controller
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmacro __using__(which) when is_atom(which) do
|
||||||
|
apply(__MODULE__, which, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
73
lib/provider_service_web/router.ex
Normal file
73
lib/provider_service_web/router.ex
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
defmodule ProviderServiceWeb.Router do
|
||||||
|
use Phoenix.Router
|
||||||
|
|
||||||
|
pipeline :api do
|
||||||
|
plug(:accepts, ["json"])
|
||||||
|
plug(OpenApiSpex.Plug.PutApiSpec, module: ProviderServiceWeb.ApiSpec)
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/api" do
|
||||||
|
pipe_through(:api)
|
||||||
|
|
||||||
|
get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
|
||||||
|
|
||||||
|
scope "/v1" do
|
||||||
|
# Providers
|
||||||
|
get("/providers", ProviderServiceWeb.ProviderController, :index)
|
||||||
|
post("/providers", ProviderServiceWeb.ProviderController, :create)
|
||||||
|
get("/providers/:provider_id", ProviderServiceWeb.ProviderController, :show)
|
||||||
|
put("/providers/:provider_id", ProviderServiceWeb.ProviderController, :update)
|
||||||
|
|
||||||
|
post(
|
||||||
|
"/providers/:provider_id/deactivate",
|
||||||
|
ProviderServiceWeb.ProviderController,
|
||||||
|
:deactivate
|
||||||
|
)
|
||||||
|
|
||||||
|
post(
|
||||||
|
"/providers/:provider_id/reactivate",
|
||||||
|
ProviderServiceWeb.ProviderController,
|
||||||
|
:reactivate
|
||||||
|
)
|
||||||
|
|
||||||
|
# Templates
|
||||||
|
get("/providers/:provider_id/templates", ProviderServiceWeb.TemplateController, :index)
|
||||||
|
|
||||||
|
post(
|
||||||
|
"/providers/:provider_id/templates",
|
||||||
|
ProviderServiceWeb.TemplateController,
|
||||||
|
:upload_template
|
||||||
|
)
|
||||||
|
|
||||||
|
post(
|
||||||
|
"/providers/:provider_id/templates/:template_id/activate",
|
||||||
|
ProviderServiceWeb.TemplateController,
|
||||||
|
:activate
|
||||||
|
)
|
||||||
|
|
||||||
|
post(
|
||||||
|
"/providers/:provider_id/templates/:template_id/deactivate",
|
||||||
|
ProviderServiceWeb.TemplateController,
|
||||||
|
:deactivate
|
||||||
|
)
|
||||||
|
|
||||||
|
post(
|
||||||
|
"/providers/:provider_id/templates/:template_id/set-default",
|
||||||
|
ProviderServiceWeb.TemplateController,
|
||||||
|
:set_default
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(
|
||||||
|
"/providers/:provider_id/templates/:template_id",
|
||||||
|
ProviderServiceWeb.TemplateController,
|
||||||
|
:remove
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Mix.env() == :dev do
|
||||||
|
scope "/swaggerui" do
|
||||||
|
get("/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
228
lib/provider_service_web/schemas/provider.ex
Normal file
228
lib/provider_service_web/schemas/provider.ex
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
defmodule ProviderServiceWeb.Schemas.Provider do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
defmodule PaginationMeta do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "PaginationMeta",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
total_count: %Schema{type: :integer},
|
||||||
|
total_pages: %Schema{type: :integer},
|
||||||
|
current_page: %Schema{type: :integer},
|
||||||
|
page_size: %Schema{type: :integer},
|
||||||
|
has_next: %Schema{type: :boolean},
|
||||||
|
has_prev: %Schema{type: :boolean}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule TemplateField do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "TemplateField",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
field: %Schema{type: :string, example: "beneficiary_name"},
|
||||||
|
label: %Schema{type: :string, example: "Beneficiary Name"},
|
||||||
|
type: %Schema{type: :string, enum: ["string", "date", "number", "select", "boolean"]},
|
||||||
|
required: %Schema{type: :boolean},
|
||||||
|
options: %Schema{type: :array, items: %Schema{type: :string}, nullable: true}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Template do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "Template",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
template_id: %Schema{type: :string, format: :uuid},
|
||||||
|
policy_type: %Schema{type: :string, enum: ["car", "life", "fire"]},
|
||||||
|
client_type: %Schema{type: :string, enum: ["natural", "juridico"]},
|
||||||
|
s3_key: %Schema{type: :string},
|
||||||
|
version: %Schema{type: :integer},
|
||||||
|
fields: %Schema{type: :array, items: TemplateField},
|
||||||
|
active: %Schema{type: :boolean}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule RegisterProvider do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "RegisterProvider",
|
||||||
|
type: :object,
|
||||||
|
required: [:provider_id, :name],
|
||||||
|
properties: %{
|
||||||
|
provider_id: %Schema{
|
||||||
|
type: :string,
|
||||||
|
pattern: "^[a-zA-Z0-9]+$",
|
||||||
|
description: "Alphanumeric ID for the provider"
|
||||||
|
},
|
||||||
|
name: %Schema{type: :string, example: "Seguros ABC"},
|
||||||
|
email: %Schema{type: :string, format: :email},
|
||||||
|
phone: %Schema{type: :string},
|
||||||
|
contact_name: %Schema{type: :string},
|
||||||
|
ruc: %Schema{type: :string},
|
||||||
|
address: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule UpdateProvider do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "UpdateProvider",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
name: %Schema{type: :string},
|
||||||
|
email: %Schema{type: :string, format: :email},
|
||||||
|
phone: %Schema{type: :string},
|
||||||
|
contact_name: %Schema{type: :string},
|
||||||
|
ruc: %Schema{type: :string},
|
||||||
|
address: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule UploadTemplateRequest do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "UploadTemplateRequest",
|
||||||
|
type: :object,
|
||||||
|
required: [:file, :policy_type, :client_type],
|
||||||
|
properties: %{
|
||||||
|
file: %Schema{
|
||||||
|
type: :string,
|
||||||
|
format: :binary,
|
||||||
|
description: "Fillable PDF (AcroForm)"
|
||||||
|
},
|
||||||
|
policy_type: %Schema{
|
||||||
|
type: :string,
|
||||||
|
enum: ["car", "life", "fire"],
|
||||||
|
description: "Policy type this template applies to"
|
||||||
|
},
|
||||||
|
client_type: %Schema{
|
||||||
|
type: :string,
|
||||||
|
enum: ["natural", "juridico"],
|
||||||
|
description: "Client type this template applies to"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule UploadTemplateResponse do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "UploadTemplateResponse",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
template_id: %Schema{type: :string, format: :uuid},
|
||||||
|
s3_key: %Schema{type: :string},
|
||||||
|
fields: %Schema{type: :array, items: TemplateField}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
# templates: %{ policy_type => %{ client_type => [Template] } }
|
||||||
|
# default_templates: %{ policy_type => %{ client_type => template_id } }
|
||||||
|
|
||||||
|
defmodule ClientTypeTemplates do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "ClientTypeTemplates",
|
||||||
|
type: :object,
|
||||||
|
description: "Map of client_type (natural | juridico) to list of templates",
|
||||||
|
properties: %{
|
||||||
|
natural: %Schema{type: :array, items: Template, nullable: true},
|
||||||
|
juridico: %Schema{type: :array, items: Template, nullable: true}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ClientTypeDefaults do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "ClientTypeDefaults",
|
||||||
|
type: :object,
|
||||||
|
description: "Map of client_type (natural | juridico) to default template_id",
|
||||||
|
properties: %{
|
||||||
|
natural: %Schema{type: :string, format: :uuid, nullable: true},
|
||||||
|
juridico: %Schema{type: :string, format: :uuid, nullable: true}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderData do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "ProviderData",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
provider_id: %Schema{type: :string, format: :uuid},
|
||||||
|
name: %Schema{type: :string},
|
||||||
|
email: %Schema{type: :string},
|
||||||
|
phone: %Schema{type: :string},
|
||||||
|
contact_name: %Schema{type: :string},
|
||||||
|
ruc: %Schema{type: :string},
|
||||||
|
address: %Schema{type: :string},
|
||||||
|
active: %Schema{type: :boolean},
|
||||||
|
templates: %Schema{
|
||||||
|
type: :object,
|
||||||
|
description: "Map of policy_type (car | life | fire) to client_type map of templates",
|
||||||
|
properties: %{
|
||||||
|
car: ClientTypeTemplates,
|
||||||
|
life: ClientTypeTemplates,
|
||||||
|
fire: ClientTypeTemplates
|
||||||
|
}
|
||||||
|
},
|
||||||
|
default_templates: %Schema{
|
||||||
|
type: :object,
|
||||||
|
description: "Map of policy_type to client_type to default template_id",
|
||||||
|
properties: %{
|
||||||
|
car: ClientTypeDefaults,
|
||||||
|
life: ClientTypeDefaults,
|
||||||
|
fire: ClientTypeDefaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderResponse do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "ProviderResponse",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
data: ProviderData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ProviderListResponse do
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "ProviderListResponse",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
data: %Schema{type: :array, items: ProviderData},
|
||||||
|
meta: PaginationMeta
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
69
mix.exs
Normal file
69
mix.exs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
defmodule ProviderService.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :provider_service,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.18",
|
||||||
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
aliases: aliases(),
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
mod: {ProviderService.Application, []},
|
||||||
|
extra_applications: [:logger, :runtime_tools]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||||
|
defp elixirc_paths(_), do: ["lib"]
|
||||||
|
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
# Phoenix
|
||||||
|
{:phoenix, "~> 1.7"},
|
||||||
|
{:phoenix_ecto, "~> 4.6"},
|
||||||
|
{:ecto_sql, "~> 3.12"},
|
||||||
|
{:postgrex, ">= 0.0.0"},
|
||||||
|
{:bandit, "~> 1.5"},
|
||||||
|
{:cors_plug, "~> 3.0"},
|
||||||
|
|
||||||
|
# Commanded / CQRS
|
||||||
|
{:commanded, "~> 1.4"},
|
||||||
|
{:commanded_eventstore_adapter, "~> 1.4"},
|
||||||
|
{:eventstore, "~> 1.4"},
|
||||||
|
{:commanded_ecto_projections, "~> 1.4"},
|
||||||
|
|
||||||
|
# Serialization
|
||||||
|
{:jason, "~> 1.4"},
|
||||||
|
|
||||||
|
# OpenAPI
|
||||||
|
{:open_api_spex, "~> 3.21"},
|
||||||
|
|
||||||
|
# Pagination
|
||||||
|
{:flop, "~> 0.26"},
|
||||||
|
|
||||||
|
# AWS S3
|
||||||
|
{:ex_aws, "~> 2.5"},
|
||||||
|
{:ex_aws_s3, "~> 2.5"},
|
||||||
|
{:hackney, "~> 1.20"},
|
||||||
|
{:req, "~> 0.5"},
|
||||||
|
{:sweet_xml, "~> 0.7"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp aliases do
|
||||||
|
[
|
||||||
|
setup: ["deps.get", "ecto.setup", "event_store.setup"],
|
||||||
|
"ecto.setup": ["ecto.create", "ecto.migrate"],
|
||||||
|
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||||
|
"event_store.setup": ["event_store.create", "event_store.init"],
|
||||||
|
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
48
mix.lock
Normal file
48
mix.lock
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
%{
|
||||||
|
"backoff": {:hex, :backoff, "1.1.6", "83b72ed2108ba1ee8f7d1c22e0b4a00cfe3593a67dbc792799e8cce9f42f796b", [:rebar3], [], "hexpm", "cf0cfff8995fb20562f822e5cc47d8ccf664c5ecdc26a684cbe85c225f9d7c39"},
|
||||||
|
"bandit": {:hex, :bandit, "1.10.3", "1e5d168fa79ec8de2860d1b4d878d97d4fbbe2fdbe7b0a7d9315a4359d1d4bb9", [: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", "99a52d909c48db65ca598e1962797659e3c0f1d06e825a50c3d75b74a5e2db18"},
|
||||||
|
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
|
||||||
|
"commanded": {:hex, :commanded, "1.4.9", "289bc371943cf082f1161b1560563f5451ca176c967670cccd63fc3988fcd225", [:mix], [{:backoff, "~> 1.1", [hex: :backoff, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.2", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "a4f49c23041a23687aa10e99f3db7ee3b8ae470bb615b73b9f887b86437263e7"},
|
||||||
|
"commanded_ecto_projections": {:hex, :commanded_ecto_projections, "1.4.0", "a1b220577577d5e0aee4c92b2d9bc6de221f9c1ac2ab36932cba15881761332f", [:mix], [{:commanded, "~> 1.4", [hex: :commanded, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "8919a6173cd8f30fe2f948c2967f9289c7f5fe4eeca7abc67966bfca31f4aa9f"},
|
||||||
|
"commanded_eventstore_adapter": {:hex, :commanded_eventstore_adapter, "1.4.2", "4f2d9d9bd8ef7807a5a4c278b4344adddbbbb4d9c86c693872bc85b944be1fe8", [:mix], [{:commanded, "~> 1.4", [hex: :commanded, repo: "hexpm", optional: false]}, {:eventstore, "~> 1.4", [hex: :eventstore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "26eaa68515e3e73834d769b73bddfea76c3fdcaff085d735c22b82a66ba19b10"},
|
||||||
|
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
|
||||||
|
"db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"},
|
||||||
|
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
||||||
|
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [: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", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
|
||||||
|
"ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
|
||||||
|
"eventstore": {:hex, :eventstore, "1.4.8", "26778c991cfb078f3906a4267060efc7bb5e5943f69ddb8ae6fb60f07042a66e", [:mix], [{:fsm, "~> 0.3", [hex: :fsm, repo: "hexpm", optional: false]}, {:gen_stage, "~> 1.2", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "30c914602fdea8db5992a90ecb1f84068531e764cf0c066be71ff0eec4e3bcb9"},
|
||||||
|
"ex_aws": {:hex, :ex_aws, "2.6.1", "194582c7b09455de8a5ab18a0182e6dd937d53df82be2e63c619d01bddaccdfa", [:mix], [{:configparser_ex, "~> 5.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67842a08c90a1d9a09dbe4ac05754175c7ca253abe4912987c759395d4bd9d26"},
|
||||||
|
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.9", "862b7792f2e60d7010e2920d79964e3fab289bc0fd951b0ba8457a3f7f9d1199", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "a480d2bb2da64610014021629800e1e9457ca5e4a62f6775bffd963360c2bf90"},
|
||||||
|
"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"},
|
||||||
|
"flop": {:hex, :flop, "0.26.3", "9bc700b34f96a57e56aaa89b850926356311372556eacd5a1abe0fdd0ea40bf2", [:mix], [{:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "cd77588229778ac55560c90dfbe15ab6486773f067d6e52db9fa703b8c9a9d2d"},
|
||||||
|
"fsm": {:hex, :fsm, "0.3.1", "087aa9b02779a84320dc7a2d8464452b5308e29877921b2bde81cdba32a12390", [:mix], [], "hexpm", "fbf0d53f89e9082b326b0b5828b94b4c549ff9d1452bbfd00b4d1ac082208e96"},
|
||||||
|
"gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"},
|
||||||
|
"hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"},
|
||||||
|
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||||
|
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||||
|
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||||
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||||
|
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
|
||||||
|
"mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"},
|
||||||
|
"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"},
|
||||||
|
"open_api_spex": {:hex, :open_api_spex, "3.22.2", "0b3c4f572ee69cb6c936abf426b9d84d8eebd34960871fd77aead746f0d69cb0", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "0a4fc08472d75e9cfe96e0748c6b1565b3b4398f97bf43fcce41b41b6fd3fb33"},
|
||||||
|
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||||
|
"phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [: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", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"},
|
||||||
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [: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]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"},
|
||||||
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
|
||||||
|
"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"},
|
||||||
|
"postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [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", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"},
|
||||||
|
"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"},
|
||||||
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||||
|
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
|
||||||
|
"telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"},
|
||||||
|
"telemetry_registry": {:hex, :telemetry_registry, "0.3.2", "701576890320be6428189bff963e865e8f23e0ff3615eade8f78662be0fc003c", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7ed191eb1d115a3034af8e1e35e4e63d5348851d556646d46ca3d1b4e16bab9"},
|
||||||
|
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
|
||||||
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
|
||||||
|
"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"},
|
||||||
|
}
|
||||||
14
ops/chart/Chart.yaml
Normal file
14
ops/chart/Chart.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: provider-service
|
||||||
|
description: Provider service for insurance quotes and policies
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
|
keywords:
|
||||||
|
- elixir
|
||||||
|
- commanded
|
||||||
|
- cqrs
|
||||||
|
dependencies:
|
||||||
|
- name: common
|
||||||
|
version: "4.6.2"
|
||||||
|
repository: https://bjw-s-labs.github.io/helm-charts/
|
||||||
158
ops/chart/values.yaml
Normal file
158
ops/chart/values.yaml
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
controllers:
|
||||||
|
main:
|
||||||
|
enabled: true
|
||||||
|
type: deployment
|
||||||
|
replicas: 1
|
||||||
|
containers:
|
||||||
|
main:
|
||||||
|
image:
|
||||||
|
repository: gitea.corredorconect.com/software-engineering/provider-service
|
||||||
|
tag: '{{ $.Chart.AppVersion }}'
|
||||||
|
env:
|
||||||
|
LOG_LEVEL: debug
|
||||||
|
MIX_ENV: prod
|
||||||
|
PORT: "8080"
|
||||||
|
PHX_HOST: "0.0.0.0"
|
||||||
|
PHX_SERVER: "true"
|
||||||
|
S3_HOST:
|
||||||
|
value: "dev.s3.corredorconect.com"
|
||||||
|
S3_BUCKET:
|
||||||
|
value: "provider-service"
|
||||||
|
AWS_REGION:
|
||||||
|
value: "us-east-1"
|
||||||
|
AWS_ACCESS_KEY_ID:
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-s3-credentials'
|
||||||
|
key: rootAccessKeyId
|
||||||
|
AWS_SECRET_ACCESS_KEY:
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-s3-credentials'
|
||||||
|
key: rootSecretAccessKey
|
||||||
|
RELEASE_COOKIE:
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-secrets'
|
||||||
|
key: cookie
|
||||||
|
SECRET_KEY_BASE:
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-secrets'
|
||||||
|
key: secretKeyBase
|
||||||
|
DATABASE_URL:
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: provider-service-cluster-pg-app
|
||||||
|
key: uri
|
||||||
|
probes:
|
||||||
|
liveness:
|
||||||
|
enabled: true
|
||||||
|
custom: true
|
||||||
|
spec:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
readiness:
|
||||||
|
enabled: true
|
||||||
|
custom: true
|
||||||
|
spec:
|
||||||
|
httpGet:
|
||||||
|
path: /health/ready
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
|
||||||
|
service:
|
||||||
|
main:
|
||||||
|
controller: main
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
http:
|
||||||
|
port: 8080
|
||||||
|
protocol: HTTP
|
||||||
|
|
||||||
|
rawResources:
|
||||||
|
password-generator:
|
||||||
|
enabled: true
|
||||||
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
|
kind: Password
|
||||||
|
suffix: password-generator
|
||||||
|
spec:
|
||||||
|
spec:
|
||||||
|
length: 32
|
||||||
|
noUpper: false
|
||||||
|
allowRepeat: true
|
||||||
|
secretKeys:
|
||||||
|
- cookie
|
||||||
|
- secretKeyBase
|
||||||
|
|
||||||
|
s3-credentials:
|
||||||
|
enabled: true
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
suffix: s3-credentials
|
||||||
|
spec:
|
||||||
|
spec:
|
||||||
|
refreshInterval: 0s
|
||||||
|
secretStoreRef:
|
||||||
|
name: cluster-secrets-store
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-s3-credentials'
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: rootAccessKeyId
|
||||||
|
remoteRef:
|
||||||
|
key: versitygw/versitygw-external-secret-secrets
|
||||||
|
property: rootAccessKeyId
|
||||||
|
- secretKey: rootSecretAccessKey
|
||||||
|
remoteRef:
|
||||||
|
key: versitygw/versitygw-external-secret-secrets
|
||||||
|
property: rootSecretAccessKey
|
||||||
|
|
||||||
|
external-secret:
|
||||||
|
enabled: true
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
suffix: secrets
|
||||||
|
spec:
|
||||||
|
spec:
|
||||||
|
refreshInterval: 0s
|
||||||
|
secretStoreRef:
|
||||||
|
name: cluster-secrets-store
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-secrets'
|
||||||
|
creationPolicy: Owner
|
||||||
|
dataFrom:
|
||||||
|
- sourceRef:
|
||||||
|
generatorRef:
|
||||||
|
apiVersion: generators.external-secrets.io/v1alpha1
|
||||||
|
kind: Password
|
||||||
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-password-generator'
|
||||||
|
|
||||||
|
cluster:
|
||||||
|
enabled: true
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
suffix: pg
|
||||||
|
spec:
|
||||||
|
spec:
|
||||||
|
description: "PostgreSQL cluster for provider-service"
|
||||||
|
instances: 1
|
||||||
|
bootstrap:
|
||||||
|
initdb:
|
||||||
|
database: provider_service
|
||||||
|
owner: provider_service
|
||||||
|
storage:
|
||||||
|
size: 5Gi
|
||||||
23
priv/repo/migrations/20260312182715_create_providers.exs
Normal file
23
priv/repo/migrations/20260312182715_create_providers.exs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
defmodule ProviderService.Repo.Migrations.CreateProviders do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:providers, primary_key: false) do
|
||||||
|
add(:provider_id, :string, primary_key: true)
|
||||||
|
add(:name, :string, null: false)
|
||||||
|
add(:email, :string)
|
||||||
|
add(:phone, :string)
|
||||||
|
add(:contact_name, :string)
|
||||||
|
add(:ruc, :string)
|
||||||
|
add(:address, :string)
|
||||||
|
add(:active, :boolean, default: true, null: false)
|
||||||
|
add(:templates, :map, default: %{})
|
||||||
|
add(:default_templates, :map, default: %{})
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime_usec)
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:providers, [:active]))
|
||||||
|
create(index(:providers, [:name]))
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
defmodule PolicyService.Repo.Migrations.CreateProjectionVersions do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:projection_versions, primary_key: false) do
|
||||||
|
add(:projection_name, :text, primary_key: true)
|
||||||
|
add(:last_seen_event_number, :bigint)
|
||||||
|
|
||||||
|
timestamps(type: :naive_datetime_usec)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
38
rel/vm.args.eex
Normal file
38
rel/vm.args.eex
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
## --- memory optimisation (embedded/low-RAM targets) ---
|
||||||
|
|
||||||
|
## disable carrier utilization limit
|
||||||
|
+MBacul 0
|
||||||
|
+MHacul 0
|
||||||
|
|
||||||
|
## smaller carrier sizes
|
||||||
|
+MBsmbcs 64
|
||||||
|
+MBlmbcs 128
|
||||||
|
+MHsmbcs 64
|
||||||
|
+MHlmbcs 128
|
||||||
|
|
||||||
|
## smaller main carrier
|
||||||
|
+MMscs 20
|
||||||
|
|
||||||
|
## --- scheduler tuning ---
|
||||||
|
|
||||||
|
+S 1:1
|
||||||
|
+SDcpu 1:1
|
||||||
|
+SDio 1
|
||||||
|
|
||||||
|
## --- resource limits ---
|
||||||
|
|
||||||
|
+t 100000
|
||||||
|
+P 50000
|
||||||
|
+Q 8192
|
||||||
|
|
||||||
|
## --- general ---
|
||||||
|
|
||||||
|
+c false
|
||||||
|
+sbwt none
|
||||||
|
+sbwtdcpu none
|
||||||
|
+sbwtdio none
|
||||||
|
+swt very_low
|
||||||
|
+swtdcpu very_low
|
||||||
|
+swtdio very_low
|
||||||
|
+secio false
|
||||||
|
+K true
|
||||||
8
test/provider_service_test.exs
Normal file
8
test/provider_service_test.exs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
defmodule ProviderServiceTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest ProviderService
|
||||||
|
|
||||||
|
test "greets the world" do
|
||||||
|
assert ProviderService.hello() == :world
|
||||||
|
end
|
||||||
|
end
|
||||||
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ExUnit.start()
|
||||||
Reference in New Issue
Block a user