Skip to content

Commit

Permalink
WHISTLE-42: optionall reconcile numbers on each callflow entry
Browse files Browse the repository at this point in the history
  • Loading branch information
k-anderson committed Mar 2, 2012
1 parent 6cbf477 commit 13eb493
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 41 deletions.
44 changes: 29 additions & 15 deletions lib/whistle_number_manager-1.0.0/src/wh_number_manager.erl
Expand Up @@ -261,17 +261,12 @@ delete_attachment(Number, AccountId, Name) ->
-spec reconcile_number/2 :: (ne_binary(), ne_binary()) -> {ok, wh_json:json_object()} |
{error, term()}.
reconcile_number(Number, AccountId) ->
Regex = whapps_config:get_binary(?WNM_CONFIG_CAT, <<"reconcile_regex">>, <<"^\\+{0,1}1{0,1}(\\d{10})$">>),
Num = wnm_util:normalize_number(Number),
?LOG("attempting to reconcile number '~s' with account '~s'", [Num, AccountId]),
try
case re:run(Num, Regex) of
nomatch ->
?LOG("number '~s' is not reconcilable", [Num]),
throw(not_reconcilable);
_ ->
?LOG("number '~s' can be reconciled, proceeding", [Num]),
ok
case wnm_util:is_reconcilable(Num) of
false -> throw(not_reconcilable);
true -> ok
end,
JObj = case store_discovery(Num, <<"wnm_local">>, wh_json:new(), {<<"reserved">>, AccountId}) of
{ok, J1} -> J1;
Expand Down Expand Up @@ -303,10 +298,14 @@ reserve_number(Number, AccountId) ->

reserve_number(Number, AccountId, PublicFields) ->
Num = wnm_util:normalize_number(Number),
case store_discovery(Num, <<"wnm_local">>, wh_json:new(), {<<"reserved">>, AccountId}, PublicFields) of
{error, {conflict, _}} -> {error, conflict};
{ok, JObj} -> {ok, wh_json:public_fields(JObj)};
Else -> Else
case wnm_util:is_reconcilable(Num) of
false -> {error, not_reconcilable};
true ->
case store_discovery(Num, <<"wnm_local">>, wh_json:new(), {<<"reserved">>, AccountId}, PublicFields) of
{error, {conflict, _}} -> {error, conflict};
{ok, JObj} -> {ok, wh_json:public_fields(JObj)};
Else -> Else
end
end.

%%--------------------------------------------------------------------
Expand All @@ -326,6 +325,10 @@ assign_number_to_account(Number, AccountId, PublicFields) ->
Num = wnm_util:normalize_number(Number),
Db = wnm_util:number_to_db_name(Num),
try
case wnm_util:is_reconcilable(Num) of
false -> throw(not_reconcilable);
true -> ok
end,
JObj1 = case couch_mgr:open_doc(Db, Num) of
{error, R1} ->
?LOG("failed to open number DB: ~p", [R1]),
Expand Down Expand Up @@ -519,6 +522,11 @@ release_number(Number, AccountId) ->
Num = wnm_util:normalize_number(Number),
Db = wnm_util:number_to_db_name(Num),
try
case wnm_util:is_reconcilable(Num) of
false -> throw(not_reconcilable);
true -> ok
end,
ReleaseState = whapps_config:get_binary(?WNM_CONFIG_CAT, <<"released_state">>, <<"available">>),
JObj1 = case couch_mgr:open_doc(Db, Num) of
{error, R1} ->
?LOG("failed to open number DB: ~p", [R1]),
Expand Down Expand Up @@ -556,7 +564,7 @@ release_number(Number, AccountId) ->
{ok, Data2} ->
Data2
end,
Updaters = [fun(J) -> wh_json:set_value(<<"pvt_number_state">>, <<"released">>, J) end
Updaters = [fun(J) -> wh_json:set_value(<<"pvt_number_state">>, ReleaseState, J) end
,fun(J) -> wh_json:set_value(<<"pvt_module_data">>, NewModuleData, J) end
,fun(J) -> wh_json:set_value(<<"pvt_modified">>, wh_util:current_tstamp(), J) end
,fun(J) -> wh_json:delete_key(<<"pvt_assigned_to">>, J) end
Expand Down Expand Up @@ -732,7 +740,8 @@ update_numbers_on_account(Number, State, AccountId) ->
Migrate = wh_json:get_value(<<"pvt_wnm_numbers">>, JObj, []),
Updated = lists:foldr(fun(<<"numbers">>, J) when Migrate =/= [] ->
N = wh_json:get_value(<<"pvt_wnm_in_service">>, J, []),
wh_json:set_value(<<"pvt_wnm_in_service">>, N ++ Migrate, J);
wh_json:set_value(<<"pvt_wnm_in_service">>, N ++ Migrate,
wh_json:delete_key(<<"pvt_wnm_numbers">>, J));
(<<"numbers">>, J) when Migrate =:= [] ->
wh_json:delete_key(<<"pvt_wnm_numbers">>, J);
(S, J) when S =:= State ->
Expand All @@ -744,7 +753,12 @@ update_numbers_on_account(Number, State, AccountId) ->
N = wh_json:get_value(<<"pvt_wnm_", S/binary>>, JObj, []),
wh_json:set_value(<<"pvt_wnm_", S/binary>>, lists:delete(Number,N), J)
end, JObj, [<<"numbers">>|?WNM_NUMBER_STATUS]),
case couch_mgr:save_doc(Db, Updated) of
Cleaned = lists:foldr(fun(S, J) ->
Nums = wh_json:get_value(<<"pvt_wnm_", S/binary>>, J, []),
Clean = ordsets:to_list(ordsets:from_list(Nums)),
wh_json:set_value(<<"pvt_wnm_", S/binary>>, Clean, J)
end, Updated, ?WNM_NUMBER_STATUS),
case couch_mgr:save_doc(Db, Cleaned) of
{ok, AccountDef} ->
?LOG("updated the account definition"),
couch_mgr:ensure_saved(?WH_ACCOUNTS_DB, AccountDef);
Expand Down
20 changes: 20 additions & 0 deletions lib/whistle_number_manager-1.0.0/src/wnm_util.erl
Expand Up @@ -9,6 +9,7 @@
-module(wnm_util).

-export([is_tollfree/1]).
-export([is_reconcilable/1]).
-export([list_carrier_modules/0]).
-export([get_carrier_module/1]).
-export([try_load_module/1]).
Expand All @@ -22,6 +23,25 @@

-define(SERVER, ?MODULE).

%%--------------------------------------------------------------------
%% @public
%% @doc
%% Determines if a given number is reconcilable
%% @end
%%--------------------------------------------------------------------
-spec is_reconcilable/1 :: (ne_binary()) -> boolean().
is_reconcilable(Number) ->
Regex = whapps_config:get_binary(?WNM_CONFIG_CAT, <<"reconcile_regex">>, <<"^\\+{0,1}1{0,1}(\\d{10})$">>),
Num = wnm_util:normalize_number(Number),
case re:run(Num, Regex) of
nomatch ->
?LOG("number '~s' is not reconcilable", [Num]),
false;
_ ->
?LOG("number '~s' can be reconciled, proceeding", [Num]),
true
end.

%%--------------------------------------------------------------------
%% @public
%% @doc
Expand Down
11 changes: 6 additions & 5 deletions whistle_apps/apps/crossbar/src/crossbar_doc.erl
Expand Up @@ -67,11 +67,12 @@ load(DocId, #cb_context{db_name=DB}=Context) ->
true ->
crossbar_util:response_bad_identifier(DocId, Context);
false ->
Context#cb_context{doc=Doc
,resp_status=success
,resp_data=public_fields(Doc)
,resp_etag=rev_to_etag(Doc)
}
Context1 = Context#cb_context{doc=Doc
,resp_status=success
,resp_data=public_fields(Doc)
,resp_etag=rev_to_etag(Doc)
},
crossbar_util:store(db_doc, Doc, Context1)
end;
_Else ->
?LOG("unexpected return from datastore: ~p", [_Else]),
Expand Down
139 changes: 120 additions & 19 deletions whistle_apps/apps/crossbar/src/modules/cb_callflows.erl
Expand Up @@ -21,6 +21,8 @@

-include("../../include/crossbar.hrl").

-define(MOD_CONFIG_CAT, <<(?CONFIG_CAT)/binary, ".callflows">>).

-define(SERVER, ?MODULE).

-define(CALLFLOWS_LIST, <<"callflows/listing_by_id">>).
Expand Down Expand Up @@ -80,24 +82,33 @@ resource_exists(_) -> true.
validate(#cb_context{req_verb = <<"get">>}=Context) ->
load_callflow_summary(Context);
validate(#cb_context{req_verb = <<"put">>}=Context) ->
create_callflow(Context).
numbers_are_unique(create_callflow(Context)).

validate(#cb_context{req_verb = <<"get">>}=Context, DocId) ->
load_callflow(DocId, Context);
validate(#cb_context{req_verb = <<"post">>}=Context, DocId) ->
update_callflow(DocId, Context);
numbers_are_unique(update_callflow(DocId, Context));
validate(#cb_context{req_verb = <<"delete">>}=Context, DocId) ->
load_callflow(DocId, Context).

-spec post/2 :: (#cb_context{}, path_token()) -> #cb_context{}.
post(Context, _DocId) ->
case crossbar_doc:save(Context) of
#cb_context{account_id=AccountId, doc=JObj, resp_status=success}=C ->
spawn(fun() ->
[wh_number_manager:reconcile_number(Number, AccountId)
|| Number <- wh_json:get_value(<<"numbers">>, JObj, [])]
end),
C;
case whapps_config:get_is_true(?MOD_CONFIG_CAT, <<"default_reconcile_numbers">>, true) of
false -> C;
true ->
DbDoc = crossbar_util:fetch(db_doc, Context),
Set1 = sets:from_list(wh_json:get_value(<<"numbers">>, DbDoc, [])),
Set2 = sets:from_list(wh_json:get_value(<<"numbers">>, JObj, [])),
NewNumbers = sets:subtract(Set2, Set1),
RemovedNumbers = sets:subtract(Set1, Set2),
[wh_number_manager:reconcile_number(Number, AccountId)
|| Number <- sets:to_list(NewNumbers)],
[wh_number_manager:release_number(Number, AccountId)
|| Number <- sets:to_list(RemovedNumbers)],
C
end;
Else ->
Else
end.
Expand All @@ -106,18 +117,23 @@ post(Context, _DocId) ->
put(Context) ->
case crossbar_doc:save(Context) of
#cb_context{account_id=AccountId, doc=JObj, resp_status=success}=C ->
spawn(fun() ->
[wh_number_manager:reconcile_number(Number, AccountId)
|| Number <- wh_json:get_value(<<"numbers">>, JObj, [])]
end),
[wh_number_manager:reconcile_number(Number, AccountId)
|| Number <- wh_json:get_value(<<"numbers">>, JObj, [])],
C;
Else ->
Else
end.

-spec delete/2 :: (#cb_context{}, path_token()) -> #cb_context{}.
delete(Context, _DocId) ->
crossbar_doc:delete(Context).
delete(#cb_context{doc=JObj, account_id=AccountId}=Context, _DocId) ->
case crossbar_doc:delete(Context) of
#cb_context{resp_status=success}=C ->
[wh_number_manager:release_number(Number, AccountId)
|| Number <- wh_json:get_value(<<"numbers">>, JObj, [])],
C;
Else ->
Else
end.

%%--------------------------------------------------------------------
%% @private
Expand Down Expand Up @@ -234,10 +250,7 @@ get_metadata(Flow, Db, JObj) ->
%% exists in metadata.
%% @end
%%--------------------------------------------------------------------
-spec create_metadata/3 :: (Db, Id, JObj) -> wh_json:json_object() when
Db :: binary(),
Id :: binary(),
JObj :: wh_json:json_object().
-spec create_metadata/3 :: (ne_binary(), ne_binary(), wh_json:json_object()) -> wh_json:json_object().
create_metadata(Db, Id, JObj) ->
case wh_json:get_value(Id, JObj) =:= undefined
andalso couch_mgr:open_doc(Db, Id) of
Expand All @@ -252,8 +265,7 @@ create_metadata(Db, Id, JObj) ->
JObj
end.

-spec create_metadata/1 :: (Doc) -> wh_json:json_object() when
Doc :: wh_json:json_object().
-spec create_metadata/1 :: (wh_json:json_object()) -> wh_json:json_object().
create_metadata(Doc) ->
%% simple funciton for setting the same key in one json object
%% with the value of that key in another, unless it doesnt exist
Expand Down Expand Up @@ -287,3 +299,92 @@ create_metadata(Doc) ->
lists:foldl(fun(Fun, JObj) ->
Fun(Doc, JObj)
end, wh_json:new(), Funs).

-spec numbers_are_unique/1 :: (#cb_context{}) -> #cb_context{}.
numbers_are_unique(#cb_context{req_data=JObj}=Context) ->
numbers_are_unique(wh_json:get_value(<<"numbers">>, JObj), Context).

-spec numbers_are_unique/2 :: (wh_json:json_object(), #cb_context{}) -> #cb_context{}.
numbers_are_unique(Numbers, #cb_context{db_name=Db, resp_status=Status, resp_data=Data, doc=JObj}=Context) ->
case couch_mgr:get_results(Db, ?CB_LIST, [{<<"include_docs">>, true}]) of
{error, _R} ->
?LOG("failed to load callflows from account: ~p", [_R]),
Context;
{ok, AllResults} ->
Results = case wh_json:get_value(<<"_id">>, JObj) of
undefined -> AllResults;
ExistingId -> [Result
|| Result <- AllResults
,wh_json:get_value(<<"id">>, Result) =/= ExistingId
]
end,
Validators = [fun(E1) ->
lists:foldr(fun(N, E2) ->
check_for_existing(N, Results, E2)
end, E1, Numbers)
end
,fun(E1) ->
lists:foldr(fun(N, E2) ->
check_patterns(N, Results, E2)
end, E1, Numbers)
end
],
New = wh_json:new(),
case lists:foldr(fun(F, E) -> F(E) end, New, Validators) of
Errors when Errors =:= New -> Context;
Errors when Status =:= success ->
crossbar_util:response_invalid_data(Errors, Context);
Errors ->
Err = wh_json:merge_recursive(Data, Errors),
crossbar_util:response_invalid_data(Err, Context)
end
end.

-spec check_for_existing/3 :: (ne_binary(), wh_json:json_objects(), wh_json:json_object()) -> wh_json:json_object().
check_for_existing(_, [], Errors) ->
Errors;
check_for_existing(Number, [Result|Results], Errors) ->
Numbers = wh_json:get_value([<<"doc">>, <<"numbers">>], Result, []),

case lists:member(Number, Numbers) of
false -> check_for_existing(Number, Results, Errors);
true ->
case wh_json:get_ne_value([<<"doc">>, <<"featurecode">>, <<"name">>], Result) of
undefined ->
Id = wh_json:get_value(<<"id">>, Result),
Error = wh_json:set_value([<<"numbers">>, Number, <<"exists">>]
,<<"Number exists in callflow ", Id/binary>>
,Errors),
check_for_existing(Number, Results, Error);
Name ->
Error = wh_json:set_value([<<"numbers">>, Number, <<"matches">>]
,<<"Number conflicts with feature code ", Name/binary>>
,Errors),
check_for_existing(Number, Results, Error)
end
end.

-spec check_patterns/3 :: (ne_binary(), wh_json:json_objects(), wh_json:json_object()) -> wh_json:json_object().
check_patterns(_, [], Errors) ->
Errors;
check_patterns(Number, [Result|Results], Errors) ->
case [false || Pattern <- wh_json:get_value([<<"doc">>, <<"patterns">>], Result, [])
,re:run(Number, Pattern) =/= nomatch
] of
[] -> check_patterns(Number, Results, Errors);
_Else ->
case wh_json:get_ne_value([<<"doc">>, <<"featurecode">>, <<"name">>], Result) of
undefined ->
Id = wh_json:get_value(<<"id">>, Result),
Error = wh_json:set_value([<<"numbers">>, Number, <<"matches">>]
,<<"Number matches pattern in callflow ", Id/binary>>
,Errors),
check_patterns(Number, Results, Error);
Name ->
Error = wh_json:set_value([<<"numbers">>, Number, <<"matches">>]
,<<"Number conflicts with feature code ", Name/binary>>
,Errors),
check_patterns(Number, Results, Error)
end
end.

2 changes: 1 addition & 1 deletion whistle_apps/apps/crossbar/src/v1_resource.erl
Expand Up @@ -301,7 +301,7 @@ resource_exists(Req0, Context0) ->
{Context1#cb_context.req_verb =/= <<"put">>, Req0, Context1};
false ->
?LOG("failed to validate resource"),
{false, Req0, Context1}
v1_util:halt(Req0, Context1)
end;
false ->
?LOG("requested resource does not exist"),
Expand Down
2 changes: 1 addition & 1 deletion whistle_apps/apps/crossbar/src/v1_util.erl
Expand Up @@ -465,7 +465,7 @@ validate(#cb_context{req_nouns=Nouns}=Context0) ->
end, Context0, Nouns),
case succeeded(Context1) of
true -> process_billing(Context1);
false -> crossbar_util:response_faulty_request(Context1)
false -> Context1
end.

%%--------------------------------------------------------------------
Expand Down

0 comments on commit 13eb493

Please sign in to comment.