This commit is contained in:
66
.gitea/workflows/build-and-publish.yaml
Normal file
66
.gitea/workflows/build-and-publish.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
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 }}
|
||||
|
||||
REGISTRY=${GITHUB_SERVER_URL#https://}
|
||||
|
||||
TARGET_IMAGE=$REGISTRY/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
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"
|
||||
@@ -43,6 +43,8 @@ config :commanded,
|
||||
config :commanded_ecto_projections,
|
||||
repo: CustomerService.Repo
|
||||
|
||||
config :flop, repo: CustomerService.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"
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
import Config
|
||||
|
||||
# Force using SSL in production. This also sets the "strict-security-transport" header,
|
||||
# known as HSTS. If you have a health check endpoint, you may want to exclude it below.
|
||||
# Note `:force_ssl` is required to be set at compile-time.
|
||||
config :customer_service, CustomerServiceWeb.Endpoint,
|
||||
force_ssl: [rewrite_on: [:x_forwarded_proto]],
|
||||
exclude: [
|
||||
# paths: ["/health"],
|
||||
hosts: ["localhost", "127.0.0.1"]
|
||||
]
|
||||
|
||||
# Do not print debug messages in production
|
||||
config :logger, level: :info
|
||||
|
||||
# Runtime production configuration, including reading
|
||||
# of environment variables, is done on config/runtime.exs.
|
||||
config :logger, level: :debug
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import Config
|
||||
|
||||
# config/runtime.exs is executed for all environments, including
|
||||
# during releases. It is executed after compilation and before the
|
||||
# system starts, so it is typically used to load production configuration
|
||||
# and secrets from environment variables or elsewhere. Do not define
|
||||
# any compile-time configuration in here, as it won't be applied.
|
||||
# The block below contains prod specific runtime configuration.
|
||||
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}
|
||||
|
||||
# ## Using releases
|
||||
#
|
||||
# If you use `mix release`, you need to explicitly enable the server
|
||||
# by passing the PHX_SERVER=true when you start it:
|
||||
#
|
||||
# PHX_SERVER=true bin/customer_service start
|
||||
#
|
||||
# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
|
||||
# script that automatically sets the env var above.
|
||||
if System.get_env("PHX_SERVER") do
|
||||
config :customer_service, CustomerServiceWeb.Endpoint, server: true
|
||||
end
|
||||
|
||||
if cookie = System.get_env("RELEASE_COOKIE") do
|
||||
config :elixir, :cookie, cookie
|
||||
end
|
||||
|
||||
config :customer_service, CustomerServiceWeb.Endpoint,
|
||||
http: [port: String.to_integer(System.get_env("PORT", "4000"))]
|
||||
http: [port: String.to_integer(System.get_env("PORT", "8080"))]
|
||||
|
||||
if config_env() == :prod do
|
||||
database_url =
|
||||
@@ -34,18 +36,15 @@ if config_env() == :prod do
|
||||
maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
|
||||
|
||||
config :customer_service, CustomerService.Repo,
|
||||
# ssl: true,
|
||||
url: database_url,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||
# For machines with several cores, consider starting multiple pools of `pool_size`
|
||||
# pool_count: 4,
|
||||
pool_size: 1,
|
||||
socket_options: maybe_ipv6
|
||||
|
||||
# The secret key base is used to sign/encrypt cookies and other secrets.
|
||||
# A default value is used in config/dev.exs and config/test.exs but you
|
||||
# want to use a different value for prod and you most likely don't want
|
||||
# to check this value into version control, so we use an environment
|
||||
# variable instead.
|
||||
config :customer_service, CustomerService.EventStore,
|
||||
serializer: Commanded.Serialization.JsonSerializer,
|
||||
url: database_url,
|
||||
pool_size: 1
|
||||
|
||||
secret_key_base =
|
||||
System.get_env("SECRET_KEY_BASE") ||
|
||||
raise """
|
||||
@@ -58,45 +57,9 @@ if config_env() == :prod do
|
||||
config :customer_service, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
|
||||
|
||||
config :customer_service, CustomerServiceWeb.Endpoint,
|
||||
url: [host: host, port: 443, scheme: "https"],
|
||||
url: [host: host, port: 80, scheme: "http"],
|
||||
http: [
|
||||
# Enable IPv6 and bind on all interfaces.
|
||||
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
|
||||
# See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0
|
||||
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
|
||||
ip: {0, 0, 0, 0, 0, 0, 0, 0}
|
||||
],
|
||||
secret_key_base: secret_key_base
|
||||
|
||||
# ## SSL Support
|
||||
#
|
||||
# To get SSL working, you will need to add the `https` key
|
||||
# to your endpoint configuration:
|
||||
#
|
||||
# config :customer_service, CustomerServiceWeb.Endpoint,
|
||||
# https: [
|
||||
# ...,
|
||||
# port: 443,
|
||||
# cipher_suite: :strong,
|
||||
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
|
||||
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
|
||||
# ]
|
||||
#
|
||||
# The `cipher_suite` is set to `:strong` to support only the
|
||||
# latest and more secure SSL ciphers. This means old browsers
|
||||
# and clients may not be supported. You can set it to
|
||||
# `:compatible` for wider support.
|
||||
#
|
||||
# `:keyfile` and `:certfile` expect an absolute path to the key
|
||||
# and cert in disk or a relative path inside priv, for example
|
||||
# "priv/ssl/server.key". For all supported SSL configuration
|
||||
# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
|
||||
#
|
||||
# We also recommend setting `force_ssl` in your config/prod.exs,
|
||||
# ensuring no data is ever sent via http, always redirecting to https:
|
||||
#
|
||||
# config :customer_service, CustomerServiceWeb.Endpoint,
|
||||
# force_ssl: [hsts: true]
|
||||
#
|
||||
# Check `Plug.SSL` for all available options in `force_ssl`.
|
||||
end
|
||||
|
||||
51
lib/customer_service/aggregates/corporate_customer.ex
Normal file
51
lib/customer_service/aggregates/corporate_customer.ex
Normal file
@@ -0,0 +1,51 @@
|
||||
defmodule CustomerService.Aggregates.CorporateCustomer do
|
||||
defstruct [
|
||||
:id,
|
||||
:legal_name,
|
||||
:commercial_name,
|
||||
:ruc,
|
||||
:legal_rep_name,
|
||||
:legal_rep_document_id,
|
||||
:email,
|
||||
:phone,
|
||||
:address
|
||||
]
|
||||
|
||||
alias __MODULE__
|
||||
alias Commanded.Aggregates.Aggregate
|
||||
alias CustomerService.Commands
|
||||
alias CustomerService.Events
|
||||
|
||||
@behaviour Aggregate
|
||||
|
||||
@impl Aggregate
|
||||
def execute(%CorporateCustomer{id: nil}, %Commands.CreateCorporateCustomer{} = cmd) do
|
||||
%Events.CorporateCustomerCreated{
|
||||
id: cmd.id,
|
||||
legal_name: cmd.legal_name,
|
||||
commercial_name: cmd.commercial_name,
|
||||
ruc: cmd.ruc,
|
||||
legal_rep_name: cmd.legal_rep_name,
|
||||
legal_rep_document_id: cmd.legal_rep_document_id,
|
||||
email: cmd.email,
|
||||
phone: cmd.phone,
|
||||
address: cmd.address
|
||||
}
|
||||
end
|
||||
|
||||
@impl Aggregate
|
||||
def apply(%CorporateCustomer{} = c, %Events.CorporateCustomerCreated{} = e) do
|
||||
%CorporateCustomer{
|
||||
c
|
||||
| id: e.id,
|
||||
legal_name: e.legal_name,
|
||||
commercial_name: e.commercial_name,
|
||||
ruc: e.ruc,
|
||||
legal_rep_name: e.legal_rep_name,
|
||||
legal_rep_document_id: e.legal_rep_document_id,
|
||||
email: e.email,
|
||||
phone: e.phone,
|
||||
address: e.address
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,9 @@ defmodule CustomerService.Aggregates.Customer do
|
||||
:birth_date,
|
||||
:gender,
|
||||
:email,
|
||||
:phone
|
||||
:phone,
|
||||
:address,
|
||||
:document_id
|
||||
]
|
||||
|
||||
alias __MODULE__
|
||||
@@ -24,13 +26,15 @@ defmodule CustomerService.Aggregates.Customer do
|
||||
birth_date: cmd.birth_date,
|
||||
gender: cmd.gender,
|
||||
email: cmd.email,
|
||||
phone: cmd.phone
|
||||
phone: cmd.phone,
|
||||
document_id: cmd.document_id,
|
||||
address: cmd.address
|
||||
}
|
||||
end
|
||||
|
||||
@impl Aggregate
|
||||
def apply(%Customer{} = c, %Events.CustomerCreated{} = e) do
|
||||
%Customer{
|
||||
%__MODULE__{
|
||||
c
|
||||
| id: e.id,
|
||||
first_name: e.first_name,
|
||||
@@ -38,7 +42,9 @@ defmodule CustomerService.Aggregates.Customer do
|
||||
birth_date: e.birth_date,
|
||||
gender: e.gender,
|
||||
email: e.email,
|
||||
phone: e.phone
|
||||
phone: e.phone,
|
||||
address: e.address,
|
||||
document_id: e.document_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,11 @@ defmodule CustomerService.Router do
|
||||
|
||||
identify(Aggregates.Customer, by: :id)
|
||||
dispatch([Commands.CreateCustomer], to: Aggregates.Customer)
|
||||
|
||||
dispatch(Commands.CreateCorporateCustomer,
|
||||
to: Aggregates.CorporateCustomer,
|
||||
identity: :id
|
||||
)
|
||||
end
|
||||
|
||||
defmodule CustomerService.CommandedApp do
|
||||
|
||||
@@ -6,6 +6,22 @@ defmodule CustomerService.Commands.CreateCustomer do
|
||||
:birth_date,
|
||||
:gender,
|
||||
:email,
|
||||
:phone
|
||||
:phone,
|
||||
:address,
|
||||
:document_id
|
||||
]
|
||||
end
|
||||
|
||||
defmodule CustomerService.Commands.CreateCorporateCustomer do
|
||||
defstruct [
|
||||
:id,
|
||||
:legal_name,
|
||||
:commercial_name,
|
||||
:ruc,
|
||||
:legal_rep_name,
|
||||
:legal_rep_document_id,
|
||||
:email,
|
||||
:phone,
|
||||
:address
|
||||
]
|
||||
end
|
||||
|
||||
19
lib/customer_service/customer/filters.ex
Normal file
19
lib/customer_service/customer/filters.ex
Normal file
@@ -0,0 +1,19 @@
|
||||
defmodule CustomerService.Customers.Filters do
|
||||
import Ecto.Query
|
||||
|
||||
def search(query, %Flop.Filter{value: value}, _opts) do
|
||||
term = "%#{value}%"
|
||||
|
||||
where(
|
||||
query,
|
||||
[c],
|
||||
ilike(c.first_name, ^term) or
|
||||
ilike(c.last_name, ^term) or
|
||||
ilike(c.legal_name, ^term) or
|
||||
ilike(c.email, ^term) or
|
||||
ilike(c.phone, ^term) or
|
||||
ilike(c.document_id, ^term) or
|
||||
ilike(c.ruc, ^term)
|
||||
)
|
||||
end
|
||||
end
|
||||
15
lib/customer_service/customer/queries.ex
Normal file
15
lib/customer_service/customer/queries.ex
Normal file
@@ -0,0 +1,15 @@
|
||||
defmodule CustomerService.Customer.Queries do
|
||||
alias CustomerService.Projections.Customer
|
||||
alias CustomerService.Repo
|
||||
|
||||
def list_customers(params \\ %{}) do
|
||||
Flop.validate_and_run(Customer, params, for: Customer)
|
||||
end
|
||||
|
||||
def get_customer(id) do
|
||||
case Repo.get(Customer, id) do
|
||||
nil -> {:error, :not_found}
|
||||
customer -> {:ok, customer}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,6 +7,23 @@ defmodule CustomerService.Events.CustomerCreated do
|
||||
:birth_date,
|
||||
:gender,
|
||||
:email,
|
||||
:phone
|
||||
:phone,
|
||||
:address,
|
||||
:document_id
|
||||
]
|
||||
end
|
||||
|
||||
defmodule CustomerService.Events.CorporateCustomerCreated do
|
||||
@derive Jason.Encoder
|
||||
defstruct [
|
||||
:id,
|
||||
:legal_name,
|
||||
:commercial_name,
|
||||
:ruc,
|
||||
:legal_rep_name,
|
||||
:legal_rep_document_id,
|
||||
:email,
|
||||
:phone,
|
||||
:address
|
||||
]
|
||||
end
|
||||
|
||||
@@ -4,22 +4,64 @@ defmodule CustomerService.Projections.Customer do
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:id,
|
||||
:customer_type,
|
||||
# individual
|
||||
:first_name,
|
||||
:last_name,
|
||||
:birth_date,
|
||||
:gender,
|
||||
:document_id,
|
||||
# corporate
|
||||
:legal_name,
|
||||
:commercial_name,
|
||||
:ruc,
|
||||
:legal_rep_name,
|
||||
:legal_rep_document_id,
|
||||
# shared
|
||||
:address,
|
||||
:email,
|
||||
:phone,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
|
||||
@derive {
|
||||
Flop.Schema,
|
||||
filterable: [:customer_type, :email, :phone, :document_id, :ruc, :search],
|
||||
sortable: [:last_name, :legal_name, :inserted_at],
|
||||
default_limit: 20,
|
||||
max_limit: 100,
|
||||
custom_fields: [
|
||||
search: [
|
||||
filter: {CustomerService.Customers.Filters, :search, []},
|
||||
ecto_type: :string,
|
||||
operators: [:==]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: false}
|
||||
@timestamps_opts [type: :utc_datetime_usec]
|
||||
|
||||
schema "customers" do
|
||||
field :customer_type, :string, default: "individual"
|
||||
|
||||
# individual fields
|
||||
field :first_name, :string
|
||||
field :last_name, :string
|
||||
field :birth_date, :date
|
||||
field :gender, :string
|
||||
field :document_id, :string
|
||||
|
||||
# corporate fields
|
||||
field :legal_name, :string
|
||||
field :commercial_name, :string
|
||||
field :ruc, :string
|
||||
field :legal_rep_name, :string
|
||||
field :legal_rep_document_id, :string
|
||||
|
||||
# shared
|
||||
field :address, :string
|
||||
field :email, :string
|
||||
field :phone, :string
|
||||
|
||||
|
||||
@@ -11,21 +11,40 @@ defmodule CustomerService.Projectors.Customer do
|
||||
project(%Events.CustomerCreated{} = event, fn multi ->
|
||||
Ecto.Multi.insert(multi, :customer, %Customer{
|
||||
id: event.id,
|
||||
customer_type: "individual",
|
||||
first_name: event.first_name,
|
||||
last_name: event.last_name,
|
||||
birth_date: event.birth_date,
|
||||
birth_date: parse_date(event.birth_date),
|
||||
gender: event.gender,
|
||||
email: event.email,
|
||||
phone: event.phone
|
||||
phone: event.phone,
|
||||
address: event.address,
|
||||
document_id: event.document_id
|
||||
})
|
||||
end)
|
||||
|
||||
# project %Events.CustomerDeactivated{} = event, _metadata do
|
||||
# Ecto.Multi.update_all(
|
||||
# multi,
|
||||
# :deactivate_customer,
|
||||
# from(c in Customer, where: c.customer_id == ^event.customer_id),
|
||||
# set: [active: false]
|
||||
# )
|
||||
# end
|
||||
defp parse_date(nil), do: nil
|
||||
defp parse_date(%Date{} = d), do: d
|
||||
|
||||
defp parse_date(str) when is_binary(str) do
|
||||
case Date.from_iso8601(str) do
|
||||
{:ok, d} -> d
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
project(%Events.CorporateCustomerCreated{} = e, _meta, fn multi ->
|
||||
Ecto.Multi.insert(multi, :customer, %Customer{
|
||||
id: e.id,
|
||||
customer_type: "corporate",
|
||||
legal_name: e.legal_name,
|
||||
commercial_name: e.commercial_name,
|
||||
ruc: e.ruc,
|
||||
legal_rep_name: e.legal_rep_name,
|
||||
legal_rep_document_id: e.legal_rep_document_id,
|
||||
email: e.email,
|
||||
phone: e.phone,
|
||||
address: e.address
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -1,84 +1,181 @@
|
||||
defmodule CustomerServiceWeb.Customer do
|
||||
defmodule CustomerServiceWeb.CustomerController do
|
||||
use CustomerServiceWeb, :controller
|
||||
|
||||
alias CustomerServiceWeb.Schemas.CreateCustomerRequest
|
||||
alias CustomerServiceWeb.Schemas.CustomerResponse
|
||||
alias CustomerService.Commands.CreateCustomer
|
||||
alias CustomerService.CommandedApp
|
||||
use OpenApiSpex.ControllerSpecs
|
||||
|
||||
tags ["Customers"]
|
||||
alias CustomerService.Commands.{CreateCustomer, CreateCorporateCustomer}
|
||||
alias CustomerService.Customer.Queries, as: CustomerQueries
|
||||
alias CustomerServiceWeb.Schemas.Customer, as: CustomerSchemas
|
||||
|
||||
operation :create,
|
||||
summary: "Create customer",
|
||||
request_body: {"Customer data", "application/json", CreateCustomerRequest},
|
||||
operation(:index,
|
||||
summary: "List customers",
|
||||
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: {"Customer created", "application/json", CustomerResponse}
|
||||
ok: {"Customer list", "application/json", CustomerSchemas.CustomerListResponse}
|
||||
]
|
||||
)
|
||||
|
||||
def index(conn, params) do
|
||||
case CustomerQueries.list_customers(params) do
|
||||
{:ok, {customers, meta}} ->
|
||||
conn
|
||||
|> put_status(:ok)
|
||||
|> json(%{
|
||||
data: Enum.map(customers, &customer_json/1),
|
||||
meta: %{
|
||||
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?
|
||||
}
|
||||
})
|
||||
|
||||
{:error, _meta} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "invalid parameters"})
|
||||
end
|
||||
end
|
||||
|
||||
operation(:show,
|
||||
summary: "Get customer",
|
||||
parameters: [
|
||||
id: [in: :path, type: :string, required: true, description: "Customer ID"]
|
||||
],
|
||||
responses: [
|
||||
ok: {"Customer", "application/json", CustomerSchemas.CustomerResponse},
|
||||
not_found: {"Not found", "application/json", %OpenApiSpex.Schema{type: :object}}
|
||||
]
|
||||
)
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
case CustomerQueries.get_customer(id) do
|
||||
{:ok, customer} ->
|
||||
conn |> put_status(:ok) |> json(%{data: customer_json(customer)})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn |> put_status(:not_found) |> json(%{error: "not found"})
|
||||
end
|
||||
end
|
||||
|
||||
operation(:create,
|
||||
summary: "Create individual customer",
|
||||
request_body:
|
||||
{"Customer data", "application/json", CustomerSchemas.CreateCustomer, required: true},
|
||||
responses: [
|
||||
ok: {"Customer created", "application/json", CustomerSchemas.CustomerResponse}
|
||||
]
|
||||
)
|
||||
|
||||
def create(conn, params) do
|
||||
customer_id = Ecto.UUID.generate()
|
||||
|
||||
command =
|
||||
%CreateCustomer{
|
||||
command = %CreateCustomer{
|
||||
id: customer_id,
|
||||
first_name: params["first_name"],
|
||||
last_name: params["last_name"],
|
||||
birth_date: Date.from_iso8601!(params["birth_date"]),
|
||||
birth_date: parse_date(params["birth_date"]),
|
||||
gender: params["gender"],
|
||||
email: params["email"],
|
||||
phone: params["phone"]
|
||||
phone: params["phone"],
|
||||
document_id: params["document_id"]
|
||||
}
|
||||
|
||||
case CommandedApp.dispatch(command, consistency: :strong) do
|
||||
dispatch_and_return(conn, command, customer_id)
|
||||
end
|
||||
|
||||
operation(:create_corporate,
|
||||
summary: "Create corporate customer",
|
||||
request_body:
|
||||
{"Corporate customer data", "application/json", CustomerSchemas.CreateCorporateCustomer,
|
||||
required: true},
|
||||
responses: [
|
||||
ok: {"Corporate customer created", "application/json", CustomerSchemas.CustomerResponse}
|
||||
]
|
||||
)
|
||||
|
||||
def create_corporate(conn, params) do
|
||||
customer_id = Ecto.UUID.generate()
|
||||
|
||||
command = %CreateCorporateCustomer{
|
||||
id: customer_id,
|
||||
legal_name: params["legal_name"],
|
||||
commercial_name: params["commercial_name"],
|
||||
ruc: params["ruc"],
|
||||
legal_rep_name: params["legal_rep_name"],
|
||||
legal_rep_document_id: params["legal_rep_document_id"],
|
||||
email: params["email"],
|
||||
phone: params["phone"],
|
||||
address: params["address"]
|
||||
}
|
||||
|
||||
dispatch_and_return(conn, command, customer_id)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Private
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp dispatch_and_return(conn, command, customer_id) do
|
||||
case CustomerService.CommandedApp.dispatch(command, consistency: :strong) do
|
||||
:ok ->
|
||||
json(conn, %{id: customer_id})
|
||||
case CustomerQueries.get_customer(customer_id) do
|
||||
{:ok, customer} ->
|
||||
conn |> put_status(:ok) |> json(%{data: customer_json(customer)})
|
||||
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "customer created but not found in projection"})
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:unprocessable_entity)
|
||||
|> json(%{error: inspect(reason)})
|
||||
conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)})
|
||||
end
|
||||
end
|
||||
|
||||
operation :show,
|
||||
summary: "Get customer",
|
||||
parameters: [
|
||||
id: [in: :path, type: :string, description: "Customer ID"]
|
||||
],
|
||||
responses: [
|
||||
ok: {"Customer", "application/json", CustomerResponse},
|
||||
not_found: {"Not found", "application/json", nil}
|
||||
]
|
||||
defp parse_date(nil), do: nil
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
case CustomerService.Repo.get(CustomerService.Projections.Customer, id) do
|
||||
nil ->
|
||||
send_resp(conn, 404, "")
|
||||
|
||||
customer ->
|
||||
json(conn, customer)
|
||||
defp parse_date(str) do
|
||||
case Date.from_iso8601(str) do
|
||||
{:ok, date} -> date
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
operation :index,
|
||||
summary: "List customers",
|
||||
responses: [
|
||||
ok:
|
||||
{"Customer list", "application/json",
|
||||
%OpenApiSpex.Schema{
|
||||
type: :array,
|
||||
items: CustomerResponse
|
||||
}}
|
||||
]
|
||||
|
||||
def index(conn, _) do
|
||||
case CustomerService.Repo.all(CustomerService.Projections.Customer) do
|
||||
nil ->
|
||||
send_resp(conn, 404, "")
|
||||
|
||||
customer ->
|
||||
json(conn, customer)
|
||||
defp customer_json(%{customer_type: "corporate"} = c) do
|
||||
%{
|
||||
id: c.id,
|
||||
customer_type: "corporate",
|
||||
legal_name: c.legal_name,
|
||||
commercial_name: c.commercial_name,
|
||||
ruc: c.ruc,
|
||||
legal_rep_name: c.legal_rep_name,
|
||||
legal_rep_document_id: c.legal_rep_document_id,
|
||||
email: c.email,
|
||||
phone: c.phone,
|
||||
address: c.address
|
||||
}
|
||||
end
|
||||
|
||||
defp customer_json(c) do
|
||||
%{
|
||||
id: c.id,
|
||||
customer_type: "individual",
|
||||
first_name: c.first_name,
|
||||
last_name: c.last_name,
|
||||
email: c.email,
|
||||
phone: c.phone,
|
||||
birth_date: c.birth_date,
|
||||
gender: c.gender,
|
||||
document_id: c.document_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
defmodule CustomerServiceWeb.ErrorJSON do
|
||||
@moduledoc """
|
||||
This module is invoked by your endpoint in case of errors on JSON requests.
|
||||
|
||||
See config/config.exs.
|
||||
"""
|
||||
|
||||
# If you want to customize a particular status code,
|
||||
# you may add your own clauses, such as:
|
||||
#
|
||||
# def render("500.json", _assigns) do
|
||||
# %{errors: %{detail: "Internal Server Error"}}
|
||||
# end
|
||||
def render("404.json", _assigns) do
|
||||
%{errors: %{detail: "Not Found"}}
|
||||
end
|
||||
|
||||
def render("500.json", _assigns) do
|
||||
%{errors: %{detail: "Internal Server Error"}}
|
||||
end
|
||||
|
||||
# By default, Phoenix returns the status message from
|
||||
# the template name. For example, "404.json" becomes
|
||||
# "Not Found".
|
||||
def render(template, _assigns) do
|
||||
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
|
||||
end
|
||||
|
||||
15
lib/customer_service_web/controllers/health_controller.ex
Normal file
15
lib/customer_service_web/controllers/health_controller.ex
Normal file
@@ -0,0 +1,15 @@
|
||||
defmodule CustomerServiceWeb.HealthController do
|
||||
use CustomerServiceWeb, :controller
|
||||
|
||||
def health(conn, _params) do
|
||||
conn
|
||||
|> put_status(:ok)
|
||||
|> json(%{status: "ok"})
|
||||
end
|
||||
|
||||
def ready(conn, _params) do
|
||||
conn
|
||||
|> put_status(:ok)
|
||||
|> json(%{status: "ready"})
|
||||
end
|
||||
end
|
||||
@@ -45,5 +45,6 @@ defmodule CustomerServiceWeb.Endpoint do
|
||||
plug Plug.MethodOverride
|
||||
plug Plug.Head
|
||||
plug Plug.Session, @session_options
|
||||
plug CORSPlug, origin: ["http://localhost:3000"]
|
||||
plug CustomerServiceWeb.Router
|
||||
end
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
defmodule CustomerServiceWeb.Router do
|
||||
use CustomerServiceWeb, :router
|
||||
alias CustomerServiceWeb.CustomerController
|
||||
|
||||
pipeline :api do
|
||||
plug CORSPlug, origin: "*"
|
||||
plug :accepts, ["json"]
|
||||
plug OpenApiSpex.Plug.PutApiSpec, module: CustomerServiceWeb.ApiSpec
|
||||
end
|
||||
|
||||
get("/health", CustomerServiceWeb.HealthController, :health)
|
||||
get("/health/ready", CustomerServiceWeb.HealthController, :ready)
|
||||
|
||||
scope "/api" do
|
||||
pipe_through :api
|
||||
|
||||
resources "/customers", CustomerServiceWeb.Customer, only: [:create, :index, :show]
|
||||
get "/openapi", OpenApiSpex.Plug.RenderSpec, []
|
||||
|
||||
scope "/v1" do
|
||||
post "/customers", CustomerController, :create
|
||||
post "/customers/individual", CustomerController, :create
|
||||
post "/customers/corporate", CustomerController, :create_corporate
|
||||
get "/customers", CustomerController, :index
|
||||
get "/customers/:id", CustomerController, :show
|
||||
end
|
||||
end
|
||||
|
||||
if Mix.env() == :dev do
|
||||
scope "/swaggerui" do
|
||||
get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,37 +1,140 @@
|
||||
defmodule CustomerServiceWeb.Schemas do
|
||||
defmodule CustomerResponse do
|
||||
require OpenApiSpex
|
||||
defmodule CustomerServiceWeb.Schemas.Customer do
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
defmodule PaginationMeta do
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "Customer",
|
||||
title: "PaginationMeta",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string, format: :uuid},
|
||||
first_name: %Schema{type: :string},
|
||||
last_name: %Schema{type: :string},
|
||||
birth_date: %Schema{type: :string, format: :date},
|
||||
gender: %Schema{type: :string},
|
||||
email: %Schema{type: :string, format: :email},
|
||||
phone: %Schema{type: :string}
|
||||
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 CreateCustomerRequest do
|
||||
defmodule IndividualCustomerData do
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "IndividualCustomer",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string, format: :uuid},
|
||||
customer_type: %Schema{type: :string, enum: ["individual"]},
|
||||
first_name: %Schema{type: :string},
|
||||
last_name: %Schema{type: :string},
|
||||
email: %Schema{type: :string, format: :email},
|
||||
phone: %Schema{type: :string},
|
||||
birth_date: %Schema{type: :string, format: :date},
|
||||
gender: %Schema{type: :string},
|
||||
document_id: %Schema{type: :string}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defmodule CorporateCustomerData do
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "CorporateCustomer",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string, format: :uuid},
|
||||
customer_type: %Schema{type: :string, enum: ["corporate"]},
|
||||
legal_name: %Schema{type: :string},
|
||||
commercial_name: %Schema{type: :string},
|
||||
ruc: %Schema{type: :string},
|
||||
legal_rep_name: %Schema{type: :string},
|
||||
legal_rep_document_id: %Schema{type: :string},
|
||||
email: %Schema{type: :string, format: :email},
|
||||
phone: %Schema{type: :string},
|
||||
address: %Schema{type: :string}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defmodule CustomerData do
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "Customer",
|
||||
oneOf: [IndividualCustomerData, CorporateCustomerData],
|
||||
discriminator: %OpenApiSpex.Discriminator{
|
||||
propertyName: "customer_type",
|
||||
mapping: %{
|
||||
"individual" => IndividualCustomerData,
|
||||
"corporate" => CorporateCustomerData
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defmodule CreateCustomer do
|
||||
require OpenApiSpex
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "CreateCustomer",
|
||||
type: :object,
|
||||
required: [:first_name, :last_name],
|
||||
properties: %{
|
||||
first_name: %Schema{type: :string},
|
||||
last_name: %Schema{type: :string},
|
||||
email: %Schema{type: :string, format: :email},
|
||||
phone: %Schema{type: :string},
|
||||
birth_date: %Schema{type: :string, format: :date},
|
||||
gender: %Schema{type: :string},
|
||||
document_id: %Schema{type: :string}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defmodule CreateCorporateCustomer do
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "CreateCorporateCustomer",
|
||||
type: :object,
|
||||
required: [:legal_name, :ruc],
|
||||
properties: %{
|
||||
legal_name: %Schema{type: :string},
|
||||
commercial_name: %Schema{type: :string},
|
||||
ruc: %Schema{type: :string},
|
||||
legal_rep_name: %Schema{type: :string},
|
||||
legal_rep_document_id: %Schema{type: :string},
|
||||
email: %Schema{type: :string, format: :email},
|
||||
phone: %Schema{type: :string}
|
||||
phone: %Schema{type: :string},
|
||||
address: %Schema{type: :string}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defmodule CustomerListResponse do
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "CustomerListResponse",
|
||||
type: :object,
|
||||
properties: %{
|
||||
data: %Schema{type: :array, items: CustomerData},
|
||||
meta: PaginationMeta
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defmodule CustomerResponse do
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "CustomerResponse",
|
||||
type: :object,
|
||||
properties: %{
|
||||
data: CustomerData
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
3
mix.exs
3
mix.exs
@@ -53,7 +53,8 @@ defmodule CustomerService.MixProject do
|
||||
{:eventstore, "~> 1.4"},
|
||||
{:open_api_spex, "~> 3.20"},
|
||||
{:commanded_eventstore_adapter, "~> 1.4"},
|
||||
{:cors_plug, "~> 3.0"}
|
||||
{:cors_plug, "~> 3.0"},
|
||||
{:flop, "~> 0.26"}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
2
mix.lock
2
mix.lock
@@ -11,11 +11,13 @@
|
||||
"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.4", "b6e9d07557ddba62508a9ce4a484989a5bb5e9a048ae0e695f6d93f095c25d60", [: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", "2b38cf0749ca4d1c5a8bcbff79bbe15446861ca12a61f9fba604486cb6b62a14"},
|
||||
"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"},
|
||||
"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"},
|
||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
"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"},
|
||||
"phoenix": {:hex, :phoenix, "1.8.4", "0387f84f00071cba8d71d930b9121b2fb3645197a9206c31b908d2e7902a4851", [: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", "c988b1cd3b084eebb13e6676d572597d387fa607dab258526637b4e6c4c08543"},
|
||||
"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"},
|
||||
|
||||
6
ops/chart/Chart.lock
Normal file
6
ops/chart/Chart.lock
Normal file
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
- name: common
|
||||
repository: https://bjw-s-labs.github.io/helm-charts/
|
||||
version: 4.6.2
|
||||
digest: sha256:35e8f4e5d15d878c246a04eb51de580291f31203fa10e9e4d2318f16026b2061
|
||||
generated: "2026-04-15T15:40:50.605667603-05:00"
|
||||
14
ops/chart/Chart.yaml
Normal file
14
ops/chart/Chart.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v2
|
||||
name: customer-service
|
||||
description: Customer 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/
|
||||
4
ops/chart/templates/common.tpl
Normal file
4
ops/chart/templates/common.tpl
Normal file
@@ -0,0 +1,4 @@
|
||||
{{/*
|
||||
Render all resources provided by the common library
|
||||
*/}}
|
||||
{{- include "bjw-s.common.loader.all" . -}}
|
||||
118
ops/chart/values.yaml
Normal file
118
ops/chart/values.yaml
Normal file
@@ -0,0 +1,118 @@
|
||||
controllers:
|
||||
main:
|
||||
enabled: true
|
||||
type: deployment
|
||||
replicas: 1
|
||||
containers:
|
||||
main:
|
||||
image:
|
||||
repository: gitea.corredorconect.com/software-engineering/customer-service
|
||||
tag: '{{ $.Chart.AppVersion }}'
|
||||
env:
|
||||
LOG_LEVEL: info
|
||||
MIX_ENV: prod
|
||||
PORT: "8080"
|
||||
PHX_HOST: "0.0.0.0"
|
||||
PHX_SERVER: "true"
|
||||
DATABASE_URL:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: customer-service-cluster-pg-app
|
||||
key: uri
|
||||
SECRET_KEY_BASE:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-secrets'
|
||||
key: secretKeyBase
|
||||
RELEASE_COOKIE:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-secrets'
|
||||
key: cookie
|
||||
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
|
||||
|
||||
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 customer-service"
|
||||
instances: 1
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: customer_service
|
||||
owner: customer_service
|
||||
storage:
|
||||
size: 5Gi
|
||||
@@ -10,7 +10,14 @@ defmodule CustomerService.Repo.Migrations.AddCustomerTable do
|
||||
add :gender, :string
|
||||
add :email, :string
|
||||
add :phone, :string
|
||||
|
||||
add :document_id, :string
|
||||
add :customer_type, :string, null: false, default: "individual"
|
||||
add :legal_name, :string
|
||||
add :commercial_name, :string
|
||||
add :ruc, :string
|
||||
add :legal_rep_name, :string
|
||||
add :legal_rep_document_id, :string
|
||||
add :address, :string
|
||||
timestamps()
|
||||
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
|
||||
@@ -2,7 +2,9 @@ defmodule CustomerServiceWeb.ErrorJSONTest do
|
||||
use CustomerServiceWeb.ConnCase, async: true
|
||||
|
||||
test "renders 404" do
|
||||
assert CustomerServiceWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}}
|
||||
assert CustomerServiceWeb.ErrorJSON.render("404.json", %{}) == %{
|
||||
errors: %{detail: "Not Found"}
|
||||
}
|
||||
end
|
||||
|
||||
test "renders 500" do
|
||||
|
||||
Reference in New Issue
Block a user