Skip to content

Commit

Permalink
KAZOO-5731: add CCVs to quickcall requests (2600hz#4297)
Browse files Browse the repository at this point in the history
* KAZOO-5731: add ccvs from request

filter by reserved set of keys

use friendly macro

use the default path instead of atom

formatting

add some more CCV exceptions

log adding custom ccv

* fix readme

* KAZOO-5731: include CCVs in channel event webhooks

* handle ccvs from query string or req body

* fix spec
  • Loading branch information
jamesaimonetti authored and icehess committed Oct 30, 2017
1 parent bc8c0f2 commit e6f76b8
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 51 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ No problem! We have many different places where folks can chip in time. These in
* Contribute scripts to the [community repo](https://github.com/2600Hz/community-scripts)
* Help with building RPMs, DEBs, and other system packages
* Browse the open [issues](https://2600Hz.atlassian.net/browse/KAZOO) and test bugs to see if they're valid
* Make a sugestion!
* Make a suggestion!

### I know me some Erlang! How can I contribute?

Expand Down
19 changes: 7 additions & 12 deletions applications/crossbar/doc/quickcall.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ Key | Type | Description
`media` | `string('bypass', 'process')` | Toggle whether to go peer-to-peer([bypass](https://freeswitch.org/confluence/display/FREESWITCH/Bypass+Media+Overview) with the RTP
`number_filter` | `boolean()`, `regex()` | If true, remove non-alphanumeric characters. If a regex, use the first capture group as the "number" to dial.
`timeout` | `integer(3..)` | In seconds, how long to ring the device(s) (defaults to 30)
`custom_channel_vars` | `object()` | Custom data to include on the call (and events related to the call)

##### Custom Channel Vars

CCVs allow you to set custom data that will appear on subsequent call events (found in webhook and websocket payloads) as well as the final CDR.

As query-string parameters: `/quickcall/{NUMBER}?foo=bar`

As POST body: `{"data":{"custom_channel_vars":{"foo":"bar"}}}`

#### Non-blocking Quickcall

Expand Down Expand Up @@ -135,18 +142,6 @@ Ring user's devices; once answered, connect to `{PHONE_NUMBER}`

In this scenario, the user's devices are considered the `callee` while the `{PHONE_NUMBER}` side is considered the caller (helpful to know when debugging a call!).

Query string options:

Key | Type | Description
--- | ---- | -----------
`auto_answer` | `boolean()` | Tells the SIP phone to auto-answer the call, if supported
`cid-name` | `string()` | Set the caller ID name (defaults to "Device QuickCall")
`cid-number` | `string()` | Set the caller ID number (defaults to the `{PHONE_NUMBER}`)
`ignore-early-media` | `boolean()` | Toggle whether to ignore [early media](https://freeswitch.org/confluence/display/FREESWITCH/Early+Media)
`media` | `string('bypass', 'process')` | Toggle whether to go peer-to-peer([bypass](https://freeswitch.org/confluence/display/FREESWITCH/Bypass+Media+Overview) with the RTP
`number_filter` | `boolean()`, `regex()` | If true, remove non-alphanumeric characters. If a regex, use the first capture group as the "number" to dial.
`timeout` | `integer(3..)` | In seconds, how long to ring the device(s) (defaults to 30)

> GET /v2/accounts/{ACCOUNT_ID}/users/{USER_ID}/quickcall/{PHONE_NUMBER}
```shell
Expand Down
33 changes: 33 additions & 0 deletions applications/crossbar/priv/api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -25112,6 +25112,39 @@
"default": 5000,
"description": "call_command message timeout",
"type": "integer"
},
"reserved_ccv_keys": {
"default": [
"Account-ID",
"Account-Name",
"Account-Realm",
"Authorizing-ID",
"Authorizing-Type",
"Auto-Answer",
"Call-Forward",
"Call-Waiting-Disabled",
"Confirm-Cancel-Timeout",
"Confirm-File",
"Confirm-Key",
"Fax-Enabled",
"Loopback-Bowout",
"Media-Encryption-Enforce-Security",
"Media-Webrtc",
"Owner-ID",
"Presence-ID",
"RTCP-MUX",
"Realm",
"Require-Ignore-Early-Media",
"Retain-CID",
"SIP-Invite-Domain",
"Simplify-Loopback",
"Username"
],
"description": "call_command reserved_ccv_keys",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,39 @@
"default": 5000,
"description": "call_command message timeout",
"type": "integer"
},
"reserved_ccv_keys": {
"default": [
"Account-ID",
"Account-Name",
"Account-Realm",
"Authorizing-ID",
"Authorizing-Type",
"Auto-Answer",
"Call-Forward",
"Call-Waiting-Disabled",
"Confirm-Cancel-Timeout",
"Confirm-File",
"Confirm-Key",
"Fax-Enabled",
"Loopback-Bowout",
"Media-Encryption-Enforce-Security",
"Media-Webrtc",
"Owner-ID",
"Presence-ID",
"RTCP-MUX",
"Realm",
"Require-Ignore-Early-Media",
"Retain-CID",
"SIP-Invite-Domain",
"Simplify-Loopback",
"Username"
],
"description": "call_command reserved_ccv_keys",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
Expand Down
5 changes: 5 additions & 0 deletions applications/crossbar/src/crossbar_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

-export([autoload_modules/0, autoload_modules/1
,set_autoload_modules/1, set_default_autoload_modules/1
,reserved_ccv_keys/0

,flush/0
]).
Expand Down Expand Up @@ -54,3 +55,7 @@ set_autoload_modules(Modules) ->
-spec set_default_autoload_modules(ne_binaries() | atoms()) -> {'ok', kz_json:object()}.
set_default_autoload_modules(Modules) ->
kapps_config:set_default(?CONFIG_CAT, <<"autoload_modules">>, Modules).

-spec reserved_ccv_keys() -> ne_binaries().
reserved_ccv_keys() ->
kapps_config:get_ne_binaries(<<"call_command">>, <<"reserved_ccv_keys">>, ?DEFAULT_CCV_KEYS).
30 changes: 30 additions & 0 deletions applications/crossbar/src/crossbar_types.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,35 @@

-define(JSONP_CONTENT_TYPE, <<"application/javascript">>).

-define(DEFAULT_CCV_KEYS
,[<<"Account-ID">>
,<<"Account-Name">>
,<<"Account-Realm">>
,<<"Authorizing-ID">>
,<<"Authorizing-Type">>
,<<"Auto-Answer">>
,<<"Call-Forward">>
,<<"Call-Interaction-ID">>
,<<"Ecallmgr-Node">>
,<<"Call-Waiting-Disabled">>
,<<"Confirm-Cancel-Timeout">>
,<<"Confirm-File">>
,<<"Confirm-Key">>
,<<"Fax-Enabled">>
,<<"Fetch-ID">>
,<<"Loopback-Bowout">>
,<<"Media-Encryption-Enforce-Security">>
,<<"Media-Webrtc">>
,<<"Owner-ID">>
,<<"Presence-ID">>
,<<"RTCP-MUX">>
,<<"Realm">>
,<<"Require-Ignore-Early-Media">>
,<<"Retain-CID">>
,<<"SIP-Invite-Domain">>
,<<"Simplify-Loopback">>
,<<"Username">>
]).

-define(CROSSBAR_TYPES_INCLUDED, 'true').
-endif.
54 changes: 46 additions & 8 deletions applications/crossbar/src/modules/cb_modules_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,13 @@ maybe_originate_quickcall(Context) ->
-spec create_call_from_context(cb_context:context()) -> kapps_call:call().
create_call_from_context(Context) ->
Routines =
[{F, V} ||
{F, V} <-
[{fun kapps_call:set_account_db/2, cb_context:account_db(Context)}
,{fun kapps_call:set_account_id/2, cb_context:account_id(Context)}
,{fun kapps_call:set_resource_type/2, <<"audio">>}
,{fun kapps_call:set_owner_id/2, kz_json:get_ne_value(<<"owner_id">>, cb_context:doc(Context))}
| request_specific_extraction_funs(Context)
],
[{F, V}
|| {F, V} <- [{fun kapps_call:set_account_db/2, cb_context:account_db(Context)}
,{fun kapps_call:set_account_id/2, cb_context:account_id(Context)}
,{fun kapps_call:set_resource_type/2, <<"audio">>}
,{fun kapps_call:set_owner_id/2, kz_json:get_ne_binary_value(<<"owner_id">>, cb_context:doc(Context))}
| request_specific_extraction_funs(Context)
],
'undefined' =/= V
],
kapps_call:exec(Routines, kapps_call:new()).
Expand Down Expand Up @@ -213,6 +212,44 @@ aleg_cid(Number, Call) ->
],
kapps_call:exec(Routines, Call).

-spec ccvs_from_context(cb_context:context()) -> kz_proplist().
ccvs_from_context(Context) ->
ReqData = cb_context:req_data(Context),
QueryString = cb_context:query_string(Context),
ccvs_from_request(ReqData, QueryString).

-spec ccvs_from_request(api_object(), api_object()) -> kz_proplist().
ccvs_from_request('undefined', 'undefined') -> [];
ccvs_from_request('undefined', QueryString) ->
ccvs_from_request(QueryString);
ccvs_from_request(ReqData, 'undefined') ->
ccvs_from_request(kz_json:get_json_value(<<"custom_channel_vars">>, ReqData));
ccvs_from_request(ReqData, QueryString) ->
CCVs = kz_json:get_json_value(<<"custom_channel_vars">>, ReqData, kz_json:new()),
ccvs_from_request(kz_json:merge(CCVs, QueryString)).

-spec ccvs_from_request(kz_json:object()) -> kz_proplist().
ccvs_from_request(CCVs) ->
lager:debug("extracting CCVs from ~p", [CCVs]),
{ReqCCVs, _} =
kz_json:foldl(fun ccv_from_request/3
,{[], crossbar_config:reserved_ccv_keys()}
,CCVs
),
ReqCCVs.

ccv_from_request(Key, Value, {Acc, Keys}) ->
case is_private_ccv(Key, Keys) of
'true' -> {Acc, Keys};
'false' ->
lager:debug("adding ccv ~s:~p", [Key, Value]),
{[{Key, Value} | Acc], Keys}
end.

-spec is_private_ccv(ne_binary(), ne_binaries()) -> boolean().
is_private_ccv(Key, Keys) ->
lists:member(Key, Keys).

-spec originate_quickcall(kz_json:objects(), kapps_call:call(), cb_context:context()) ->
cb_context:context().
originate_quickcall(Endpoints, Call, Context) ->
Expand All @@ -222,6 +259,7 @@ originate_quickcall(Endpoints, Call, Context) ->
,{<<"Inherit-Codec">>, <<"false">>}
,{<<"Authorizing-Type">>, kapps_call:authorizing_type(Call)}
,{<<"Authorizing-ID">>, kapps_call:authorizing_id(Call)}
| ccvs_from_context(Context)
],
MsgId = case kz_term:is_empty(cb_context:req_id(Context)) of
'true' -> kz_binary:rand_hex(16);
Expand Down
10 changes: 2 additions & 8 deletions applications/crossbar/src/modules/cb_quickcall.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,9 @@ resource_exists(_Number) -> 'true'.
validate(Context, _Number) ->
validate_quickcall(Context, cb_context:req_nouns(Context)).

validate_quickcall(Context, [{<<"quickcall">>, [_Number]}
,{<<"devices">>, [DeviceId]}
,{<<"accounts">>, [_AccountId]}
]) ->
validate_quickcall(Context, ?DEVICES_QCALL_NOUNS(DeviceId, _Number)) ->
validate_quickcall(load_endpoint(Context, DeviceId, kz_device:type()));
validate_quickcall(Context, [{<<"quickcall">>, [_Number]}
,{<<"users">>, [UserId]}
,{<<"accounts">>, [_AccountId]}
]) ->
validate_quickcall(Context, ?USERS_QCALL_NOUNS(UserId, _Number)) ->
validate_quickcall(load_endpoint(Context, UserId, kzd_user:type())).

-spec validate_quickcall(cb_context:context()) -> cb_context:context().
Expand Down
43 changes: 27 additions & 16 deletions applications/webhooks/src/webhooks_channel_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,28 @@ base_hook_event(JObj, AccountId, Acc) ->
WasGlobal = kz_term:is_true(ccv(JObj, <<"Global-Resource">>)),

kz_json:from_list(
[{<<"call_direction">>, kz_json:get_value(<<"Call-Direction">>, JObj)}
,{<<"timestamp">>, kz_call_event:timestamp(JObj)}
,{<<"account_id">>, ccv(JObj, <<"Account-ID">>, AccountId)}
,{<<"request">>, kz_json:get_value(<<"Request">>, JObj)}
,{<<"to">>, kz_json:get_value(<<"To">>, JObj)}
,{<<"from">>, kz_json:get_value(<<"From">>, JObj)}
,{<<"inception">>, kz_json:get_value(<<"Inception">>, JObj)}
[{<<"account_id">>, ccv(JObj, <<"Account-ID">>, AccountId)}
,{<<"authorizing_id">>, kz_call_event:authorizing_id(JObj)}
,{<<"authorizing_type">>, kz_call_event:authorizing_type(JObj)}
,{<<"call_direction">>, kz_json:get_value(<<"Call-Direction">>, JObj)}
,{<<"call_forwarded">>, kz_call_event:is_call_forwarded(JObj)}
,{<<"call_id">>, kz_call_event:call_id(JObj)}
,{<<"other_leg_call_id">>, kz_call_event:other_leg_call_id(JObj)}
,{<<"caller_id_name">>, kz_json:get_value(<<"Caller-ID-Name">>, JObj)}
,{<<"caller_id_number">>, kz_json:get_value(<<"Caller-ID-Number">>, JObj)}
,{<<"callee_id_name">>, kz_json:get_value(<<"Callee-ID-Name">>, JObj)}
,{<<"callee_id_number">>, kz_json:get_value(<<"Callee-ID-Number">>, JObj)}
,{<<"caller_id_name">>, kz_json:get_value(<<"Caller-ID-Name">>, JObj)}
,{<<"caller_id_number">>, kz_json:get_value(<<"Caller-ID-Number">>, JObj)}
,{<<"custom_channel_vars">>, non_reserved_ccvs(JObj)}
,{<<"emergency_resource_used">>, kz_term:is_true(ccv(JObj, <<"Emergency-Resource">>))}
,{<<"from">>, kz_json:get_value(<<"From">>, JObj)}
,{<<"inception">>, kz_json:get_value(<<"Inception">>, JObj)}
,{<<"local_resource_id">>, resource_used(WasGlobal, JObj)}
,{<<"local_resource_used">>, (not WasGlobal)}
,{<<"other_leg_call_id">>, kz_call_event:other_leg_call_id(JObj)}
,{<<"owner_id">>, kz_call_event:owner_id(JObj)}
,{<<"request">>, kz_json:get_value(<<"Request">>, JObj)}
,{<<"reseller_id">>, kz_services:find_reseller_id(AccountId)}
,{<<"authorizing_id">>, kz_call_event:authorizing_id(JObj)}
,{<<"authorizing_type">>, kz_call_event:authorizing_type(JObj)}
,{<<"local_resource_used">>, (not WasGlobal)}
,{<<"local_resource_id">>, resource_used(WasGlobal, JObj)}
,{<<"emergency_resource_used">>, kz_term:is_true(ccv(JObj, <<"Emergency-Resource">>))}
,{<<"call_forwarded">>, kz_call_event:is_call_forwarded(JObj)}
,{<<"timestamp">>, kz_call_event:timestamp(JObj)}
,{<<"to">>, kz_json:get_value(<<"To">>, JObj)}
| Acc
]).

Expand All @@ -110,3 +111,13 @@ ccv(JObj, Key) ->
ccv(JObj, Key, 'undefined').
ccv(JObj, Key, Default) ->
kz_call_event:custom_channel_var(JObj, Key, Default).

-spec non_reserved_ccvs(kz_call_event:doc()) -> api_object().
-spec non_reserved_ccvs(kz_json:object(), api_ne_binaries()) -> api_object().
non_reserved_ccvs(JObj) ->
CCVs = kz_call_event:custom_channel_vars(JObj, kz_json:new()),
non_reserved_ccvs(CCVs, kapps_config:get_ne_binaries(<<"call_command">>, <<"reserved_ccv_keys">>)).

non_reserved_ccvs(_CCVs, 'undefined') -> 'undefined';
non_reserved_ccvs(CCVs, Keys) ->
kz_json:filter(fun({K, _}) -> not lists:member(K, Keys) end, CCVs).
6 changes: 3 additions & 3 deletions core/kazoo_ast/src/kapps_config_usage.erl
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ print_dot(_Module, Acc) ->

-spec new_acc() -> acc().
new_acc() ->
#{'schema_dir' => 'default'
#{'schema_dir' => kz_ast_util:default_schema_priv_dir()
,'app_schemas' => kz_json:new() %% SchemaName => SchemaProperties
,'project_schemas' => kz_json:from_list([{'default', kz_json:new()}]) %% Path => Schemas
,'project_schemas' => kz_json:from_list([{kz_ast_util:default_schema_priv_dir(), kz_json:new()}]) %% Path => Schemas
}.

-spec add_app_config(atom(), acc()) -> acc().
Expand All @@ -153,7 +153,7 @@ add_app_config(App, Acc) ->
{'ok', 'true'} ->
?DEBUG("detected schemas will go in ~s/priv~n", [App]),
Acc#{schema_dir => kz_term:to_binary(code:priv_dir(App))};
_ -> Acc#{schema_dir => 'default'}
_ -> Acc#{schema_dir => kz_ast_util:default_schema_priv_dir()}
end.

add_schemas_to_bucket(_App, #{schema_dir := PrivDir
Expand Down
7 changes: 6 additions & 1 deletion core/kazoo_ast/src/kz_ast_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
,binary_match_to_binary/1
,smash_snake/1

,default_schema_priv_dir/0
,schema_path/1, schema_path/2
,api_path/1
,ensure_file_exists/1
Expand Down Expand Up @@ -124,10 +125,14 @@ format_name_part(<<"auth">>) -> <<"Authentication">>;
format_name_part(Part) ->
kz_binary:ucfirst(Part).

-spec default_schema_priv_dir() -> file:filename_all().
default_schema_priv_dir() ->
kz_term:to_binary(code:priv_dir('crossbar')).

-spec schema_path(binary()) -> file:filename_all().
-spec schema_path(binary(), file:filename_all()) -> file:filename_all().
schema_path(Base) ->
schema_path(Base, code:priv_dir('crossbar')).
schema_path(Base, default_schema_priv_dir()).
schema_path(Base, PrivDir) ->
case filename:join([PrivDir
,<<"couchdb">>
Expand Down
4 changes: 2 additions & 2 deletions core/kazoo_config/src/kapps_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,12 @@ get_ne_binary(Category, Key, Default, Node) ->
'false' -> kz_term:to_binary(Value)
end.

-spec get_ne_binaries(config_category(), config_key()) -> ne_binaries().
-spec get_ne_binaries(config_category(), config_key()) -> api_ne_binaries().
-spec get_ne_binaries(config_category(), config_key(), Default) -> ne_binaries() | Default.
-spec get_ne_binaries(config_category(), config_key(), Default, ne_binary()) -> ne_binaries() | Default.

get_ne_binaries(Category, Key) ->
get_ne_binaries(Category, Key, undefined).
get_ne_binaries(Category, Key, 'undefined').
get_ne_binaries(Category, Key, Default) ->
get_ne_binaries(Category, Key, Default, kz_term:to_binary(node())).
get_ne_binaries(Category, Key, Default, Node) ->
Expand Down

0 comments on commit e6f76b8

Please sign in to comment.