init commit
Some checks failed
Build and Publish / build-release (push) Failing after 1s

This commit is contained in:
2026-04-15 16:20:22 -05:00
parent 072dbf6e66
commit b1b1c21e4e
28 changed files with 822 additions and 194 deletions

View 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"

View File

@@ -43,6 +43,8 @@ config :commanded,
config :commanded_ecto_projections, config :commanded_ecto_projections,
repo: CustomerService.Repo repo: CustomerService.Repo
config :flop, repo: CustomerService.Repo
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs" import_config "#{config_env()}.exs"

View File

@@ -1,17 +1,3 @@
import Config import Config
# Force using SSL in production. This also sets the "strict-security-transport" header, config :logger, level: :debug
# 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.

View File

@@ -1,27 +1,29 @@
import Config import Config
# config/runtime.exs is executed for all environments, including logger_level =
# during releases. It is executed after compilation and before the case System.get_env("LOG_LEVEL", "info") do
# system starts, so it is typically used to load production configuration "debug" -> :debug
# and secrets from environment variables or elsewhere. Do not define "info" -> :info
# any compile-time configuration in here, as it won't be applied. "warn" -> :warning
# The block below contains prod specific runtime configuration. "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 if System.get_env("PHX_SERVER") do
config :customer_service, CustomerServiceWeb.Endpoint, server: true config :customer_service, CustomerServiceWeb.Endpoint, server: true
end end
if cookie = System.get_env("RELEASE_COOKIE") do
config :elixir, :cookie, cookie
end
config :customer_service, CustomerServiceWeb.Endpoint, 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 if config_env() == :prod do
database_url = 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: [] maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
config :customer_service, CustomerService.Repo, config :customer_service, CustomerService.Repo,
# ssl: true,
url: database_url, url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), pool_size: 1,
# For machines with several cores, consider starting multiple pools of `pool_size`
# pool_count: 4,
socket_options: maybe_ipv6 socket_options: maybe_ipv6
# The secret key base is used to sign/encrypt cookies and other secrets. config :customer_service, CustomerService.EventStore,
# A default value is used in config/dev.exs and config/test.exs but you serializer: Commanded.Serialization.JsonSerializer,
# want to use a different value for prod and you most likely don't want url: database_url,
# to check this value into version control, so we use an environment pool_size: 1
# variable instead.
secret_key_base = secret_key_base =
System.get_env("SECRET_KEY_BASE") || System.get_env("SECRET_KEY_BASE") ||
raise """ 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, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
config :customer_service, CustomerServiceWeb.Endpoint, config :customer_service, CustomerServiceWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"], url: [host: host, port: 80, scheme: "http"],
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} ip: {0, 0, 0, 0, 0, 0, 0, 0}
], ],
secret_key_base: secret_key_base 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 end

View 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

View File

@@ -6,7 +6,9 @@ defmodule CustomerService.Aggregates.Customer do
:birth_date, :birth_date,
:gender, :gender,
:email, :email,
:phone :phone,
:address,
:document_id
] ]
alias __MODULE__ alias __MODULE__
@@ -24,13 +26,15 @@ defmodule CustomerService.Aggregates.Customer do
birth_date: cmd.birth_date, birth_date: cmd.birth_date,
gender: cmd.gender, gender: cmd.gender,
email: cmd.email, email: cmd.email,
phone: cmd.phone phone: cmd.phone,
document_id: cmd.document_id,
address: cmd.address
} }
end end
@impl Aggregate @impl Aggregate
def apply(%Customer{} = c, %Events.CustomerCreated{} = e) do def apply(%Customer{} = c, %Events.CustomerCreated{} = e) do
%Customer{ %__MODULE__{
c c
| id: e.id, | id: e.id,
first_name: e.first_name, first_name: e.first_name,
@@ -38,7 +42,9 @@ defmodule CustomerService.Aggregates.Customer do
birth_date: e.birth_date, birth_date: e.birth_date,
gender: e.gender, gender: e.gender,
email: e.email, email: e.email,
phone: e.phone phone: e.phone,
address: e.address,
document_id: e.document_id
} }
end end
end end

View File

@@ -5,6 +5,11 @@ defmodule CustomerService.Router do
identify(Aggregates.Customer, by: :id) identify(Aggregates.Customer, by: :id)
dispatch([Commands.CreateCustomer], to: Aggregates.Customer) dispatch([Commands.CreateCustomer], to: Aggregates.Customer)
dispatch(Commands.CreateCorporateCustomer,
to: Aggregates.CorporateCustomer,
identity: :id
)
end end
defmodule CustomerService.CommandedApp do defmodule CustomerService.CommandedApp do

View File

@@ -6,6 +6,22 @@ defmodule CustomerService.Commands.CreateCustomer do
:birth_date, :birth_date,
:gender, :gender,
:email, :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 end

View 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

View 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

View File

@@ -7,6 +7,23 @@ defmodule CustomerService.Events.CustomerCreated do
:birth_date, :birth_date,
:gender, :gender,
:email, :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 end

View File

@@ -2,24 +2,66 @@ defmodule CustomerService.Projections.Customer do
use Ecto.Schema use Ecto.Schema
@derive {Jason.Encoder, @derive {Jason.Encoder,
only: [ only: [
:id, :id,
:first_name, :customer_type,
:last_name, # individual
:birth_date, :first_name,
:gender, :last_name,
:email, :birth_date,
:phone, :gender,
:inserted_at, :document_id,
:updated_at # 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} @primary_key {:id, :binary_id, autogenerate: false}
@timestamps_opts [type: :utc_datetime_usec] @timestamps_opts [type: :utc_datetime_usec]
schema "customers" do schema "customers" do
field :customer_type, :string, default: "individual"
# individual fields
field :first_name, :string field :first_name, :string
field :last_name, :string field :last_name, :string
field :birth_date, :date field :birth_date, :date
field :gender, :string 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 :email, :string
field :phone, :string field :phone, :string

View File

@@ -11,21 +11,40 @@ defmodule CustomerService.Projectors.Customer do
project(%Events.CustomerCreated{} = event, fn multi -> project(%Events.CustomerCreated{} = event, fn multi ->
Ecto.Multi.insert(multi, :customer, %Customer{ Ecto.Multi.insert(multi, :customer, %Customer{
id: event.id, id: event.id,
customer_type: "individual",
first_name: event.first_name, first_name: event.first_name,
last_name: event.last_name, last_name: event.last_name,
birth_date: event.birth_date, birth_date: parse_date(event.birth_date),
gender: event.gender, gender: event.gender,
email: event.email, email: event.email,
phone: event.phone phone: event.phone,
address: event.address,
document_id: event.document_id
}) })
end) end)
# project %Events.CustomerDeactivated{} = event, _metadata do defp parse_date(nil), do: nil
# Ecto.Multi.update_all( defp parse_date(%Date{} = d), do: d
# multi,
# :deactivate_customer, defp parse_date(str) when is_binary(str) do
# from(c in Customer, where: c.customer_id == ^event.customer_id), case Date.from_iso8601(str) do
# set: [active: false] {:ok, d} -> d
# ) _ -> nil
# end 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 end

View File

@@ -1,84 +1,181 @@
defmodule CustomerServiceWeb.Customer do defmodule CustomerServiceWeb.CustomerController do
use CustomerServiceWeb, :controller use CustomerServiceWeb, :controller
alias CustomerServiceWeb.Schemas.CreateCustomerRequest
alias CustomerServiceWeb.Schemas.CustomerResponse
alias CustomerService.Commands.CreateCustomer
alias CustomerService.CommandedApp
use OpenApiSpex.ControllerSpecs use OpenApiSpex.ControllerSpecs
tags ["Customers"] alias CustomerService.Commands.{CreateCustomer, CreateCorporateCustomer}
alias CustomerService.Customer.Queries, as: CustomerQueries
alias CustomerServiceWeb.Schemas.Customer, as: CustomerSchemas
operation :create, operation(:index,
summary: "Create customer", summary: "List customers",
request_body: {"Customer data", "application/json", CreateCustomerRequest}, 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: [ 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 def create(conn, params) do
customer_id = Ecto.UUID.generate() customer_id = Ecto.UUID.generate()
command = command = %CreateCustomer{
%CreateCustomer{ id: customer_id,
id: customer_id, first_name: params["first_name"],
first_name: params["first_name"], last_name: params["last_name"],
last_name: params["last_name"], birth_date: parse_date(params["birth_date"]),
birth_date: Date.from_iso8601!(params["birth_date"]), gender: params["gender"],
gender: params["gender"], email: params["email"],
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 -> :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} -> {:error, reason} ->
conn conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)})
|> put_status(:unprocessable_entity)
|> json(%{error: inspect(reason)})
end end
end end
operation :show, defp parse_date(nil), do: nil
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}
]
def show(conn, %{"id" => id}) do defp parse_date(str) do
case CustomerService.Repo.get(CustomerService.Projections.Customer, id) do case Date.from_iso8601(str) do
nil -> {:ok, date} -> date
send_resp(conn, 404, "") _ -> nil
customer ->
json(conn, customer)
end end
end end
operation :index, defp customer_json(%{customer_type: "corporate"} = c) do
summary: "List customers", %{
responses: [ id: c.id,
ok: customer_type: "corporate",
{"Customer list", "application/json", legal_name: c.legal_name,
%OpenApiSpex.Schema{ commercial_name: c.commercial_name,
type: :array, ruc: c.ruc,
items: CustomerResponse 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
def index(conn, _) do defp customer_json(c) do
case CustomerService.Repo.all(CustomerService.Projections.Customer) do %{
nil -> id: c.id,
send_resp(conn, 404, "") customer_type: "individual",
first_name: c.first_name,
customer -> last_name: c.last_name,
json(conn, customer) email: c.email,
end phone: c.phone,
birth_date: c.birth_date,
gender: c.gender,
document_id: c.document_id
}
end end
end end

View File

@@ -1,20 +1,16 @@
defmodule CustomerServiceWeb.ErrorJSON do defmodule CustomerServiceWeb.ErrorJSON do
@moduledoc """ @moduledoc """
This module is invoked by your endpoint in case of errors on JSON requests. 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, def render("404.json", _assigns) do
# you may add your own clauses, such as: %{errors: %{detail: "Not Found"}}
# end
# def render("500.json", _assigns) do
# %{errors: %{detail: "Internal Server Error"}} def render("500.json", _assigns) do
# end %{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 def render(template, _assigns) do
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
end end

View 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

View File

@@ -45,5 +45,6 @@ defmodule CustomerServiceWeb.Endpoint do
plug Plug.MethodOverride plug Plug.MethodOverride
plug Plug.Head plug Plug.Head
plug Plug.Session, @session_options plug Plug.Session, @session_options
plug CORSPlug, origin: ["http://localhost:3000"]
plug CustomerServiceWeb.Router plug CustomerServiceWeb.Router
end end

View File

@@ -1,15 +1,32 @@
defmodule CustomerServiceWeb.Router do defmodule CustomerServiceWeb.Router do
use CustomerServiceWeb, :router use CustomerServiceWeb, :router
alias CustomerServiceWeb.CustomerController
pipeline :api do pipeline :api do
plug CORSPlug, origin: "*" plug :accepts, ["json"]
plug OpenApiSpex.Plug.PutApiSpec, module: CustomerServiceWeb.ApiSpec plug OpenApiSpex.Plug.PutApiSpec, module: CustomerServiceWeb.ApiSpec
end end
get("/health", CustomerServiceWeb.HealthController, :health)
get("/health/ready", CustomerServiceWeb.HealthController, :ready)
scope "/api" do scope "/api" do
pipe_through :api pipe_through :api
resources "/customers", CustomerServiceWeb.Customer, only: [:create, :index, :show]
get "/openapi", OpenApiSpex.Plug.RenderSpec, [] 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
end end

View File

@@ -1,37 +1,140 @@
defmodule CustomerServiceWeb.Schemas do defmodule CustomerServiceWeb.Schemas.Customer do
defmodule CustomerResponse do alias OpenApiSpex.Schema
defmodule PaginationMeta do
require OpenApiSpex require OpenApiSpex
alias OpenApiSpex.Schema
OpenApiSpex.schema(%{ OpenApiSpex.schema(%{
title: "Customer", title: "PaginationMeta",
type: :object, type: :object,
properties: %{ properties: %{
id: %Schema{type: :string, format: :uuid}, total_count: %Schema{type: :integer},
first_name: %Schema{type: :string}, total_pages: %Schema{type: :integer},
last_name: %Schema{type: :string}, current_page: %Schema{type: :integer},
birth_date: %Schema{type: :string, format: :date}, page_size: %Schema{type: :integer},
gender: %Schema{type: :string}, has_next: %Schema{type: :boolean},
email: %Schema{type: :string, format: :email}, has_prev: %Schema{type: :boolean}
phone: %Schema{type: :string}
} }
}) })
end 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 require OpenApiSpex
alias OpenApiSpex.Schema
OpenApiSpex.schema(%{ OpenApiSpex.schema(%{
title: "CreateCustomer", title: "CreateCustomer",
type: :object, type: :object,
required: [:first_name, :last_name],
properties: %{ properties: %{
first_name: %Schema{type: :string}, first_name: %Schema{type: :string},
last_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}, birth_date: %Schema{type: :string, format: :date},
gender: %Schema{type: :string}, 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}, 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 end

View File

@@ -53,7 +53,8 @@ defmodule CustomerService.MixProject do
{:eventstore, "~> 1.4"}, {:eventstore, "~> 1.4"},
{:open_api_spex, "~> 3.20"}, {:open_api_spex, "~> 3.20"},
{:commanded_eventstore_adapter, "~> 1.4"}, {:commanded_eventstore_adapter, "~> 1.4"},
{:cors_plug, "~> 3.0"} {:cors_plug, "~> 3.0"},
{:flop, "~> 0.26"}
] ]
end end

View File

@@ -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": {: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"}, "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"}, "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"}, "fsm": {:hex, :fsm, "0.3.1", "087aa9b02779a84320dc7a2d8464452b5308e29877921b2bde81cdba32a12390", [:mix], [], "hexpm", "fbf0d53f89e9082b326b0b5828b94b4c549ff9d1452bbfd00b4d1ac082208e96"},
"gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"}, "gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "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"}, "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"}, "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"}, "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": {: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"}, "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
View 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
View 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/

View 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
View 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

View File

@@ -10,7 +10,14 @@ defmodule CustomerService.Repo.Migrations.AddCustomerTable do
add :gender, :string add :gender, :string
add :email, :string add :email, :string
add :phone, :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() timestamps()
end end

38
rel/vm.args.eex Normal file
View 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

View File

@@ -2,7 +2,9 @@ defmodule CustomerServiceWeb.ErrorJSONTest do
use CustomerServiceWeb.ConnCase, async: true use CustomerServiceWeb.ConnCase, async: true
test "renders 404" do 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 end
test "renders 500" do test "renders 500" do