fix(share): register calendar_share migration and rebuild old PK schema. Refs EventHub/EventHubBack#73
CI / test (push) Successful in 9m32s
CI / deploy-ift (push) Successful in 4m35s
CI / e2e-ift (push) Successful in 1m40s
CI / deploy-stage (push) Successful in 2m27s
CI / e2e-stage (push) Successful in 4m23s

This commit is contained in:
2026-08-16 10:04:16 +03:00
parent fb927b7f71
commit 1c701a5254
5 changed files with 108 additions and 32 deletions
+6 -2
View File
@@ -148,5 +148,9 @@ map_decide_error(Req, not_pending) ->
handler_utils:send_error(Req, 409, <<"Invite is not pending">>); handler_utils:send_error(Req, 409, <<"Invite is not pending">>);
map_decide_error(Req, expired) -> map_decide_error(Req, expired) ->
handler_utils:send_error(Req, 410, <<"Invite expired">>); handler_utils:send_error(Req, 410, <<"Invite expired">>);
map_decide_error(Req, _) -> map_decide_error(Req, {no_exists, Table}) ->
handler_utils:send_error(Req, 500, <<"Internal server error">>). handler_utils:report_and_send_error(Req, 503,
<<"Schema not ready">>, #{reason => no_exists, table => Table});
map_decide_error(Req, Reason) ->
handler_utils:report_and_send_error(Req, 500,
<<"Internal server error">>, #{reason => Reason}).
+4 -1
View File
@@ -28,7 +28,10 @@
'20260720210000_calendar_follow', '20260720210000_calendar_follow',
'20260722150000_calendar_specialist_id', '20260722150000_calendar_specialist_id',
'20260722190000_specialist_invite', '20260722190000_specialist_invite',
'20260730120000_recurrence_exception_bag' '20260730120000_recurrence_exception_bag',
'20260814193000_waitlist_entry',
'20260815200000_push_subscription',
'20260815220000_calendar_share_invite'
]). ]).
%% ------------------------------ %% ------------------------------
@@ -1,4 +1,6 @@
%% @doc calendar_share: id PK + mirror_to_default; create calendar_share_invite. %% @doc calendar_share: id PK + mirror_to_default; create calendar_share_invite.
%% Old schema PK was calendar_id — mnesia:transform_table cannot change the key,
%% so we rebuild the table (feature was unused until Back#73).
-module('20260815220000_calendar_share_invite'). -module('20260815220000_calendar_share_invite').
-export([up/0, down/0]). -export([up/0, down/0]).
@@ -6,7 +8,7 @@
-include("records.hrl"). -include("records.hrl").
up() -> up() ->
transform_calendar_share(), rebuild_calendar_share_if_needed(),
ensure_index(calendar_share, calendar_id), ensure_index(calendar_share, calendar_id),
ensure_index(calendar_share, user_id), ensure_index(calendar_share, user_id),
ensure_table(calendar_share_invite, record_info(fields, calendar_share_invite)), ensure_table(calendar_share_invite, record_info(fields, calendar_share_invite)),
@@ -21,7 +23,7 @@ down() ->
_ = mnesia:delete_table(calendar_share_invite), _ = mnesia:delete_table(calendar_share_invite),
ok. ok.
transform_calendar_share() -> rebuild_calendar_share_if_needed() ->
case lists:member(calendar_share, mnesia:system_info(tables)) of case lists:member(calendar_share, mnesia:system_info(tables)) of
false -> false ->
ensure_table(calendar_share, record_info(fields, calendar_share)); ensure_table(calendar_share, record_info(fields, calendar_share));
@@ -31,9 +33,29 @@ transform_calendar_share() ->
true -> true ->
ok; ok;
false -> false ->
Fun = fun(Rec) -> Rows = dump_calendar_share_rows(),
case tuple_to_list(Rec) of case mnesia:delete_table(calendar_share) of
[calendar_share, CalId, UserId, Rights] -> {atomic, ok} -> ok;
{aborted, Reason} -> error({delete_calendar_share_failed, Reason})
end,
ensure_table(calendar_share, record_info(fields, calendar_share)),
case mnesia:wait_for_tables([calendar_share], 30000) of
ok -> ok;
{timeout, _} -> error({wait_calendar_share_timeout})
end,
lists:foreach(fun(Rec) -> ok = mnesia:dirty_write(Rec) end, Rows),
ok
end
end.
dump_calendar_share_rows() ->
Raw = try mnesia:dirty_select(calendar_share, [{'_', [], ['$_']}])
catch _:_ -> []
end,
[transform_row(R) || R <- Raw].
transform_row({calendar_share, CalId, UserId, Rights})
when is_binary(CalId), is_binary(UserId) ->
#calendar_share{ #calendar_share{
id = infra_utils:generate_id(16), id = infra_utils:generate_id(16),
calendar_id = CalId, calendar_id = CalId,
@@ -41,7 +63,8 @@ transform_calendar_share() ->
rights = Rights, rights = Rights,
mirror_to_default = false mirror_to_default = false
}; };
[calendar_share, Id, CalId, UserId, Rights] -> transform_row({calendar_share, Id, CalId, UserId, Rights})
when is_binary(Id), is_binary(CalId), is_binary(UserId) ->
#calendar_share{ #calendar_share{
id = Id, id = Id,
calendar_id = CalId, calendar_id = CalId,
@@ -49,17 +72,10 @@ transform_calendar_share() ->
rights = Rights, rights = Rights,
mirror_to_default = false mirror_to_default = false
}; };
_ -> transform_row(#calendar_share{} = Rec) ->
Rec Rec;
end transform_row(Other) ->
end, error({unexpected_calendar_share_row, Other}).
case mnesia:transform_table(calendar_share, Fun,
record_info(fields, calendar_share)) of
{atomic, ok} -> ok;
{aborted, Reason} -> error({transform_calendar_share_failed, Reason})
end
end
end.
ensure_table(Table, Attrs) -> ensure_table(Table, Attrs) ->
case lists:member(Table, mnesia:system_info(tables)) of case lists:member(Table, mnesia:system_info(tables)) of
@@ -0,0 +1,50 @@
-module(calendar_share_migration_tests).
-include_lib("eunit/include/eunit.hrl").
-include("records.hrl").
setup() ->
mnesia:stop(),
mnesia:delete_schema([node()]),
mnesia:create_schema([node()]),
mnesia:start(),
{atomic, ok} = mnesia:create_table(calendar_share, [
{ram_copies, [node()]},
{attributes, [calendar_id, user_id, rights]}
]),
ok.
cleanup(_) ->
catch mnesia:delete_table(calendar_share),
catch mnesia:delete_table(calendar_share_invite),
mnesia:stop(),
mnesia:delete_schema([node()]),
ok.
calendar_share_migration_test_() ->
{foreach, fun setup/0, fun cleanup/1, [
{"transforms old calendar_share to id+mirror", fun test_transform_old_share/0},
{"idempotent when already new schema", fun test_idempotent_new_schema/0}
]}.
test_transform_old_share() ->
%% Pre-#73 row shape: PK = calendar_id (no id / mirror_to_default).
ok = mnesia:dirty_write({calendar_share, <<"cal1">>, <<"u1">>, write}),
?assertEqual([calendar_id, user_id, rights],
mnesia:table_info(calendar_share, attributes)),
ok = '20260815220000_calendar_share_invite':up(),
?assertEqual(record_info(fields, calendar_share),
mnesia:table_info(calendar_share, attributes)),
[Rec] = mnesia:dirty_match_object(#calendar_share{calendar_id = <<"cal1">>, _ = '_'}),
?assertMatch(#calendar_share{user_id = <<"u1">>, rights = write,
mirror_to_default = false}, Rec),
?assert(is_binary(Rec#calendar_share.id)),
?assert(lists:member(calendar_share_invite, mnesia:system_info(tables))),
{ok, Share} = core_calendar_share:upsert(<<"cal2">>, <<"u2">>, admin, true),
?assertEqual(admin, Share#calendar_share.rights),
?assertEqual(true, Share#calendar_share.mirror_to_default).
test_idempotent_new_schema() ->
ok = '20260815220000_calendar_share_invite':up(),
ok = '20260815220000_calendar_share_invite':up(),
?assertEqual(record_info(fields, calendar_share),
mnesia:table_info(calendar_share, attributes)).
+4 -1
View File
@@ -14,7 +14,10 @@
"20260720210000_calendar_follow", "20260720210000_calendar_follow",
"20260722150000_calendar_specialist_id", "20260722150000_calendar_specialist_id",
"20260722190000_specialist_invite", "20260722190000_specialist_invite",
"20260730120000_recurrence_exception_bag" "20260730120000_recurrence_exception_bag",
"20260814193000_waitlist_entry",
"20260815200000_push_subscription",
"20260815220000_calendar_share_invite"
]). ]).
setup() -> setup() ->