Skip to content

Commit a275eb9

Browse files
authored
chore: migration prep (#49)
1 parent a0e7a2d commit a275eb9

File tree

15 files changed

+2318
-243
lines changed

15 files changed

+2318
-243
lines changed

lib/algora/admin/migration.ex

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
defmodule Algora.Admin.Migration do
2+
@moduledoc false
3+
import Ecto.Query
4+
5+
alias Algora.Accounts.User
6+
alias Algora.Payments.Transaction
7+
alias Algora.Repo
8+
9+
@balances_file ".local/db/balance-2025-02-13.json"
10+
11+
def get_actual_balances(type) do
12+
with {:ok, content} <- File.read(@balances_file),
13+
{:ok, data} <- Jason.decode(content) do
14+
Enum.map(data[Atom.to_string(type)], fn %{"provider_login" => login, "balance" => balance} ->
15+
%{
16+
provider_login: login,
17+
balance: Money.new!(:USD, Decimal.new(balance))
18+
}
19+
end)
20+
else
21+
error -> raise "Failed to load balances: #{inspect(error)}"
22+
end
23+
end
24+
25+
def get_balances(type) do
26+
get_balances()
27+
|> Enum.filter(&(&1.type == type))
28+
|> Enum.reject(&(&1.provider_login in ["algora-io", "Uber4Coding"]))
29+
end
30+
31+
def get_balances do
32+
user_txs =
33+
from t in Transaction,
34+
group_by: [t.user_id, t.type],
35+
select: %{
36+
user_id: t.user_id,
37+
type: t.type,
38+
net_amount: sum(t.net_amount)
39+
}
40+
41+
user_balances =
42+
from ut in subquery(user_txs),
43+
group_by: ut.user_id,
44+
select: %{
45+
user_id: ut.user_id,
46+
balance:
47+
sum(
48+
fragment(
49+
"""
50+
CASE
51+
WHEN ? = 'credit' THEN ?
52+
WHEN ? = 'debit' THEN -?
53+
WHEN ? = 'charge' THEN ?
54+
WHEN ? = 'transfer' THEN -?
55+
ELSE ('USD', 0)::money_with_currency
56+
END
57+
""",
58+
ut.type,
59+
ut.net_amount,
60+
ut.type,
61+
ut.net_amount,
62+
ut.type,
63+
ut.net_amount,
64+
ut.type,
65+
ut.net_amount
66+
)
67+
)
68+
}
69+
70+
query =
71+
from ub in subquery(user_balances),
72+
join: u in User,
73+
on: u.id == ub.user_id,
74+
where: ub.balance != fragment("('USD', 0)::money_with_currency"),
75+
order_by: [desc: u.type, desc: ub.balance, asc: u.provider_login],
76+
select: %{
77+
type: u.type,
78+
provider_login: u.provider_login,
79+
balance: ub.balance
80+
}
81+
82+
query
83+
|> Repo.all()
84+
|> Enum.map(fn
85+
user ->
86+
{currency, amount} = user.balance
87+
%{user | balance: Money.new!(currency, amount)}
88+
end)
89+
end
90+
91+
def diff_balances do
92+
diff_balances(:individual) ++ diff_balances(:organization)
93+
end
94+
95+
def diff_balances(type) do
96+
actual = Enum.map(get_actual_balances(type), &Map.take(&1, [:provider_login, :balance]))
97+
current = Enum.map(get_balances(type), &Map.take(&1, [:provider_login, :balance]))
98+
99+
actual_map = Map.new(actual, &{&1.provider_login, &1.balance})
100+
current_map = Map.new(current, &{&1.provider_login, &1.balance})
101+
102+
all_logins =
103+
MapSet.union(
104+
MapSet.new(Map.keys(actual_map)),
105+
MapSet.new(Map.keys(current_map))
106+
)
107+
108+
differences =
109+
Enum.reduce(all_logins, [], fn login, acc ->
110+
actual_balance = Map.get(actual_map, login)
111+
current_balance = Map.get(current_map, login)
112+
113+
cond do
114+
actual_balance == current_balance ->
115+
acc
116+
117+
actual_balance == nil ->
118+
[{:extra_in_current, login, current_balance} | acc]
119+
120+
current_balance == nil ->
121+
[{:missing_in_current, login, actual_balance} | acc]
122+
123+
true ->
124+
[{:different, login, actual_balance, current_balance} | acc]
125+
end
126+
end)
127+
128+
Enum.sort_by(differences, &elem(&1, 1))
129+
end
130+
end

lib/algora/bounties/schemas/bounty.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule Algora.Bounties.Bounty do
99
typed_schema "bounties" do
1010
field :amount, Algora.Types.Money
1111
field :status, Ecto.Enum, values: [:open, :cancelled, :paid]
12+
field :number, :integer, default: 0
1213

1314
belongs_to :ticket, Algora.Workspace.Ticket
1415
belongs_to :owner, User
@@ -33,7 +34,7 @@ defmodule Algora.Bounties.Bounty do
3334
|> foreign_key_constraint(:ticket)
3435
|> foreign_key_constraint(:owner)
3536
|> foreign_key_constraint(:creator)
36-
|> unique_constraint([:ticket_id, :owner_id])
37+
|> unique_constraint([:ticket_id, :owner_id, :number])
3738
|> Algora.Validations.validate_money_positive(:amount)
3839
end
3940

lib/algora/integrations/github/client.ex

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@ defmodule Algora.Github.Client do
88

99
@type token :: String.t()
1010

11-
# TODO: move to a separate module and use only for data migration between databases
12-
def http_cached(host, method, path, headers, body, opts \\ []) do
13-
cache_path = ".local/github/#{path}.json"
14-
15-
with :error <- read_from_cache(cache_path),
16-
{:ok, response_body} <- do_http_request(host, method, path, headers, body, opts) do
17-
write_to_cache(cache_path, response_body)
18-
{:ok, response_body}
11+
def http(host, method, path, headers, body, opts \\ []) do
12+
# TODO: remove after migration
13+
if System.get_env("MIGRATION", "false") == "true" do
14+
cache_path = ".local/github/#{path}.json"
15+
16+
with :error <- read_from_cache(cache_path),
17+
{:ok, response_body} <- do_http_request(host, method, path, headers, body, opts) do
18+
write_to_cache(cache_path, response_body)
19+
{:ok, response_body}
20+
else
21+
{:ok, cached_data} -> {:ok, cached_data}
22+
{:error, reason} -> {:error, reason}
23+
end
1924
else
20-
{:ok, cached_data} -> {:ok, cached_data}
21-
{:error, reason} -> {:error, reason}
25+
do_http_request(host, method, path, headers, body, opts)
2226
end
2327
end
2428

25-
def http(host, method, path, headers, body, opts \\ []) do
26-
do_http_request(host, method, path, headers, body, opts)
27-
end
28-
2929
defp do_http_request(host, method, path, headers, body, opts) do
3030
url = "https://#{host}#{path}"
3131
headers = [{"Content-Type", "application/json"} | headers]

lib/algora/payments/schemas/account.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule Algora.Payments.Account do
99
typed_schema "accounts" do
1010
field :provider, :string, null: false
1111
field :provider_id, :string, null: false
12-
field :provider_meta, :map, null: false
12+
field :provider_meta, :map
1313

1414
field :name, :string
1515
field :details_submitted, :boolean, default: false, null: false
@@ -19,7 +19,7 @@ defmodule Algora.Payments.Account do
1919
field :payout_speed, :integer
2020
field :default_currency, :string
2121
field :service_agreement, :string
22-
field :country, :string, null: false
22+
field :country, :string
2323
field :type, Ecto.Enum, values: [:standard, :express], null: false
2424
field :stale, :boolean, default: false, null: false
2525

lib/algora/workspace/schemas/command_response.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule Algora.Workspace.CommandResponse do
77

88
typed_schema "command_responses" do
99
field :provider, :string, null: false
10-
field :provider_meta, :map, null: false
10+
field :provider_meta, :map
1111
field :provider_command_id, :string
1212
field :provider_response_id, :string, null: false
1313
field :command_source, Ecto.Enum, values: [:ticket, :comment], null: false

lib/algora/workspace/schemas/installation.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ defmodule Algora.Workspace.Installation do
99
typed_schema "installations" do
1010
field :provider, :string, null: false
1111
field :provider_id, :string, null: false
12-
field :provider_meta, :map, null: false
13-
field :provider_user_id, :string, null: false
12+
field :provider_meta, :map
13+
field :provider_user_id, :string
1414

1515
field :avatar_url, :string
1616
field :repository_selection, :string

mix.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ defmodule Algora.MixProject do
9494
# monitoring, logging
9595
{:appsignal_phoenix, "~> 2.6"},
9696
{:logfmt_ex, "~> 0.4"},
97-
{:oban_live_dashboard, "~> 0.1.0"}
97+
{:oban_live_dashboard, "~> 0.1.0"},
98+
# TODO: delete after migration
99+
{:yaml_elixir, "~> 2.9"}
98100
]
99101
end
100102

mix.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,6 @@
8888
"uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], [], "hexpm", "e3bc81816c98502c36498b9b2f239b89c71ce5eadfff7ceb2d6c0a2e6ae2ea0c"},
8989
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
9090
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
91+
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
92+
"yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
9193
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
defmodule Algora.Repo.Migrations.MakeProviderMetaNullable do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:command_responses) do
6+
modify :provider_meta, :map, null: true
7+
end
8+
9+
alter table(:installations) do
10+
modify :provider_meta, :map, null: true
11+
modify :provider_user_id, :string, null: true
12+
end
13+
14+
alter table(:customers) do
15+
modify :provider_meta, :map, null: true
16+
end
17+
18+
alter table(:payment_methods) do
19+
modify :provider_meta, :map, null: true
20+
end
21+
22+
alter table(:accounts) do
23+
modify :provider_meta, :map, null: true
24+
modify :country, :string, null: true
25+
end
26+
27+
alter table(:identities) do
28+
modify :provider_meta, :map, null: true
29+
modify :provider_login, :string, null: true
30+
end
31+
32+
alter table(:claims) do
33+
modify :source_id, :string, null: true
34+
end
35+
36+
alter table(:tips) do
37+
modify :creator_id, :string, null: true
38+
end
39+
40+
alter table(:bounties) do
41+
add :number, :integer, null: false, default: 0
42+
end
43+
44+
drop unique_index(:bounties, [:ticket_id, :owner_id])
45+
create unique_index(:bounties, [:ticket_id, :owner_id, :number])
46+
47+
drop unique_index(:command_responses, [:ticket_id, :command_type])
48+
create unique_index(:command_responses, [:provider, :provider_command_id])
49+
end
50+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
defmodule Algora.Repo.Migrations.DefineNegateOperator do
2+
use Ecto.Migration
3+
4+
def up do
5+
execute("""
6+
CREATE OR REPLACE FUNCTION money_negate(money_1 money_with_currency)
7+
RETURNS money_with_currency
8+
IMMUTABLE
9+
STRICT
10+
LANGUAGE plpgsql
11+
AS $$
12+
DECLARE
13+
currency varchar;
14+
addition numeric;
15+
BEGIN
16+
currency := currency_code(money_1);
17+
addition := amount(money_1) * -1;
18+
return row(currency, addition);
19+
END;
20+
$$;
21+
""")
22+
|> Money.Migration.adjust_for_type(repo())
23+
24+
execute("""
25+
CREATE OPERATOR - (
26+
rightarg = money_with_currency,
27+
procedure = money_negate
28+
);
29+
""")
30+
|> Money.Migration.adjust_for_type(repo())
31+
end
32+
33+
def down do
34+
execute("DROP OPERATOR IF EXISTS - (none, money_with_currency);")
35+
36+
execute("DROP FUNCTION IF EXISTS money_negate(money_with_currency);")
37+
end
38+
end

0 commit comments

Comments
 (0)