src/core/core_calendar_follow.erl View File
| 29 | },
|
| 30 | mnesia:write(Rec),
|
| 31 | {ok, Rec}
|
| 33 | end,
|
| 34 | case mnesia:transaction(F) of
|
| 35 | {atomic, Result} -> Result;
|
| 49 | [#calendar_follow{id = Id}] ->
|
| 50 | mnesia:delete({calendar_follow, Id}),
|
| 51 | ok
|
| 53 | end,
|
| 54 | case mnesia:transaction(F) of
|
| 55 | {atomic, ok} -> ok;
|
| 64 | is_following(UserId, CalendarId) ->
|
| 65 | case mnesia:dirty_match_object(
|
| 66 | #calendar_follow{calendar_id = CalendarId, user_id = UserId, _ = '_'}) of
|
| 76 | list_by_user(UserId) ->
|
| 77 | mnesia:dirty_match_object(#calendar_follow{user_id = UserId, _ = '_'}).
|
| 78 |
|
| 84 | list_by_calendar(CalendarId) ->
|
| 85 | mnesia:dirty_match_object(#calendar_follow{calendar_id = CalendarId, _ = '_'}).
|
| 86 |
|
| 95 | fun(#calendar_follow{id = Id}) ->
|
| 96 | mnesia:delete({calendar_follow, Id})
|
| 97 | end,
|
| 98 | mnesia:match_object(#calendar_follow{calendar_id = CalendarId, _ = '_'})),
|
| 99 | ok
|
| 100 | end,
|
| 101 | case mnesia:transaction(F) of
|
| 102 | {atomic, ok} -> ok;
|
| 110 | find(CalendarId, UserId) ->
|
| 111 | mnesia:match_object(#calendar_follow{calendar_id = CalendarId, user_id = UserId, _ = '_'}). |
src/core/core_event.erl View File
| 59 | },
|
| 60 | F = fun() -> mnesia:write(Event), {ok, Event} end,
|
| 61 | case mnesia:transaction(F) of
|
| 62 | {atomic, Result} -> Result;
|
| 101 | },
|
| 102 | F = fun() -> mnesia:write(Event), {ok, Event} end,
|
| 103 | case mnesia:transaction(F) of
|
| 104 | {atomic, Result} -> Result;
|
| 116 | materialize_occurrence(MasterId, OccurrenceStart, SpecialistId) ->
|
| 117 | case mnesia:dirty_read(event, MasterId) of
|
| 118 | [] ->
|
| 120 | [Master] when Master#event.event_type =:= recurring ->
|
| 121 | Existing = mnesia:dirty_match_object(
|
| 122 | #event{master_id = MasterId, start_time = OccurrenceStart,
|
| 153 | },
|
| 154 | F = fun() -> mnesia:write(Instance), {ok, Instance} end,
|
| 155 | case mnesia:transaction(F) of
|
| 156 | {atomic, Result} -> Result;
|
| 171 | get_by_id(Id) ->
|
| 172 | case mnesia:dirty_read(event, Id) of
|
| 173 | [] -> {error, not_found};
|
| 183 | Match = #event{calendar_id = CalendarId, status = active, is_instance = false, _ = '_'},
|
| 184 | Events = mnesia:dirty_match_object(Match),
|
| 185 | {ok, Events}.
|
| 196 | F = fun() ->
|
| 197 | case mnesia:read(event, Id) of
|
| 198 | [] -> {error, not_found};
|
| 200 | UpdatedEvent = apply_updates(Event, Updates),
|
| 201 | mnesia:write(UpdatedEvent),
|
| 202 | {ok, UpdatedEvent}
|
| 204 | end,
|
| 205 | case mnesia:transaction(F) of
|
| 206 | {atomic, Result} -> Result;
|
| 223 | count_events() ->
|
| 224 | mnesia:table_info(event, size).
|
| 225 |
|
| 232 | Match = #event{status = active, is_instance = false, _ = '_'},
|
| 233 | mnesia:dirty_match_object(Match).
|
| 234 |
|
| 243 | core_stats:with_daily(events_created, From, To, fun() ->
|
| 244 | All = mnesia:dirty_match_object(#event{_ = '_'}),
|
| 245 | Filtered = lists:filter(fun(E) -> E#event.created_at >= From andalso
|
| 263 | core_stats:with_dim(event, type, fun() ->
|
| 264 | Events = mnesia:dirty_match_object(#event{_ = '_'}),
|
| 265 | lists:foldl(fun(#event{event_type = Type}, Acc) ->
|
| 279 | core_stats:with_dim(event, status, fun() ->
|
| 280 | Events = mnesia:dirty_match_object(#event{_ = '_'}),
|
| 281 | lists:foldl(fun(#event{status = Status}, Acc) ->
|
| 297 | {error, _} ->
|
| 298 | Events = mnesia:dirty_match_object(#event{_ = '_'}),
|
| 299 | Sorted = lists:reverse(lists:sort( |
src/core/core_node_metric.erl View File
| 10 | save(Metric) ->
|
| 11 | try mnesia:dirty_write(Metric)
|
| 12 | catch _:_ -> ok
|
| 21 | get_by_timerange(From, To, Node) ->
|
| 22 | All = mnesia:dirty_match_object(#node_metric{node = Node, _ = '_'}),
|
| 23 | Filtered = lists:filter(fun(M) ->
|
| 36 | calendar:datetime_to_gregorian_seconds(Now) - Seconds),
|
| 37 | All = mnesia:dirty_match_object(#node_metric{_ = '_'}),
|
| 38 | lists:foreach(fun(M) ->
|
| 39 | case M#node_metric.timestamp < Threshold of
|
| 40 | true -> mnesia:dirty_delete_object(M);
|
| 41 | false -> ok |
src/core/core_report.erl View File
| 36 | },
|
| 37 | F = fun() -> mnesia:write(Report), {ok, Report} end,
|
| 38 | case mnesia:transaction(F) of
|
| 39 | {atomic, Result} -> Result;
|
| 48 | get_by_id(Id) ->
|
| 49 | case mnesia:dirty_read(report, Id) of
|
| 50 | [] -> {error, not_found};
|
| 59 | list_all() ->
|
| 60 | mnesia:dirty_match_object(#report{_ = '_'}).
|
| 61 |
|
| 68 | Match = #report{reporter_id = ReporterId, _ = '_'},
|
| 69 | mnesia:dirty_match_object(Match).
|
| 70 |
|
| 78 | Match = #report{target_type = TargetType, target_id = TargetId, _ = '_'},
|
| 79 | mnesia:dirty_match_object(Match).
|
| 80 |
|
| 99 | F = fun() ->
|
| 100 | case mnesia:read(report, Id) of
|
| 101 | [] -> {error, not_found};
|
| 107 | },
|
| 108 | mnesia:write(Updated),
|
| 109 | {ok, Updated}
|
| 111 | end,
|
| 112 | case mnesia:transaction(F) of
|
| 113 | {atomic, Result} -> Result;
|
| 122 | count_reports() ->
|
| 123 | mnesia:table_info(report, size).
|
| 124 |
|
| 133 | Match = #report{status = Status, _ = '_'},
|
| 134 | length(mnesia:dirty_match_object(Match))
|
| 135 | end).
|
| 144 | Rows = index_read(report, AdminId, #report.resolved_by, fun() ->
|
| 145 | mnesia:dirty_match_object(#report{resolved_by = AdminId, _ = '_'})
|
| 146 | end),
|
| 159 | Match = #report{status = Status, _ = '_'},
|
| 160 | Reports = mnesia:dirty_match_object(Match),
|
| 161 | Resolved = [R || R <- Reports, R#report.resolved_at /= undefined],
|
| 179 | core_stats:with_daily(reports_created, From, To, fun() ->
|
| 180 | All = mnesia:dirty_match_object(#report{_ = '_'}),
|
| 181 | Filtered = lists:filter(fun(R) -> R#report.created_at >= From andalso
|
| 199 | core_stats:with_dim(report, target_type, fun() ->
|
| 200 | Reports = mnesia:dirty_match_object(#report{_ = '_'}),
|
| 201 | lists:foldl(fun(#report{target_type = Type}, Acc) ->
|
| 215 | core_stats:with_dim(report, status, fun() ->
|
| 216 | Reports = mnesia:dirty_match_object(#report{_ = '_'}),
|
| 217 | lists:foldl(fun(#report{status = Status}, Acc) ->
|
| 235 | {error, _} ->
|
| 236 | Reports = mnesia:dirty_match_object(#report{_ = '_'}),
|
| 237 | Dict = lists:foldl(fun(#report{target_type = Type, target_id = Id}, Acc) ->
|
| 251 | index_read(Table, Key, Pos, FallbackFun) ->
|
| 252 | case catch mnesia:dirty_index_read(Table, Key, Pos) of
|
| 253 | {'EXIT', _} -> FallbackFun(); |
src/core/core_review.erl View File
| 42 | },
|
| 43 | F = fun() -> mnesia:write(Review), {ok, Review} end,
|
| 44 | case mnesia:transaction(F) of
|
| 45 | {atomic, Result} -> Result;
|
| 54 | get_by_id(Id) ->
|
| 55 | case mnesia:dirty_read(review, Id) of
|
| 56 | [] -> {error, not_found};
|
| 68 | status = visible, _ = '_'},
|
| 69 | Reviews = mnesia:dirty_match_object(Match),
|
| 70 | {ok, lists:sort(fun(A, B) -> A#review.created_at >= B#review.created_at end,
|
| 79 | Match = #review{user_id = UserId, _ = '_'},
|
| 80 | Reviews = mnesia:dirty_match_object(Match),
|
| 81 | {ok, Reviews}.
|
| 92 | F = fun() ->
|
| 93 | case mnesia:read(review, Id) of
|
| 94 | [] -> {error, not_found};
|
| 96 | UpdatedReview = apply_updates(Review, Updates),
|
| 97 | mnesia:write(UpdatedReview),
|
| 98 | {ok, UpdatedReview}
|
| 100 | end,
|
| 101 | case mnesia:transaction(F) of
|
| 102 | {atomic, Result} -> Result;
|
| 112 | F = fun() ->
|
| 113 | case mnesia:read(review, Id) of
|
| 114 | [] ->
|
| 116 | [Review] ->
|
| 117 | Votes = mnesia:match_object(#review_vote{review_id = Id, _ = '_'}),
|
| 118 | lists:foreach(fun(#review_vote{id = VoteId}) ->
|
| 119 | mnesia:delete({review_vote, VoteId})
|
| 120 | end, Votes),
|
| 121 | mnesia:delete_object(Review),
|
| 122 | {ok, deleted}
|
| 124 | end,
|
| 125 | case mnesia:transaction(F) of
|
| 126 | {atomic, Result} -> Result;
|
| 157 | status = visible, _ = '_'},
|
| 158 | Reviews = mnesia:dirty_match_object(Match),
|
| 159 | case length(Reviews) of
|
| 174 | target_id = TargetId, _ = '_'},
|
| 175 | case mnesia:dirty_match_object(Match) of
|
| 176 | [] -> false;
|
| 185 | count_reviews() ->
|
| 186 | mnesia:table_info(review, size).
|
| 187 |
|
| 193 | list_all() ->
|
| 194 | mnesia:dirty_match_object(#review{_ = '_'}).
|
| 195 |
|
| 203 | core_stats:with_daily(reviews_created, From, To, fun() ->
|
| 204 | All = mnesia:dirty_match_object(#review{_ = '_'}),
|
| 205 | Filtered = lists:filter(fun(R) -> R#review.created_at >= From andalso
|
| 225 | core_stats:with_dim(review, target_type, fun() ->
|
| 226 | Reviews = mnesia:dirty_match_object(#review{_ = '_'}),
|
| 227 | lists:foldl(fun(#review{target_type = Type}, Acc) ->
|
| 241 | core_stats:with_dim(review, status, fun() ->
|
| 242 | Reviews = mnesia:dirty_match_object(#review{_ = '_'}),
|
| 243 | lists:foldl(fun(#review{status = Status}, Acc) ->
|
| 280 | aggregate_targets_by(Pred, N) ->
|
| 281 | Reviews = mnesia:dirty_match_object(#review{_ = '_'}),
|
| 282 | Filtered = lists:filter(Pred, Reviews), |
src/core/core_review_vote.erl View File
| 20 | F = fun() ->
|
| 21 | case mnesia:read(review, ReviewId) of
|
| 22 | [] ->
|
| 38 | },
|
| 39 | mnesia:write(Vote),
|
| 40 | Updated = bump(Review, Value, 1),
|
| 41 | mnesia:write(Updated),
|
| 42 | {ok, Updated, Value};
|
| 44 | %% тот же голос — идемпотентно
|
| 45 | mnesia:write(Vote#review_vote{updated_at = Now}),
|
| 46 | {ok, Review, Value};
|
| 48 | UpdatedVote = Vote#review_vote{value = Value, updated_at = Now},
|
| 49 | mnesia:write(UpdatedVote),
|
| 50 | Updated0 = bump(Review, OldValue, -1),
|
| 51 | Updated1 = bump(Updated0, Value, 1),
|
| 52 | mnesia:write(Updated1),
|
| 53 | {ok, Updated1, Value}
|
| 56 | end,
|
| 57 | case mnesia:transaction(F) of
|
| 58 | {atomic, Result} -> Result;
|
| 69 | F = fun() ->
|
| 70 | case mnesia:read(review, ReviewId) of
|
| 71 | [] ->
|
| 81 | [#review_vote{value = Value, id = VoteId}] ->
|
| 82 | mnesia:delete({review_vote, VoteId}),
|
| 83 | Updated = bump(Review, Value, -1),
|
| 84 | mnesia:write(Updated),
|
| 85 | {ok, Updated, null}
|
| 88 | end,
|
| 89 | case mnesia:transaction(F) of
|
| 90 | {atomic, Result} -> Result;
|
| 100 | get_by_user_and_review(UserId, ReviewId) ->
|
| 101 | case mnesia:dirty_match_object(
|
| 102 | #review_vote{review_id = ReviewId, user_id = UserId, _ = '_'}) of
|
| 113 | list_by_user(UserId) ->
|
| 114 | mnesia:dirty_match_object(#review_vote{user_id = UserId, _ = '_'}).
|
| 115 |
|
| 121 | list_by_review(ReviewId) ->
|
| 122 | mnesia:dirty_match_object(#review_vote{review_id = ReviewId, _ = '_'}).
|
| 123 |
|
| 132 | lists:foreach(fun(#review_vote{id = Id}) ->
|
| 133 | mnesia:delete({review_vote, Id})
|
| 134 | end, list_by_review_tx(ReviewId)),
|
| 136 | end,
|
| 137 | case mnesia:transaction(F) of
|
| 138 | {atomic, ok} -> ok;
|
| 146 | find_vote(ReviewId, UserId) ->
|
| 147 | mnesia:match_object(#review_vote{review_id = ReviewId, user_id = UserId, _ = '_'}).
|
| 148 |
|
| 149 | list_by_review_tx(ReviewId) ->
|
| 150 | mnesia:match_object(#review_vote{review_id = ReviewId, _ = '_'}).
|
src/core/core_session.erl View File
| 27 | },
|
| 28 | mnesia:dirty_write(Session),
|
| 29 | core_counters:inc(active_sessions),
|
| 41 | validate(Token) ->
|
| 42 | case mnesia:dirty_read({session, Token}) of
|
| 43 | [Session] ->
|
| 46 | % Получаем пользователя по ID из сессии
|
| 47 | case mnesia:dirty_read({user, Session#session.user_id}) of
|
| 48 | [User] -> {ok, Session#session.user_id, User};
|
| 51 | false ->
|
| 52 | mnesia:dirty_delete({session, Token}),
|
| 53 | core_counters:dec(active_sessions),
|
| 64 | delete(Token) ->
|
| 65 | mnesia:dirty_delete({session, Token}),
|
| 66 | core_counters:dec(active_sessions), |
src/core/core_stats.erl View File
src/core/core_subscription.erl View File
| 43 | },
|
| 44 | F = fun() -> mnesia:write(Subscription), {ok, Subscription} end,
|
| 45 | case mnesia:transaction(F) of
|
| 46 | {atomic, Result} -> Result;
|
| 55 | get_by_id(Id) ->
|
| 56 | case mnesia:dirty_read(subscription, Id) of
|
| 57 | [] -> {error, not_found};
|
| 67 | Match = #subscription{user_id = UserId, status = active, _ = '_'},
|
| 68 | case mnesia:dirty_match_object(Match) of
|
| 69 | [] -> {error, not_found};
|
| 79 | Match = #subscription{user_id = UserId, _ = '_'},
|
| 80 | Subscriptions = mnesia:dirty_match_object(Match),
|
| 81 | {ok, lists:sort(fun(A, B) -> A#subscription.created_at >= B#subscription.created_at end,
|
| 90 | Match = #subscription{_ = '_'},
|
| 91 | Subscriptions = mnesia:dirty_match_object(Match),
|
| 92 | {ok, Subscriptions}.
|
| 101 | F = fun() ->
|
| 102 | case mnesia:read(subscription, Id) of
|
| 103 | [] -> {error, not_found};
|
| 108 | },
|
| 109 | mnesia:write(Updated),
|
| 110 | {ok, Updated}
|
| 112 | end,
|
| 113 | case mnesia:transaction(F) of
|
| 114 | {atomic, Result} -> Result;
|
| 125 | Match = #subscription{status = active, _ = '_'},
|
| 126 | ActiveSubscriptions = mnesia:dirty_match_object(Match),
|
| 127 | lists:foreach(fun(Sub) ->
|
| 146 | Match = #calendar{owner_id = UserId, type = commercial, _ = '_'},
|
| 147 | Calendars = mnesia:dirty_match_object(Match),
|
| 148 | lists:foreach(fun(Cal) ->
|
| 215 | Updated = apply_updates(Sub, Updates),
|
| 216 | mnesia:dirty_write(Updated),
|
| 217 | {ok, Updated};
|
| 228 | {ok, _Sub} ->
|
| 229 | mnesia:dirty_delete({subscription, Id}),
|
| 230 | {ok, deleted};
|
| 303 | count_subscriptions() ->
|
| 304 | mnesia:table_info(subscription, size).
|
| 305 |
|
| 313 | core_stats:with_daily(subscriptions_created, From, To, fun() ->
|
| 314 | All = mnesia:dirty_match_object(#subscription{_ = '_'}),
|
| 315 | Filtered = lists:filter(fun(S) -> S#subscription.created_at >= From andalso
|
| 335 | core_stats:with_dim(subscription, plan, fun() ->
|
| 336 | Subs = mnesia:dirty_match_object(#subscription{_ = '_'}),
|
| 337 | lists:foldl(fun(#subscription{plan = Plan}, Acc) ->
|
| 351 | core_stats:with_dim(subscription, status, fun() ->
|
| 352 | Subs = mnesia:dirty_match_object(#subscription{_ = '_'}),
|
| 353 | lists:foldl(fun(#subscription{status = Status}, Acc) ->
|
| 368 | Match = #subscription{trial_used = false, _ = '_'},
|
| 369 | length(mnesia:dirty_match_object(Match))
|
| 370 | end).
|
| 381 | Match = #subscription{status = active, trial_used = true, _ = '_'},
|
| 382 | ActivePaid = mnesia:dirty_match_object(Match),
|
| 383 | lists:filter(fun(#subscription{expires_at = Exp}) -> |
src/core/core_ticket.erl View File
| 50 | },
|
| 51 | F = fun() -> mnesia:write(Ticket), {ok, Ticket} end,
|
| 52 | case mnesia:transaction(F) of
|
| 53 | {atomic, Result} -> Result;
|
| 104 | get_by_id(Id) ->
|
| 105 | case mnesia:dirty_read(ticket, Id) of
|
| 106 | [] -> {error, not_found};
|
| 115 | list_all() ->
|
| 116 | mnesia:dirty_match_object(#ticket{_ = '_'}).
|
| 117 |
|
| 124 | Match = #ticket{reporter_id = UserId, _ = '_'},
|
| 125 | mnesia:dirty_match_object(Match).
|
| 126 |
|
| 134 | F = fun() ->
|
| 135 | case mnesia:read(ticket, Id) of
|
| 136 | [] -> {error, not_found};
|
| 138 | UpdatedTicket = apply_updates(Ticket, Updates),
|
| 139 | mnesia:write(UpdatedTicket),
|
| 140 | {ok, UpdatedTicket}
|
| 142 | end,
|
| 143 | case mnesia:transaction(F) of
|
| 144 | {atomic, Result} -> Result;
|
| 163 | delete(Id) ->
|
| 164 | case mnesia:dirty_read(ticket, Id) of
|
| 165 | [] -> {error, not_found};
|
| 166 | [_] -> mnesia:dirty_delete(ticket, Id), {ok, deleted}
|
| 167 | end.
|
| 211 | count_tickets() ->
|
| 212 | mnesia:table_info(ticket, size).
|
| 213 |
|
| 222 | Match = #ticket{status = Status, _ = '_'},
|
| 223 | length(mnesia:dirty_match_object(Match))
|
| 224 | end).
|
| 262 | length(index_read(ticket, AdminId, #ticket.assigned_to, fun() ->
|
| 263 | mnesia:dirty_match_object(#ticket{assigned_to = AdminId, _ = '_'})
|
| 264 | end));
|
| 266 | Rows = index_read(ticket, AdminId, #ticket.assigned_to, fun() ->
|
| 267 | mnesia:dirty_match_object(#ticket{assigned_to = AdminId, _ = '_'})
|
| 268 | end),
|
| 278 | core_stats:with_daily(tickets_created, From, To, fun() ->
|
| 279 | All = mnesia:dirty_match_object(#ticket{_ = '_'}),
|
| 280 | Filtered = lists:filter(fun(T) -> T#ticket.first_seen >= From andalso
|
| 295 | index_read(Table, Key, Pos, FallbackFun) ->
|
| 296 | case catch mnesia:dirty_index_read(Table, Key, Pos) of
|
| 297 | {'EXIT', _} -> FallbackFun(); |
src/core/core_user.erl View File
| 58 | },
|
| 59 | F = fun() -> mnesia:write(User), {ok, User} end,
|
| 60 | case mnesia:transaction(F) of
|
| 61 | {atomic, Result} -> Result;
|
| 71 | get_by_id(Id) ->
|
| 72 | case mnesia:dirty_read(user, Id) of
|
| 73 | [] -> {error, not_found};
|
| 83 | Match = #user{email = Email, _ = '_'},
|
| 84 | case mnesia:dirty_match_object(Match) of
|
| 85 | [] -> {error, not_found};
|
| 109 | F = fun() ->
|
| 110 | case mnesia:read(user, Id) of
|
| 111 | [] -> {error, not_found};
|
| 113 | UpdatedUser = apply_updates(User, Updates),
|
| 114 | mnesia:write(UpdatedUser),
|
| 115 | {ok, UpdatedUser}
|
| 117 | end,
|
| 118 | case mnesia:transaction(F) of
|
| 119 | {atomic, Result} -> Result;
|
| 132 | Updated = User#user{last_login = calendar:universal_time()},
|
| 133 | mnesia:dirty_write(Updated),
|
| 134 | {ok, Updated};
|
| 148 | updated_at = calendar:universal_time()},
|
| 149 | mnesia:dirty_write(Updated),
|
| 150 | {ok, Updated};
|
| 169 | list_users() ->
|
| 170 | Users = mnesia:dirty_match_object(#user{_ = '_'}),
|
| 171 | ActiveUsers = [U || U <- Users, U#user.status =/= deleted],
|
| 211 | updated_at = calendar:universal_time()},
|
| 212 | mnesia:dirty_write(Updated),
|
| 213 | {ok, Updated};
|
| 227 | updated_at = calendar:universal_time()},
|
| 228 | mnesia:dirty_write(Updated),
|
| 229 | {ok, Updated};
|
| 250 | count_users() ->
|
| 251 | mnesia:table_info(user, size).
|
| 252 |
|
| 258 | list_all() ->
|
| 259 | mnesia:dirty_match_object(#user{_ = '_'}).
|
| 260 |
|
| 269 | core_stats:with_daily(users_created, From, To, fun() ->
|
| 270 | All = mnesia:dirty_match_object(#user{_ = '_'}),
|
| 271 | Filtered = lists:filter(fun(U) -> U#user.created_at >= From andalso
|
| 290 | Match = #user{status = pending, _ = '_'},
|
| 291 | length(mnesia:dirty_match_object(Match))
|
| 292 | end).
|
| 301 | core_stats:with_daily(pending_users_created, From, To, fun() ->
|
| 302 | All = mnesia:dirty_match_object(#user{_ = '_'}),
|
| 303 | Pending = lists:filter(fun(U) -> U#user.status =:= pending end, All),
|
| 323 | core_stats:with_dim(user, role, fun() ->
|
| 324 | Users = mnesia:dirty_match_object(#user{_ = '_'}),
|
| 325 | lists:foldl(fun(#user{role = Role}, Acc) ->
|
| 340 | core_stats:with_dim(user, status, fun() ->
|
| 341 | Users = mnesia:dirty_match_object(#user{_ = '_'}),
|
| 342 | lists:foldl(fun(#user{status = Status}, Acc) ->
|
| 399 | false ->
|
| 400 | case mnesia:dirty_index_read(user, Email, email) of
|
| 401 | [] ->
|
| 422 | },
|
| 423 | ok = mnesia:dirty_write(User),
|
| 424 | {ok, User};
|
| 435 | delete_bot(Id) ->
|
| 436 | case mnesia:dirty_read({user, Id}) of
|
| 437 | [#user{role = bot}] ->
|
| 438 | mnesia:dirty_delete({user, Id}),
|
| 439 | ok; |
src/core/core_verification.erl View File
| 15 | calendar:datetime_to_gregorian_seconds(calendar:universal_time()) + ?TOKEN_LIFETIME_HOURS * 3600),
|
| 16 | mnesia:dirty_write(#verification{token = Token, user_id = UserId, expires_at = Expires}),
|
| 17 | {ok, Token, Expires}.
|
| 25 | verify_token(Token) ->
|
| 26 | case mnesia:dirty_read(verification, Token) of
|
| 27 | [#verification{user_id = UserId, expires_at = Expires}] ->
|
| 58 | delete_token(Token) ->
|
| 59 | mnesia:dirty_delete(verification, Token),
|
| 60 | ok.
|
| 63 | find_token(UserId) ->
|
| 64 | case mnesia:dirty_match_object(#verification{user_id = UserId, _ = '_'}) of
|
| 65 | [#verification{token = Token, expires_at = ExpiresAt} | _] ->
|
| 75 | {error, not_found} ->
|
| 76 | case mnesia:transaction(fun() -> mnesia:read(user, UserId) end) of
|
| 77 | {atomic, [_ | _]} -> true; |
src/eventhub_app.erl View File
| 48 | end,
|
| 49 | ok = infra_mnesia:configure_dump_log(),
|
| 50 | application:ensure_all_started(mnesia),
|
| 51 | ok = infra_mnesia:verify_dump_log(),
|
| 52 | ok = infra_mnesia:init_tables(),
|
| 53 | case infra_mnesia:wait_for_tables() of
|
| 54 | ok -> ok;
|
| 55 | {error, WaitReason} -> error({mnesia_tables_not_ready, WaitReason})
|
| 56 | end, |
src/handlers/admin/admin_handler_node_metrics.erl View File
| 63 | % Остальные узлы кластера
|
| 64 | OtherNodes = mnesia:system_info(db_nodes) -- [node()],
|
| 65 | io:format("[NODE_METRICS] Aggregating metrics from ~p (local) and remote nodes: ~p~n", [node(), OtherNodes]),
|
| 109 | <<"run_queue">> => M#node_metric.run_queue,
|
| 110 | <<"mnesia_commits">> => M#node_metric.mnesia_commits,
|
| 111 | <<"mnesia_failures">> => M#node_metric.mnesia_failures,
|
| 112 | <<"table_sizes">> => M#node_metric.table_sizes,
|
| 165 | <<"run_queue">> => #{type => integer, description => <<"Run queue length (0 means idle)">>},
|
| 166 | <<"mnesia_commits">> => #{type => integer, description => <<"Mnesia transaction commits since boot">>},
|
| 167 | <<"mnesia_failures">> => #{type => integer, description => <<"Mnesia transaction failures since boot">>},
|
| 168 | <<"table_sizes">> => #{type => object, description => <<"Map of Mnesia table names to record counts">>, additionalProperties => #{type => integer}},
|
| 169 | <<"active_sessions">> => #{type => integer, description => <<"Number of active user/admin sessions">>}, |
src/handlers/admin/admin_ws_handler.erl View File
src/handlers/handler_calendar_view.erl View File
| 81 | is_owner(UserId, CalendarId) ->
|
| 82 | case mnesia:dirty_read({calendar, CalendarId}) of
|
| 83 | [#calendar{owner_id = UserId}] -> true;
|
| 127 |
|
| 128 | %% @private Извлекает "горячие" события из Mnesia.
|
| 129 | -spec fetch_hot_events(binary(), integer(), integer()) -> list(#event{}).
|
| 132 | End = {{Year, Month, calendar:last_day_of_the_month(Year, Month)}, {23, 59, 59}},
|
| 133 | mnesia:dirty_select(event, [
|
| 134 | {#event{calendar_id = CalendarId, start_time = '$1', _ = '_'},
|
| 139 | %% @private Извлекает архивные события через RPC на архивный узел.
|
| 140 | %% При недоступности архива возвращает события из основной Mnesia.
|
| 141 | -spec fetch_archive_events(binary(), integer(), integer()) -> list(#event{}). |
src/infra/bot_controller.erl View File
src/infra/cluster_discovery.erl View File
| 38 | %% ------------------------------------------------------------------
|
| 39 | %% @doc Добавляет удалённый узел в Mnesia и реплицирует данные
|
| 40 | %% ------------------------------------------------------------------
|
| 41 | join_and_replicate(Node) ->
|
| 42 | case lists:member(Node, mnesia:system_info(db_nodes)) of
|
| 43 | true ->
|
| 44 | io:format("Node ~p already in Mnesia cluster, skipping~n", [Node]);
|
| 45 | false ->
|
| 46 | io:format("Adding node ~p to Mnesia cluster...~n", [Node]),
|
| 47 | infra_mnesia:add_cluster_nodes([Node])
|
| 48 | end. |
src/infra/infra_mnesia.erl View File
| 1 | %% ===================================================================
|
| 2 | %% EventHub – infra_mnesia (финальная версия с автоочисткой кластера)
|
| 3 | %% ===================================================================
|
| 4 | -module(infra_mnesia).
|
| 5 | -behaviour(gen_server).
|
| 57 | %% is requested while the previous dump still runs.
|
| 58 | %% Must be set BEFORE mnesia starts — change_config/2 does not accept
|
| 59 | %% dump_log_write_threshold (returns {error, dump_log_write_threshold}).
|
| 60 | %% Override: MNESIA_DUMP_LOG_WRITE_THRESHOLD (default 50000; OTP default ~1000).
|
| 61 | -spec configure_dump_log() -> ok.
|
| 62 | configure_dump_log() ->
|
| 63 | Threshold = env_int("MNESIA_DUMP_LOG_WRITE_THRESHOLD", 50000),
|
| 64 | case application:load(mnesia) of
|
| 65 | ok -> ok;
|
| 66 | {error, {already_loaded, mnesia}} -> ok;
|
| 67 | {error, LoadErr} ->
|
| 68 | io:format("Mnesia load before dump_log config failed: ~p~n", [LoadErr])
|
| 69 | end,
|
| 70 | ok = application:set_env(mnesia, dump_log_write_threshold, Threshold),
|
| 71 | io:format("Mnesia dump_log_write_threshold set to ~p (before start)~n", [Threshold]),
|
| 72 | ok.
|
| 73 |
|
| 74 | %% @doc Log active threshold after mnesia:start (sanity check for IFT logs).
|
| 75 | -spec verify_dump_log() -> ok.
|
| 76 | verify_dump_log() ->
|
| 77 | case catch mnesia:system_info(dump_log_write_threshold) of
|
| 78 | N when is_integer(N) ->
|
| 79 | io:format("Mnesia dump_log_write_threshold active: ~p~n", [N]),
|
| 80 | ok;
|
| 81 | Other ->
|
| 82 | io:format("Mnesia dump_log_write_threshold verify: ~p~n", [Other]),
|
| 83 | ok
|
| 116 | % Принудительное создание node_metric на каждом узле
|
| 117 | case mnesia:create_table(node_metric, [
|
| 118 | {disc_copies, [node()]}, % хранить на диске
|
| 126 | % ГАРАНТИРУЕМ, что узел имеет локальную копию node_metric (критично для присоединяющихся узлов)
|
| 127 | case lists:member(node(), mnesia:table_info(node_metric, disc_copies) ++
|
| 128 | mnesia:table_info(node_metric, ram_copies)) of
|
| 129 | false -> mnesia:add_table_copy(node_metric, node(), disc_copies);
|
| 130 | true -> ok
|
| 161 | maybe_recreate_schema() ->
|
| 162 | MnesiaDir = mnesia:system_info(directory),
|
| 163 | case filelib:is_dir(MnesiaDir) of
|
| 164 | false ->
|
| 165 | io:format("Mnesia directory (~s) not found. Creating fresh schema...~n", [MnesiaDir]),
|
| 166 | mnesia:stop(),
|
| 167 | mnesia:delete_schema([node()]),
|
| 168 | mnesia:create_schema([node()]),
|
| 169 | mnesia:start(),
|
| 170 | ok;
|
| 171 | true ->
|
| 172 | io:format("Mnesia directory exists (~s). Reusing existing schema.~n", [MnesiaDir]),
|
| 173 | case mnesia:system_info(is_running) of
|
| 174 | yes -> ok;
|
| 175 | _ -> mnesia:start()
|
| 176 | end
|
| 179 | join_cluster(Nodes) ->
|
| 180 | case mnesia:system_info(is_running) of
|
| 181 | yes -> mnesia:stop();
|
| 182 | no -> ok
|
| 183 | end,
|
| 184 | application:set_env(mnesia, extra_db_nodes, Nodes),
|
| 185 | mnesia:start(),
|
| 186 | ensure_schema_disc(),
|
| 193 | application:set_env(eventhub, extra_db_nodes, Nodes ++ ExtraNodes),
|
| 194 | {ok, _} = mnesia:change_config(extra_db_nodes, Nodes),
|
| 195 | ensure_schema_disc(),
|
| 200 | ensure_schema_disc() ->
|
| 201 | case lists:member(node(), mnesia:table_info(schema, disc_copies)) of
|
| 202 | false ->
|
| 203 | io:format("Changing schema copy to disc...~n"),
|
| 204 | case mnesia:change_table_copy_type(schema, node(), disc_copies) of
|
| 205 | {atomic, ok} -> ok;
|
| 212 | add_local_disc_copy(Tab) ->
|
| 213 | case lists:member(node(), mnesia:table_info(Tab, disc_copies)) of
|
| 214 | false ->
|
| 215 | io:format("Adding local disc copy of table ~p...~n", [Tab]),
|
| 216 | case mnesia:add_table_copy(Tab, node(), disc_copies) of
|
| 217 | {atomic, ok} -> ok;
|
| 225 | add_local_ram_copy(Tab) ->
|
| 226 | case lists:member(node(), mnesia:table_info(Tab, ram_copies)) of
|
| 227 | false ->
|
| 228 | io:format("Adding local ram copy of table ~p...~n", [Tab]),
|
| 229 | case mnesia:add_table_copy(Tab, node(), ram_copies) of
|
| 230 | {atomic, ok} -> ok;
|
| 241 | do_wait_for_tables() ->
|
| 242 | case mnesia:wait_for_tables(?TABLES, ?TABLE_WAIT_TIMEOUT) of
|
| 243 | ok -> ok;
|
| 244 | {timeout, Remaining} ->
|
| 245 | io:format("Mnesia wait_for_tables timeout, repairing ~p~n", [Remaining]),
|
| 246 | lists:foreach(fun(Tab) ->
|
| 252 | end, Remaining),
|
| 253 | case mnesia:wait_for_tables(?TABLES, ?TABLE_WAIT_TIMEOUT) of
|
| 254 | ok -> ok;
|
| 255 | {timeout, Still} ->
|
| 256 | io:format("Mnesia tables still not ready: ~p~n", [Still]),
|
| 257 | {error, {tables_not_ready, Still}}
|
| 261 | wait_for_table(Tab) ->
|
| 262 | case lists:member(Tab, mnesia:system_info(tables)) of
|
| 263 | true -> ok;
|
| 279 | Node =/= node() andalso net_adm:ping(Node) =:= pong
|
| 280 | end, mnesia:system_info(db_nodes)),
|
| 281 | DeadNodes = mnesia:system_info(db_nodes) -- [node() | AliveNodes],
|
| 282 | lists:foreach(fun(Node) ->
|
| 283 | io:format("Removing dead node ~p from Mnesia schema...~n", [Node]),
|
| 284 | lists:foreach(fun(Tab) ->
|
| 285 | case lists:member(Node, mnesia:table_info(Tab, disc_copies)) of
|
| 286 | true -> catch mnesia:del_table_copy(Tab, Node);
|
| 287 | false -> ok
|
| 290 | lists:foreach(fun(Tab) ->
|
| 291 | case lists:member(Node, mnesia:table_info(Tab, ram_copies)) of
|
| 292 | true -> catch mnesia:del_table_copy(Tab, Node);
|
| 293 | false -> ok
|
| 295 | end, ?RAM_TABLES),
|
| 296 | catch mnesia:del_table_copy(schema, Node)
|
| 297 | end, DeadNodes).
|
| 304 | Opts = table_opts(Table),
|
| 305 | case mnesia:create_table(Table, Opts) of
|
| 306 | {atomic, ok} -> ok;
|
| 348 | create_indices() ->
|
| 349 | mnesia:add_table_index(event, calendar_id),
|
| 350 | mnesia:add_table_index(event, title),
|
| 351 | mnesia:add_table_index(event, created_at),
|
| 352 | mnesia:add_table_index(event, start_time),
|
| 353 | mnesia:add_table_index(event, event_type),
|
| 354 | mnesia:add_table_index(event, master_id),
|
| 355 | mnesia:add_table_index(event, specialist_id),
|
| 356 | mnesia:add_table_index(event, status),
|
| 357 | mnesia:add_table_index(booking, event_id),
|
| 358 | mnesia:add_table_index(booking, user_id),
|
| 359 | mnesia:add_table_index(booking, status),
|
| 360 | mnesia:add_table_index(review_vote, review_id),
|
| 361 | mnesia:add_table_index(review_vote, user_id),
|
| 362 | mnesia:add_table_index(calendar, owner_id),
|
| 363 | mnesia:add_table_index(calendar, status),
|
| 364 | mnesia:add_table_index(calendar, short_name),
|
| 365 | mnesia:add_table_index(calendar, category),
|
| 366 | mnesia:add_table_index(calendar_specialist, calendar_id),
|
| 367 | mnesia:add_table_index(calendar_specialist, user_id),
|
| 368 | mnesia:add_table_index(user, nickname),
|
| 369 | mnesia:add_table_index(user, email),
|
| 370 | mnesia:add_table_index(verification, user_id),
|
| 371 | mnesia:add_table_index(notification, user_id),
|
| 372 | mnesia:add_table_index(notification, is_read),
|
| 373 | mnesia:add_table_index(auth_session, family_id),
|
| 374 | mnesia:add_table_index(auth_session, subject_id),
|
| 375 | mnesia:add_table_index(report, resolved_by),
|
| 376 | mnesia:add_table_index(ticket, assigned_to),
|
| 377 | ok. |