Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
aa31919
in midst of getting migration script up to date
zcesur Feb 7, 2025
c9a4b53
add scripts to track progress
zcesur Feb 7, 2025
b24a15a
use cached http method during migration
zcesur Feb 7, 2025
6a24ac0
in midst of getting migration script up to date
zcesur Feb 7, 2025
0d32708
update analyze_progress script
zcesur Feb 7, 2025
2600a66
in midst of getting migration script up to date
zcesur Feb 8, 2025
0b98041
add transform for BountyCharge
zcesur Feb 8, 2025
d7f2cd6
add transform for Claim
zcesur Feb 8, 2025
5151a09
add transform for GithubInstallation
zcesur Feb 8, 2025
a672b9f
add progress yamls
zcesur Feb 8, 2025
cae3734
add transform for StripeAccount
zcesur Feb 8, 2025
85a62db
add transform for StripeCustomer
zcesur Feb 8, 2025
dcb6a95
add missing aliases
zcesur Feb 8, 2025
119f40d
add transform for StripePaymentMethod
zcesur Feb 8, 2025
7a34077
skip OrgBalanceTransaction
zcesur Feb 8, 2025
6e12eb2
add transform for OrgMember
zcesur Feb 8, 2025
8eb41fb
add transform for Attempt
zcesur Feb 8, 2025
6138f58
update v2 progress
zcesur Feb 8, 2025
9298501
update progress formatting
zcesur Feb 8, 2025
5d841a3
add transform for Account
zcesur Feb 8, 2025
be26168
derive table mappings from schema mappings
zcesur Feb 8, 2025
1609c7b
allow duplicate keys to handle multi migrations
zcesur Feb 8, 2025
3efc3ea
add transform for {"BountyTransfer", Tip}
zcesur Feb 8, 2025
97db615
track tip transfers accurately
zcesur Feb 8, 2025
234589a
remove obsolete function
zcesur Feb 8, 2025
4e775c7
add transform for {"Bounty", CommandResponse}
zcesur Feb 8, 2025
3f1dc74
add missing transaction data
zcesur Feb 8, 2025
a7dba3f
add missing claim fields
zcesur Feb 8, 2025
24630db
skip bonuses for now
zcesur Feb 8, 2025
8e7b2ea
update progress
zcesur Feb 8, 2025
f01a3b5
fix miscellaneous issues
zcesur Feb 8, 2025
37e08b0
fix miscellaneous issues
zcesur Feb 8, 2025
62975d3
fix more issues
zcesur Feb 8, 2025
42719c4
fix more issues
zcesur Feb 8, 2025
5307c56
speed up retrievals by building indexes upfront
zcesur Feb 8, 2025
2f86af7
fix more issues
zcesur Feb 8, 2025
b4388a4
derive backfilled_tables from schema_mappings
zcesur Feb 8, 2025
ee07152
in midst of merging some user records
zcesur Feb 9, 2025
d5c40c6
merge related user records
zcesur Feb 9, 2025
914b91e
remove unused indexes
zcesur Feb 9, 2025
324b134
enforce unique result for each index
zcesur Feb 9, 2025
d1680a3
remove nullish keys in indexes
zcesur Feb 9, 2025
ed1f362
more fixes
zcesur Feb 10, 2025
6828afb
add sanity checks
zcesur Feb 10, 2025
13e9f89
migrate bounty statuses
zcesur Feb 10, 2025
5b67cc3
def money_negate op
zcesur Feb 13, 2025
7e8cae6
migrate org balance transactions
zcesur Feb 13, 2025
d361ded
update claim status
zcesur Feb 14, 2025
42b83ca
update bounty status
zcesur Feb 14, 2025
320c25f
update sanity checks
zcesur Feb 14, 2025
10f78b0
update migration script
zcesur Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions lib/algora/admin/migration.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
defmodule Algora.Admin.Migration do
@moduledoc false
import Ecto.Query

alias Algora.Accounts.User
alias Algora.Payments.Transaction
alias Algora.Repo

@balances_file ".local/db/balance-2025-02-13.json"

def get_actual_balances(type) do
with {:ok, content} <- File.read(@balances_file),
{:ok, data} <- Jason.decode(content) do
Enum.map(data[Atom.to_string(type)], fn %{"provider_login" => login, "balance" => balance} ->
%{
provider_login: login,
balance: Money.new!(:USD, Decimal.new(balance))
}
end)
else
error -> raise "Failed to load balances: #{inspect(error)}"
end
end

def get_balances(type) do
get_balances()
|> Enum.filter(&(&1.type == type))
|> Enum.reject(&(&1.provider_login in ["algora-io", "Uber4Coding"]))
end

def get_balances do
user_txs =
from t in Transaction,
group_by: [t.user_id, t.type],
select: %{
user_id: t.user_id,
type: t.type,
net_amount: sum(t.net_amount)
}

user_balances =
from ut in subquery(user_txs),
group_by: ut.user_id,
select: %{
user_id: ut.user_id,
balance:
sum(
fragment(
"""
CASE
WHEN ? = 'credit' THEN ?
WHEN ? = 'debit' THEN -?
WHEN ? = 'charge' THEN ?
WHEN ? = 'transfer' THEN -?
ELSE ('USD', 0)::money_with_currency
END
""",
ut.type,
ut.net_amount,
ut.type,
ut.net_amount,
ut.type,
ut.net_amount,
ut.type,
ut.net_amount
)
)
}

query =
from ub in subquery(user_balances),
join: u in User,
on: u.id == ub.user_id,
where: ub.balance != fragment("('USD', 0)::money_with_currency"),
order_by: [desc: u.type, desc: ub.balance, asc: u.provider_login],
select: %{
type: u.type,
provider_login: u.provider_login,
balance: ub.balance
}

query
|> Repo.all()
|> Enum.map(fn
user ->
{currency, amount} = user.balance
%{user | balance: Money.new!(currency, amount)}
end)
end

def diff_balances do
diff_balances(:individual) ++ diff_balances(:organization)
end

def diff_balances(type) do
actual = Enum.map(get_actual_balances(type), &Map.take(&1, [:provider_login, :balance]))
current = Enum.map(get_balances(type), &Map.take(&1, [:provider_login, :balance]))

actual_map = Map.new(actual, &{&1.provider_login, &1.balance})
current_map = Map.new(current, &{&1.provider_login, &1.balance})

all_logins =
MapSet.union(
MapSet.new(Map.keys(actual_map)),
MapSet.new(Map.keys(current_map))
)

differences =
Enum.reduce(all_logins, [], fn login, acc ->
actual_balance = Map.get(actual_map, login)
current_balance = Map.get(current_map, login)

cond do
actual_balance == current_balance ->
acc

actual_balance == nil ->
[{:extra_in_current, login, current_balance} | acc]

current_balance == nil ->
[{:missing_in_current, login, actual_balance} | acc]

true ->
[{:different, login, actual_balance, current_balance} | acc]
end
end)

Enum.sort_by(differences, &elem(&1, 1))
end
end
3 changes: 2 additions & 1 deletion lib/algora/bounties/schemas/bounty.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Algora.Bounties.Bounty do
typed_schema "bounties" do
field :amount, Algora.Types.Money
field :status, Ecto.Enum, values: [:open, :cancelled, :paid]
field :number, :integer, default: 0

belongs_to :ticket, Algora.Workspace.Ticket
belongs_to :owner, User
Expand All @@ -33,7 +34,7 @@ defmodule Algora.Bounties.Bounty do
|> foreign_key_constraint(:ticket)
|> foreign_key_constraint(:owner)
|> foreign_key_constraint(:creator)
|> unique_constraint([:ticket_id, :owner_id])
|> unique_constraint([:ticket_id, :owner_id, :number])
|> Algora.Validations.validate_money_positive(:amount)
end

Expand Down
28 changes: 14 additions & 14 deletions lib/algora/integrations/github/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,24 @@ defmodule Algora.Github.Client do

@type token :: String.t()

# TODO: move to a separate module and use only for data migration between databases
def http_cached(host, method, path, headers, body, opts \\ []) do
cache_path = ".local/github/#{path}.json"

with :error <- read_from_cache(cache_path),
{:ok, response_body} <- do_http_request(host, method, path, headers, body, opts) do
write_to_cache(cache_path, response_body)
{:ok, response_body}
def http(host, method, path, headers, body, opts \\ []) do
# TODO: remove after migration
if System.get_env("MIGRATION", "false") == "true" do
cache_path = ".local/github/#{path}.json"

with :error <- read_from_cache(cache_path),
{:ok, response_body} <- do_http_request(host, method, path, headers, body, opts) do
write_to_cache(cache_path, response_body)
{:ok, response_body}
else
{:ok, cached_data} -> {:ok, cached_data}
{:error, reason} -> {:error, reason}
end
else
{:ok, cached_data} -> {:ok, cached_data}
{:error, reason} -> {:error, reason}
do_http_request(host, method, path, headers, body, opts)
end
end

def http(host, method, path, headers, body, opts \\ []) do
do_http_request(host, method, path, headers, body, opts)
end

defp do_http_request(host, method, path, headers, body, opts) do
url = "https://#{host}#{path}"
headers = [{"Content-Type", "application/json"} | headers]
Expand Down
4 changes: 2 additions & 2 deletions lib/algora/payments/schemas/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Algora.Payments.Account do
typed_schema "accounts" do
field :provider, :string, null: false
field :provider_id, :string, null: false
field :provider_meta, :map, null: false
field :provider_meta, :map

field :name, :string
field :details_submitted, :boolean, default: false, null: false
Expand All @@ -19,7 +19,7 @@ defmodule Algora.Payments.Account do
field :payout_speed, :integer
field :default_currency, :string
field :service_agreement, :string
field :country, :string, null: false
field :country, :string
field :type, Ecto.Enum, values: [:standard, :express], null: false
field :stale, :boolean, default: false, null: false

Expand Down
2 changes: 1 addition & 1 deletion lib/algora/workspace/schemas/command_response.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Algora.Workspace.CommandResponse do

typed_schema "command_responses" do
field :provider, :string, null: false
field :provider_meta, :map, null: false
field :provider_meta, :map
field :provider_command_id, :string
field :provider_response_id, :string, null: false
field :command_source, Ecto.Enum, values: [:ticket, :comment], null: false
Expand Down
4 changes: 2 additions & 2 deletions lib/algora/workspace/schemas/installation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ defmodule Algora.Workspace.Installation do
typed_schema "installations" do
field :provider, :string, null: false
field :provider_id, :string, null: false
field :provider_meta, :map, null: false
field :provider_user_id, :string, null: false
field :provider_meta, :map
field :provider_user_id, :string

field :avatar_url, :string
field :repository_selection, :string
Expand Down
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ defmodule Algora.MixProject do
# monitoring, logging
{:appsignal_phoenix, "~> 2.6"},
{:logfmt_ex, "~> 0.4"},
{:oban_live_dashboard, "~> 0.1.0"}
{:oban_live_dashboard, "~> 0.1.0"},
# TODO: delete after migration
{:yaml_elixir, "~> 2.9"}
]
end

Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,6 @@
"uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], [], "hexpm", "e3bc81816c98502c36498b9b2f239b89c71ce5eadfff7ceb2d6c0a2e6ae2ea0c"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"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"},
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
"yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule Algora.Repo.Migrations.MakeProviderMetaNullable do
use Ecto.Migration

def change do
alter table(:command_responses) do
modify :provider_meta, :map, null: true
end

alter table(:installations) do
modify :provider_meta, :map, null: true
modify :provider_user_id, :string, null: true
end

alter table(:customers) do
modify :provider_meta, :map, null: true
end

alter table(:payment_methods) do
modify :provider_meta, :map, null: true
end

alter table(:accounts) do
modify :provider_meta, :map, null: true
modify :country, :string, null: true
end

alter table(:identities) do
modify :provider_meta, :map, null: true
modify :provider_login, :string, null: true
end

alter table(:claims) do
modify :source_id, :string, null: true
end

alter table(:tips) do
modify :creator_id, :string, null: true
end

alter table(:bounties) do
add :number, :integer, null: false, default: 0
end

drop unique_index(:bounties, [:ticket_id, :owner_id])
create unique_index(:bounties, [:ticket_id, :owner_id, :number])

drop unique_index(:command_responses, [:ticket_id, :command_type])
create unique_index(:command_responses, [:provider, :provider_command_id])
end
end
38 changes: 38 additions & 0 deletions priv/repo/migrations/20250213155854_define_negate_operator.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Algora.Repo.Migrations.DefineNegateOperator do
use Ecto.Migration

def up do
execute("""
CREATE OR REPLACE FUNCTION money_negate(money_1 money_with_currency)
RETURNS money_with_currency
IMMUTABLE
STRICT
LANGUAGE plpgsql
AS $$
DECLARE
currency varchar;
addition numeric;
BEGIN
currency := currency_code(money_1);
addition := amount(money_1) * -1;
return row(currency, addition);
END;
$$;
""")
|> Money.Migration.adjust_for_type(repo())

execute("""
CREATE OPERATOR - (
rightarg = money_with_currency,
procedure = money_negate
);
""")
|> Money.Migration.adjust_for_type(repo())
end

def down do
execute("DROP OPERATOR IF EXISTS - (none, money_with_currency);")

execute("DROP FUNCTION IF EXISTS money_negate(money_with_currency);")
end
end
Loading