This commit is contained in:
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
|
||||
Reference in New Issue
Block a user