diff --git a/applications/crossbar/doc/port_requests.md b/applications/crossbar/doc/port_requests.md index 2ca7add55d5..73b2e9cdd0e 100644 --- a/applications/crossbar/doc/port_requests.md +++ b/applications/crossbar/doc/port_requests.md @@ -1,262 +1,1030 @@ - ### Port Requests + Manage and track number port requests through the Port Requests API. -A port request can be in one of five states: -* Unconfirmed: A port request has been created, but the details have not been confirmed and the port process has not started. -* Submitted: Indicates the number port is ready to be processed and sent to the losing carrier. -* Scheduled: The port is in progress and the losing carrier has been notified. -* Completed: The port request has been finished, and numbers are activated. -* Rejected: The port request has been cancelled, or something has gone wrong during the port process. The port can be resubmitted. +A port request can be in one of five **states**: -##### List port requests +* `unconfirmed`: A port request has been created, but the details have not been confirmed and the port process has not started. +* `submitted`: Indicates the number port is ready to be processed and sent to the losing carrier. +* `scheduled`: The port is in progress and the losing carrier has been notified. +* `completed`: The port request has been finished, and numbers are activated. +* `rejected`: The port request has been cancelled, or something has gone wrong during the port process. The port can be resubmitted. - curl -v -X GET \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests -###### Listing by port state +#### List port requests -You can issue GET requests to find all ports in a particular state too: +> GET /v2/accounts/{ACCOUNT_ID}/port_requests - curl -v -X GET \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{STATE_NAME} +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests +``` -Where `{STATE_NAME}` is one of: +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "account_name": "{ACCOUNT_NAME}", + "port_requests": [ + { + "account_id": "{ACCOUNT_ID}", + "created": 63630097779, + "id": "462da37f8be11e46161fb40bc71173a9", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} + }, + "port_state": "unconfirmed", + "sent": false, + "updated": 63630097779, + "uploads": {} + } + ] + } + ], + "request_id": "6b667214680d1cc3143b8a187d820af6", + "revision": "undefined", + "status": "success" +} +``` -* submitted -* pending -* scheduled -* completed -* rejected -* canceled -All requests are not paginated, with the exception of the `completed` state. Use pagination toggles for date range as desired. +#### Get a port request by phone number -##### List port requests of self and sub accounts +> GET /v2/accounts/{ACCOUNT_ID}/port_requests - curl -v -X GET \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/descendants/port_requests +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests?by_number={PHONE_NUMBER} +``` -##### Create a new port request +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "created": 63630107130, + "id": "0684aa1e2785d62c76ce27d2451a1c26", + "name": "Porting 202.555.9000", + "numbers": { + "{PHONE_NUMBER}": {} + }, + "port_state": "canceled", + "sent": false, + "updated": 63630120578, + "uploads": { + "file.pdf": { + "content_type": "application/pdf", + "length": 90931 + } + } + } + ], + "page_size": 1, + "request_id": "24060fb2eb79e6dd0d29b290e2cb7085", + "revision": "bb7443aecba069b09d7197410dc02fbe", + "status": "success" +} +``` - curl -v -X PUT \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - -d '{"data":{"numbers":{"+12025559000":{}}, "name":"Porting 202.555.9000"}}' \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests -##### List port request details +#### Listing by port state - curl -v -X GET \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}} +You can issue GET requests to find all ports in a particular state too. -##### Edit a port request +All requests are not paginated, with the exception of the `completed` state. +Use pagination toggles for date range as desired. - curl -v -X POST \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - -d '{"data":{"numbers":{"+12025559000":{"state":"NY"}}}}' \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}} +##### Listing by `unconfirmed` port -##### DELETE a port request +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/unconfirmed - curl -v -X DELETE \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}} +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/unconfirmed +``` -#### Attachment manipulation +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "account_name": "{ACCOUNT_NAME}", + "port_requests": [ + { + "account_id": "{ACCOUNT_ID}", + "created": 63630097779, + "id": "462da37f8be11e46161fb40bc71173a9", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} + }, + "port_state": "unconfirmed", + "sent": false, + "updated": 63630097779, + "uploads": {} + } + ] + } + ], + "page_size": 1, + "request_id": "ecd09332796338bd1cfada0a74e6bd28", + "revision": "db7651aaeed4a373915b516963cd6dd4", + "status": "success" +} +``` -##### List attachments on a port request +##### Listing by `submitted` port - curl -v -X GET \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/attachments +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/submitted -##### Add an attachment to a port request +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/submitted +``` - curl -v -X PUT \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/pdf" \ - --data-binary @/path/to/file.pdf \ - 'http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/attachments?filename=file.pdf' +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "account_name": "{ACCOUNT_NAME}", + "port_requests": [ + { + "account_id": "009afc511c97b2ae693c6cc4920988e8", + "created": 63630130298, + "id": "c320d6506f1afcd715a1a49bac019c51", + "name": "My port req1", + "numbers": { + "+12025559042": {} + }, + "port_state": "submitted", + "sent": false, + "updated": 63630130376, + "uploads": {} + } + ] + } + ], + "page_size": 1, + "request_id": "1bf407e087f04e6096f7f010c4eef045", + "revision": "e4058257bbc9e33ceab14632c5662eaf", + "status": "success" +} +``` -##### Get an attachment from a port request - curl -v -X GET -H \ - "X-Auth-Token: {{AUTH_TOKEN}}" -H \ - "Accept: application/pdf" \ - 'http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/attachments/file.pdf' > file.pdf +##### Listing by `pending` port -##### Replace an attachment on a port request +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/pending - curl -v -X POST \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/pdf" \ - --data-binary @/path/to/file.pdf \ - 'http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/attachments/file.pdf' +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/pending +``` -##### Delete an attachment on a port request +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "account_name": "{ACCOUNT_NAME}", + "port_requests": [ + { + "account_id": "009afc511c97b2ae693c6cc4920988e8", + "created": 63630130298, + "id": "c320d6506f1afcd715a1a49bac019c51", + "name": "My port req1", + "numbers": { + "+12025559042": {} + }, + "port_state": "pending", + "sent": false, + "updated": 63630130450, + "uploads": {} + } + ] + } + ], + "page_size": 1, + "request_id": "156c9979f04229a0088653bfbff0b08c", + "revision": "cc55f42be1e2294a9291a2dca8087d10", + "status": "success" +} +``` - curl -v -X DELETE \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - 'http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/attachments/otp.pdf' -#### State changes +##### Listing by `scheduled` port -##### Indicate a port is ready to be processed +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/scheduled - curl -v -X PATCH \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/submitted +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/scheduled +``` -##### Put port in pending +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "account_name": "{ACCOUNT_NAME}", + "port_requests": [ + { + "account_id": "009afc511c97b2ae693c6cc4920988e8", + "created": 63630130298, + "id": "c320d6506f1afcd715a1a49bac019c51", + "name": "My port req1", + "numbers": { + "+12025559042": {} + }, + "port_state": "scheduled", + "sent": false, + "updated": 63630130490, + "uploads": {} + } + ] + } + ], + "page_size": 1, + "request_id": "ab348636e435cd01888356607b183697", + "revision": "ae048f434e5b16749c721eb0ab8fa781", + "status": "success" +} +``` - curl -v -X PATCH \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/pending -##### Put port in progress (sent to losing carrier) +##### Listing by `completed` port - curl -v -X PATCH \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/scheduled +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/completed -##### Complete port, numbers will activate in the system, account will be billed +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/completed +``` - curl -v -X PATCH \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/completed +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [], + "page_size": 0, + "request_id": "388313efcf12e4eda5672b5336828350", + "revision": "undefined", + "start_key": [ + "009afc511c97b2ae693c6cc4920988e8", + "completed", + 63630130530 + ], + "status": "success" +} +``` -##### Reject a port - curl -v -X PATCH \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/rejected +##### Listing by `rejected` port + +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/rejected + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/rejected +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "account_name": "{ACCOUNT_NAME}", + "port_requests": [ + { + "account_id": "009afc511c97b2ae693c6cc4920988e8", + "created": 63630130298, + "id": "c320d6506f1afcd715a1a49bac019c51", + "name": "My port req1", + "numbers": { + "+12025559042": {} + }, + "port_state": "rejected", + "sent": false, + "updated": 63630130557, + "uploads": {} + } + ] + } + ], + "page_size": 1, + "request_id": "92200b874efd5cbb641599f47cd2431a", + "revision": "26e7f1549eed11fb1d5418c58424baf6", + "status": "success" +} +``` + + +##### Listing by `canceled` port + +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/canceled + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/canceled +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "account_id": "{ACCOUNT_ID}", + "account_name": "{ACCOUNT_NAME}", + "port_requests": [ + { + "account_id": "009afc511c97b2ae693c6cc4920988e8", + "created": 63630107130, + "id": "0684aa1e2785d62c76ce27d2451a1c26", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} + }, + "port_state": "canceled", + "sent": false, + "updated": 63630120578, + "uploads": { + "file.pdf": { + "content_type": "application/pdf", + "length": 90931 + } + } + } + ] + } + ], + "page_size": 1, + "request_id": "745be8bfd341636eabb9a0b9572846eb", + "revision": "bddb6f5c0566d7a84ca2877abff750a8", + "start_key": [ + "009afc511c97b2ae693c6cc4920988e8", + "canceled", + 63630129922 + ], + "status": "success" +} +``` + + +#### List port requests of self and sub accounts + +> GET /v2/accounts/{ACCOUNT_ID}/descendants/port_requests + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/descendants/port_requests +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [], + "request_id": "48f4e5e053801a13b4e4e3e3ba9c10c4", + "revision": "undefined", + "status": "success" +} +``` + + +#### Get port request for account and descendants + +> GET /v2/accounts/{ACCOUNT_ID}/descendants/port_requests + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/descendants/port_requests?by_number={PHONE_NUMBER} +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": [ + { + "bill": { + "address": "116, natoma street", + "locality": "San Francisco", + "name": "John Doe", + "postal_code": "95109", + "region": "Ca" + }, + "carrier": "PACIFIC BELL", + "created": 63597642009, + "id": "84e0a824c6b74fe1e3ec48962a600ef2", + "name": "Port request test", + "notifications": { + "email": { + "send_to": "someone@2600hz.com" + } + }, + "numbers": { + "{PHONE_NUMBER}": {} + }, + "port_state": "submitted", + "sent": false, + "transfer_date": 63598114800, + "updated": 63597642011, + "uploads": { + "bill.pdf": { + "content_type": "application/pdf", + "length": 8304 + }, + "loa.pdf": { + "content_type": "application/pdf", + "length": 59196 + } + } + } + ], + "page_size": 1, + "request_id": "82db6e47a0adafc1db83369723390dce", + "revision": "4dd6dc369615b519373a4deeaa1567bd", + "status": "success" +} +``` -##### Cancel a port - curl -v -X PATCH \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ +#### Create a new port request + +> PUT /v2/accounts/{ACCOUNT_ID}/port_requests + +```shell +curl -v -X PUT \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ -H "Content-Type: application/json" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/canceled + -d '{"data":{"numbers":{"{PHONE_NUMBER}":{}}, "name":"{PORTREQUEST_NAME}"}}' \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests +``` -#### Extra features +##### Success -##### Build an LOA PDF from a port request +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "id": "462da37f8be11e46161fb40bc71173a9", + "name": "{PORTREQUEST_NAME}", + "numbers": { + "{PHONE_NUMBER}": {} + }, + "port_state": "unconfirmed" + }, + "request_id": "54672607e9884437069abb7e5a891a5c", + "revision": "1-184548f264275cf9351872e21a195152", + "status": "success" +} +``` - curl -v -X GET \ - -H "Accept: application/x-pdf" \ - -H "X-Auth-Token: {{AUTH_TOKEN}}" \ - http://{{SERVER}}:8000/v2/accounts/{{ACCOUNT_ID}}/port_requests/{{PORT_REQUEST_ID}}/loa +##### Failure: a port already exists for this number -#### Get port request for account +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "{PHONE_NUMBER}": { + "type": { + "cause": "{PHONE_NUMBER}", + "message": "Number is on a port request already: 41ed5722d24bfc69bc479208b274be6b" + } + } + }, + "error": "500", + "message": "invalid request", + "request_id": "7cdd44775dc7b583a2628e05588c439b", + "status": "error" +} +``` -##### Request +##### Failure: an account already owns this number -- Verb: `GET` -- Url: `/accounts/{{ACCOUNT_ID}}/port_requests?by_number={{NUMBER}}` -- Payload: None +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "{PHONE_NUMBER}": { + "type": { + "cause": "{PHONE_NUMBER}", + "message": "Number exists on the system already" + } + } + }, + "error": "500", + "message": "invalid request", + "request_id": "05ec9cf8811661fed103864e2ed07bcd", + "status": "error" +} +``` + + +#### List port request details -##### Response +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} - {"page_size": 1, - "data": [{ - "carrier": "PACIFIC BELL", - "bill": { - "name": "John Doe", - "address": "116, natoma street", - "locality": "San Francisco", - "region": "Ca", - "postal_code": "95109" +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630097779, + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} }, - "name": "Port request test", - "notifications": { - "email": { - "send_to": "someone@2600hz.com" + "port_state": "unconfirmed", + "sent": false, + "updated": 63630097779, + "uploads": {} + }, + "request_id": "32afa58869e88fd709f88739f2d0bb12", + "revision": "1-184548f264275cf9351872e21a195152", + "status": "success" +} +``` + + +#### Edit a port request + +> POST /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} + +```shell +curl -v -X POST \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"data":{"numbers":{"{PHONE_NUMBER}":{"state":"NY"}}, "name": "{PORTREQUEST_NAME}"}}' \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630097779, + "id": "{PORTREQUEST_ID}", + "name": "{PORTREQUEST_NAME}", + "numbers": { + "{PHONE_NUMBER}": { + "state": "NY" } }, - "transfer_date": 63598114800, - "port_state": "submitted", + "port_state": "unconfirmed", + "sent": false, + "updated": 63630104652, + "uploads": {} + }, + "request_id": "aed9b0228ef02bcd92322442b02204e9", + "revision": "2-b43873df23aedaaa96682ec2da32e688", + "status": "success" +} +``` + + +#### DELETE a port request + +> DELETE /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} + +```shell +curl -v -X DELETE \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": { + "state": "NY" + } + }, + "port_state": "unconfirmed" + }, + "request_id": "80b315b87c9db92a2ae755c581d838a3", + "revision": "2-b43873df23aedaaa96682ec2da32e688", + "status": "success" +} +``` + + +#### List attachments on a port request + +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "file.pdf": { + "content_type": "application/pdf", + "length": 90931 + }, + "otherfile.pdf": { + "content_type": "application/pdf", + "length": 767684 + } + }, + "request_id": "ed71507eeccbd3eeab8ccd7bd6cf023c", + "revision": "6-ade05f2d7e539be79f425bfb39e41cef", + "status": "success" +} +``` + + +#### Add an attachment to a port request + +Note: if `ATTACHMENT_ID` does not end with `.pdf` the extension will be appended. + +> PUT /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments + +```shell +curl -v -X PUT \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + -H "Content-Type: application/pdf" \ + --data-binary @/path/to/file.pdf \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments?filename={ATTACHMENT_ID}' +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": {}, + "request_id": "e9db1aa74945884eed5a4545a405b490", + "revision": "2-7ab1daee08211c782a6cdc5425886be4", + "status": "success" +} +``` + + +#### Get an attachment from a port request + +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + -H "Accept: application/pdf" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} +``` + +Streams back the contents of the PDF file `{ATTACHMENT_ID}`. + + +#### Replace an attachment on a port request + +> POST /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} + +```shell +curl -v -X POST \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + -H "Content-Type: application/pdf" \ + --data-binary @/path/to/file.pdf \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": {}, + "request_id": "5ccaf7eb473c350c2e460ae5b4a76697", + "revision": "4-288d2d9e121c8e5d5e0d137626d70536", + "status": "success" +} +``` + + +#### Delete an attachment on a port request + +> DELETE /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} + +```shell +curl -v -X DELETE \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": {}, + "request_id": "1178da494ef5a953240b33eeaa7df859", + "revision": "undefined", + "status": "success" +} +``` + + +#### Indicate a port is ready to be processed + +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/submitted + +```shell +curl -v -X PATCH \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/submitted +``` + +##### Success + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630107130, + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", "numbers": { - "{{NUMBER}}": {} + "+12025559000": {} }, + "port_state": "submitted", "sent": false, + "updated": 63630120443, "uploads": { - "loa.pdf": { + "file.pdf": { "content_type": "application/pdf", - "length": 59196 - }, - "bill.pdf": { - "content_type": "application/pdf", - "length": 8304 + "length": 90931 } - }, - "updated": 63597642011, - "created": 63597642009, - "id": "84e0a824c6b74fe1e3ec48962a600ef2" - }], - "status": "success" - } + } + }, + "request_id": "6a05c7559aabd2ff55f13e364ccfa63c", + "revision": "14-f5fcbb593efe8719d9b3e29cb745ead9", + "status": "success" +} +``` +###### Failure: charges have to be accepted -#### Get port request for account and descendants +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "activation_charges": 10.0, + "number_services": { + "port": { + "activation_charges": 10.0 + } + } + }, + "error": "402", + "message": "accept charges", + "request_id": "9abd3b133b4bf2e2f006fa164a842441", + "status": "error" +} +``` -##### Request -- Verb: `GET` -- Url: `/accounts/{{ACCOUNT_ID}}/descendants/port_requests?by_number={{NUMBER}}` -- Payload: None +#### Put port in pending +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/pending -##### Response +```shell +curl -v -X PATCH \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/pending +``` - {"page_size": 1, - "data": [{ - "carrier": "PACIFIC BELL", - "bill": { - "name": "John Doe", - "address": "116, natoma street", - "locality": "San Francisco", - "region": "Ca", - "postal_code": "95109" +##### Success + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630107130, + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} }, - "name": "Port request test", - "notifications": { - "email": { - "send_to": "someone@2600hz.com" + "port_state": "pending", + "sent": false, + "updated": 63630120502, + "uploads": { + "file.pdf": { + "content_type": "application/pdf", + "length": 90931 } + } + }, + "request_id": "f9012ea6d97a1fa3a54e3f5615e5ad24", + "revision": "15-eefd336bebf12ec109880527a01387c2", + "status": "success" +} +``` + +##### Failure: target state illegal given current state + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "port_state": { + "enum": { + "cause": "pending", + "message": "Cannot move to new state from current state" + } + } + }, + "error": "500", + "message": "invalid request", + "request_id": "a6c9b35b2fde177b833ff79e78dcc21c", + "status": "error" +} +``` + + +#### Put port in progress (sent to losing carrier) + +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/scheduled + +```shell +curl -v -X PATCH \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/scheduled +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630107130, + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} }, - "transfer_date": 63598114800, - "port_state": "submitted", + "port_state": "scheduled", + "sent": false, + "updated": 63630120528, + "uploads": { + "file.pdf": { + "content_type": "application/pdf", + "length": 90931 + } + } + }, + "request_id": "9942dcff784138431b268b6bd140862f", + "revision": "16-1377609b9166abf7a5dbb6b25a5a3890", + "status": "success" +} +``` + + +#### Complete port, numbers will activate in the system, account will be billed + +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/completed + +```shell +curl -v -X PATCH \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/completed +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630107130, + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", "numbers": { - "{{NUMBER}}": {} + "+12025559000": {} }, + "port_state": "completed", "sent": false, + "updated": 63630120570, "uploads": { - "loa.pdf": { + "file.pdf": { "content_type": "application/pdf", - "length": 59196 - }, - "bill.pdf": { + "length": 90931 + } + } + }, + "request_id": "b760e5f682423a3e7224b1afc21e0f48", + "revision": "1-8edacf2b310d0fd1e919fda51ed10306", + "status": "success" +} +``` + + +#### Reject a port + +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/rejected + +```shell +curl -v -X PATCH \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/rejected +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630107130, + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} + }, + "port_state": "rejected", + "sent": false, + "updated": 63630120570, + "uploads": { + "file.pdf": { "content_type": "application/pdf", - "length": 8304 + "length": 90931 } + } + }, + "request_id": "8edacf2b310d0fd1e919fda51ed10306", + "revision": "17-84f0e12cfa1b4227e3a324286f5e067b", + "status": "success" +} +``` + + +#### Cancel a port + +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/canceled + +```shell +curl -v -X PATCH \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/canceled +``` + +```json +{ + "auth_token": "{AUTH_TOKEN}", + "data": { + "created": 63630107130, + "id": "{PORTREQUEST_ID}", + "name": "Porting 202.555.9000", + "numbers": { + "+12025559000": {} }, - "updated": 63597642011, - "created": 63597642009, - "id": "84e0a824c6b74fe1e3ec48962a600ef2" - }], - "status": "success" - } + "port_state": "canceled", + "sent": false, + "updated": 63630120578, + "uploads": { + "file.pdf": { + "content_type": "application/pdf", + "length": 90931 + } + } + }, + "request_id": "cb1cff969c1513d8a21df2e4c5bbe341", + "revision": "18-82cca84c2b3d9019309a68f1d95c2d0c", + "status": "success" +} +``` + + +#### Build an LOA PDF from a port request + +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/loa + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + -H "Accept: application/x-pdf" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/loa +``` + +Streams back the contents of the generated Letter Of Authorization PDF. diff --git a/applications/crossbar/doc/ref/port_requests.md b/applications/crossbar/doc/ref/port_requests.md index 845c0f78891..683e54447b8 100644 --- a/applications/crossbar/doc/ref/port_requests.md +++ b/applications/crossbar/doc/ref/port_requests.md @@ -47,32 +47,32 @@ curl -v -X PUT \ #### Remove -> DELETE /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID} +> DELETE /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} ```curl curl -v -X DELETE \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID} + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} ``` #### Fetch -> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID} +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} ```curl curl -v -X GET \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID} + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} ``` #### Change -> POST /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID} +> POST /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} ```curl curl -v -X POST \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID} + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID} ``` #### Fetch @@ -137,121 +137,121 @@ curl -v -X GET \ #### Fetch -> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/loa +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/loa ```curl curl -v -X GET \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/loa + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/loa ``` #### Fetch -> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments ```curl curl -v -X GET \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments ``` #### Create -> PUT /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments +> PUT /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments ```curl curl -v -X PUT \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments ``` #### Patch -> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/canceled +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/canceled ```curl curl -v -X PATCH \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/canceled + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/canceled ``` #### Patch -> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/rejected +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/rejected ```curl curl -v -X PATCH \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/rejected + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/rejected ``` #### Patch -> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/completed +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/completed ```curl curl -v -X PATCH \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/completed + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/completed ``` #### Patch -> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/scheduled +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/scheduled ```curl curl -v -X PATCH \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/scheduled + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/scheduled ``` #### Patch -> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/pending +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/pending ```curl curl -v -X PATCH \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/pending + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/pending ``` #### Patch -> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/submitted +> PATCH /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/submitted ```curl curl -v -X PATCH \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/submitted + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/submitted ``` #### Remove -> DELETE /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments/{ATTACHMENT_ID} +> DELETE /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} ```curl curl -v -X DELETE \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments/{ATTACHMENT_ID} + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} ``` #### Fetch -> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments/{ATTACHMENT_ID} +> GET /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} ```curl curl -v -X GET \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments/{ATTACHMENT_ID} + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} ``` #### Change -> POST /v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments/{ATTACHMENT_ID} +> POST /v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} ```curl curl -v -X POST \ -H "X-Auth-Token: {AUTH_TOKEN}" \ - http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}/attachments/{ATTACHMENT_ID} + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID} ``` diff --git a/applications/crossbar/priv/couchdb/swagger/swagger.json b/applications/crossbar/priv/couchdb/swagger/swagger.json index dd5939fab6b..f3763dcd5a7 100644 --- a/applications/crossbar/priv/couchdb/swagger/swagger.json +++ b/applications/crossbar/priv/couchdb/swagger/swagger.json @@ -8070,6 +8070,41 @@ "/v2/accounts/{ACCOUNT_ID}/port_requests/submitted": { "get": {} }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}": { + "delete": {}, + "get": {}, + "post": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments": { + "get": {}, + "put": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/attachments/{ATTACHMENT_ID}": { + "delete": {}, + "get": {}, + "post": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/canceled": { + "patch": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/completed": { + "patch": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/loa": { + "get": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/pending": { + "patch": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/rejected": { + "patch": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/scheduled": { + "patch": {} + }, + "/v2/accounts/{ACCOUNT_ID}/port_requests/{PORTREQUEST_ID}/submitted": { + "patch": {} + }, "/v2/accounts/{ACCOUNT_ID}/port_requests/{_ID}": { "delete": {}, "get": {}, diff --git a/applications/crossbar/src/crossbar_maintenance.erl b/applications/crossbar/src/crossbar_maintenance.erl index ae6515c63a6..732b6d55e08 100644 --- a/applications/crossbar/src/crossbar_maintenance.erl +++ b/applications/crossbar/src/crossbar_maintenance.erl @@ -190,21 +190,20 @@ running_modules() -> crossbar_bindings:modules_loaded(). %% %% @end %%-------------------------------------------------------------------- --spec find_account_by_number(input_term()) -> - {'ok', ne_binary()} | - {'error', any()}. +-spec find_account_by_number(input_term()) -> {'ok', ne_binary()} | + {'error', any()}. find_account_by_number(Number) when not is_binary(Number) -> find_account_by_number(kz_util:to_binary(Number)); find_account_by_number(Number) -> case knm_number:lookup_account(Number) of {'ok', AccountId, _} -> - AccountDb = kz_util:format_account_id(AccountId, 'encoded'), + AccountDb = kz_util:format_account_db(AccountId), print_account_info(AccountDb, AccountId); {'error', {'not_in_service', AssignedTo}} -> - AccountDb = kz_util:format_account_id(AssignedTo, 'encoded'), + AccountDb = kz_util:format_account_db(AssignedTo), print_account_info(AccountDb, AssignedTo); {'error', {'account_disabled', AssignedTo}} -> - AccountDb = kz_util:format_account_id(AssignedTo, 'encoded'), + AccountDb = kz_util:format_account_db(AssignedTo), print_account_info(AccountDb, AssignedTo); {'error', Reason}=E -> io:format("failed to find account assigned to number '~s': ~p~n", [Number, Reason]), diff --git a/applications/crossbar/src/crossbar_services.erl b/applications/crossbar/src/crossbar_services.erl index 1806abb8fb9..38b5ca79fb4 100644 --- a/applications/crossbar/src/crossbar_services.erl +++ b/applications/crossbar/src/crossbar_services.erl @@ -13,6 +13,8 @@ ]). -include("crossbar.hrl"). +-include_lib("kazoo_number_manager/include/knm_phone_number.hrl"). %% PVT_FEATURES +-include_lib("kazoo_number_manager/include/knm_port_request.hrl"). %% PORT_KEY %%-------------------------------------------------------------------- %% @public @@ -214,11 +216,13 @@ calc_service_updates(Context, <<"port_request">>) -> JObj = cb_context:doc(Context), Numbers = kz_json:get_value(<<"numbers">>, JObj), PhoneNumbers = - kz_json:foldl( - fun port_request_foldl/3 - ,kz_json:new() - ,Numbers - ), + [kz_doc:set_id( + kz_json:set_value(?PVT_FEATURES, [?PORT_KEY + | kz_json:get_list_value(?PVT_FEATURES, NumberJObj, []) + ], NumberJObj) + ,NumberKey + ) + || {NumberKey, NumberJObj} <- kz_json:to_proplist(Numbers)], kz_service_phone_numbers:reconcile(Services, PhoneNumbers); calc_service_updates(Context, <<"app">>) -> [{<<"apps_store">>, [Id]} | _] = cb_context:req_nouns(Context), @@ -249,23 +253,6 @@ calc_service_updates(_Context, _Type, _Props) -> lager:warning("unknown type ~p, cannot execute dry run", [_Type]), 'undefined'. -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% @end -%%-------------------------------------------------------------------- --spec port_request_foldl(ne_binary(), kz_json:object(), kz_json:object()) -> kz_json:object(). -port_request_foldl(Number, NumberJObj, JObj) -> - kz_json:set_value( - Number - ,kz_json:set_value( - <<"features">> - ,[<<"port">>] - ,NumberJObj - ) - ,JObj - ). - %%-------------------------------------------------------------------- %% @private %% @doc diff --git a/applications/crossbar/src/modules/cb_port_requests.erl b/applications/crossbar/src/modules/cb_port_requests.erl index 18757520fc3..5614b20b7b3 100644 --- a/applications/crossbar/src/modules/cb_port_requests.erl +++ b/applications/crossbar/src/modules/cb_port_requests.erl @@ -1,5 +1,5 @@ %%%------------------------------------------------------------------- -%%% @copyright (C) 2013-2015, 2600Hz INC +%%% @copyright (C) 2013-2016, 2600Hz INC %%% @doc %%% %%% Handles port request life cycles @@ -23,7 +23,6 @@ ,post/2, post/4 ,delete/2, delete/4 ,cleanup/1 - ,find_template/1, find_template/2 ,authority/1 ,acceptable_content_types/0 @@ -148,33 +147,33 @@ allowed_methods(?PORT_PENDING) -> [?HTTP_GET]; allowed_methods(?PORT_SCHEDULED) -> [?HTTP_GET]; -allowed_methods(?PORT_COMPLETE) -> +allowed_methods(?PORT_COMPLETED) -> [?HTTP_GET]; -allowed_methods(?PORT_REJECT) -> +allowed_methods(?PORT_REJECTED) -> [?HTTP_GET]; allowed_methods(?PORT_CANCELED) -> [?HTTP_GET]; -allowed_methods(_Id) -> +allowed_methods(_PortRequestId) -> [?HTTP_GET, ?HTTP_POST, ?HTTP_DELETE]. -allowed_methods(_Id, ?PORT_SUBMITTED) -> +allowed_methods(_PortRequestId, ?PORT_SUBMITTED) -> [?HTTP_PATCH]; -allowed_methods(_Id, ?PORT_PENDING) -> +allowed_methods(_PortRequestId, ?PORT_PENDING) -> [?HTTP_PATCH]; -allowed_methods(_Id, ?PORT_SCHEDULED) -> +allowed_methods(_PortRequestId, ?PORT_SCHEDULED) -> [?HTTP_PATCH]; -allowed_methods(_Id, ?PORT_COMPLETE) -> +allowed_methods(_PortRequestId, ?PORT_COMPLETED) -> [?HTTP_PATCH]; -allowed_methods(_Id, ?PORT_REJECT) -> +allowed_methods(_PortRequestId, ?PORT_REJECTED) -> [?HTTP_PATCH]; -allowed_methods(_Id, ?PORT_CANCELED) -> +allowed_methods(_PortRequestId, ?PORT_CANCELED) -> [?HTTP_PATCH]; -allowed_methods(_Id, ?PORT_ATTACHMENT) -> +allowed_methods(_PortRequestId, ?PORT_ATTACHMENT) -> [?HTTP_GET, ?HTTP_PUT]; -allowed_methods(_Id, ?PATH_TOKEN_LOA) -> +allowed_methods(_PortRequestId, ?PATH_TOKEN_LOA) -> [?HTTP_GET]. -allowed_methods(_Id, ?PORT_ATTACHMENT, _AttachmentId) -> +allowed_methods(_PortRequestId, ?PORT_ATTACHMENT, _AttachmentId) -> [?HTTP_GET, ?HTTP_POST, ?HTTP_DELETE]. %%-------------------------------------------------------------------- @@ -192,19 +191,19 @@ allowed_methods(_Id, ?PORT_ATTACHMENT, _AttachmentId) -> -spec resource_exists(path_token(), path_token(), path_token()) -> 'true'. resource_exists() -> 'true'. -resource_exists(_Id) -> 'true'. +resource_exists(_PortRequestId) -> 'true'. -resource_exists(_Id, ?PORT_SUBMITTED) -> 'true'; -resource_exists(_Id, ?PORT_PENDING) -> 'true'; -resource_exists(_Id, ?PORT_SCHEDULED) -> 'true'; -resource_exists(_Id, ?PORT_COMPLETE) -> 'true'; -resource_exists(_Id, ?PORT_REJECT) -> 'true'; -resource_exists(_Id, ?PORT_CANCELED) -> 'true'; -resource_exists(_Id, ?PORT_ATTACHMENT) -> 'true'; -resource_exists(_Id, ?PATH_TOKEN_LOA) -> 'true'; -resource_exists(_Id, _Unknown) -> 'false'. +resource_exists(_PortRequestId, ?PORT_SUBMITTED) -> 'true'; +resource_exists(_PortRequestId, ?PORT_PENDING) -> 'true'; +resource_exists(_PortRequestId, ?PORT_SCHEDULED) -> 'true'; +resource_exists(_PortRequestId, ?PORT_COMPLETED) -> 'true'; +resource_exists(_PortRequestId, ?PORT_REJECTED) -> 'true'; +resource_exists(_PortRequestId, ?PORT_CANCELED) -> 'true'; +resource_exists(_PortRequestId, ?PORT_ATTACHMENT) -> 'true'; +resource_exists(_PortRequestId, ?PATH_TOKEN_LOA) -> 'true'; +resource_exists(_PortRequestId, _) -> 'false'. -resource_exists(_Id, ?PORT_ATTACHMENT, _AttachmentId) -> 'true'. +resource_exists(_PortRequestId, ?PORT_ATTACHMENT, _AttachmentId) -> 'true'. %%-------------------------------------------------------------------- %% @public @@ -294,9 +293,9 @@ validate(Context, ?PORT_PENDING = Type) -> validate_load_summary(Context, Type); validate(Context, ?PORT_SCHEDULED = Type) -> validate_load_summary(Context, Type); -validate(Context, ?PORT_COMPLETE = Type) -> +validate(Context, ?PORT_COMPLETED = Type) -> validate_load_summary(Context, Type); -validate(Context, ?PORT_REJECT = Type) -> +validate(Context, ?PORT_REJECTED = Type) -> validate_load_summary(Context, Type); validate(Context, ?PORT_CANCELED = Type) -> validate_load_summary(Context, Type); @@ -311,10 +310,10 @@ validate(Context, Id, ?PORT_PENDING) -> validate_port_request(Context, Id, ?PORT_PENDING, cb_context:req_verb(Context)); validate(Context, Id, ?PORT_SCHEDULED) -> validate_port_request(Context, Id, ?PORT_SCHEDULED, cb_context:req_verb(Context)); -validate(Context, Id, ?PORT_COMPLETE) -> - validate_port_request(Context, Id, ?PORT_COMPLETE, cb_context:req_verb(Context)); -validate(Context, Id, ?PORT_REJECT) -> - validate_port_request(Context, Id, ?PORT_REJECT, cb_context:req_verb(Context)); +validate(Context, Id, ?PORT_COMPLETED) -> + validate_port_request(Context, Id, ?PORT_COMPLETED, cb_context:req_verb(Context)); +validate(Context, Id, ?PORT_REJECTED) -> + validate_port_request(Context, Id, ?PORT_REJECTED, cb_context:req_verb(Context)); validate(Context, Id, ?PORT_CANCELED) -> validate_port_request(Context, Id, ?PORT_CANCELED, cb_context:req_verb(Context)); validate(Context, Id, ?PORT_ATTACHMENT) -> @@ -328,7 +327,6 @@ validate(Context, Id, ?PORT_ATTACHMENT, AttachmentId) -> %%-------------------------------------------------------------------- %% @public %% @doc -%% If the HTTP verb is PUT, execute the actual action, usually a db save. %% @end %%-------------------------------------------------------------------- -spec get(cb_context:context(), path_token(), path_token()) -> cb_context:context(). @@ -345,30 +343,26 @@ get(Context, Id, ?PATH_TOKEN_LOA) -> -spec put(cb_context:context()) -> cb_context:context(). -spec put(cb_context:context(), path_token(), path_token()) -> cb_context:context(). put(Context) -> - crossbar_doc:save( - update_port_request_for_save(Context) - ). + crossbar_doc:save(update_port_request_for_save(Context)). -spec update_port_request_for_save(cb_context:context()) -> cb_context:context(). +-spec update_port_request_for_save(cb_context:context(), kz_json:object()) -> cb_context:context(). update_port_request_for_save(Context) -> + update_port_request_for_save(Context, cb_context:doc(Context)). +update_port_request_for_save(Context, Doc) -> + NewDoc = kz_account:set_tree(Doc, kz_account:tree(cb_context:account_doc(Context))), cb_context:setters(Context ,[{fun cb_context:set_account_db/2, ?KZ_PORT_REQUESTS_DB} - ,{fun cb_context:set_doc/2, add_pvt_fields(Context, cb_context:doc(Context))} + ,{fun cb_context:set_doc/2, NewDoc} ] ). --spec add_pvt_fields(cb_context:context(), kz_json:object()) -> - kz_json:object(). -add_pvt_fields(Context, PortRequest) -> - Tree = kz_account:tree(cb_context:account_doc(Context)), - kz_json:set_value(<<"pvt_tree">>, Tree, PortRequest). - put(Context, Id, ?PORT_ATTACHMENT) -> [{Filename, FileJObj}] = cb_context:req_files(Context), Contents = kz_json:get_value(<<"contents">>, FileJObj), - CT = kz_json:get_string_value([<<"headers">>, <<"content_type">>], FileJObj), + CT = kz_json:get_value([<<"headers">>, <<"content_type">>], FileJObj), Opts = [{'content_type', CT} | ?TYPE_CHECK_OPTION(<<"port_request">>)], crossbar_doc:save_attachment(Id @@ -381,69 +375,42 @@ put(Context, Id, ?PORT_ATTACHMENT) -> %%-------------------------------------------------------------------- %% @public %% @doc -%% If the HTTP verb is POST, execute the actual action, usually a db save -%% (after a merge perhaps). %% @end %%-------------------------------------------------------------------- -spec patch(cb_context:context(), path_token(), path_token()) -> cb_context:context(). patch(Context, Id, ?PORT_SUBMITTED) -> - Callback = - fun() -> - Context1 = do_patch(Context, Id), - case cb_context:resp_status(Context1) of - 'success' -> - send_port_notification(Context1, Id, ?PORT_SUBMITTED); - _ -> Context1 - end - end, + Callback = fun() -> patch_then_notify(Context, Id, ?PORT_SUBMITTED) end, crossbar_services:maybe_dry_run(Context, Callback); patch(Context, Id, ?PORT_PENDING) -> - Context1 = do_patch(Context, Id), - case cb_context:resp_status(Context1) of - 'success' -> - send_port_notification(Context1, Id, ?PORT_PENDING); - _ -> Context1 - end; + patch_then_notify(Context, Id, ?PORT_PENDING); patch(Context, Id, ?PORT_SCHEDULED) -> - Context1 = do_patch(Context, Id), - case cb_context:resp_status(Context1) of - 'success' -> - send_port_notification(Context1, Id, ?PORT_SCHEDULED); - _ -> Context1 - end; -patch(Context, Id, ?PORT_COMPLETE) -> - Context1 = do_patch(Context, Id), - case cb_context:resp_status(Context1) of - 'success' -> - send_port_notification(Context1, Id, ?PORT_COMPLETE); - _ -> Context1 - end; -patch(Context, Id, ?PORT_REJECT) -> - Context1 = do_patch(Context, Id), - case cb_context:resp_status(Context1) of - 'success' -> - send_port_notification(Context1, Id, ?PORT_REJECT); - _ -> Context1 - end; + patch_then_notify(Context, Id, ?PORT_SCHEDULED); +patch(Context, Id, ?PORT_COMPLETED) -> + patch_then_notify(Context, Id, ?PORT_COMPLETED); +patch(Context, Id, ?PORT_REJECTED) -> + patch_then_notify(Context, Id, ?PORT_REJECTED); patch(Context, Id, ?PORT_CANCELED) -> - Context1 = do_patch(Context, Id), + patch_then_notify(Context, Id, ?PORT_CANCELED). + +%% @private +-spec patch_then_notify(cb_context:context(), path_token(), path_token()) -> cb_context:context(). +patch_then_notify(Context, PortId, PortState) -> + Context1 = do_patch(Context), case cb_context:resp_status(Context1) of 'success' -> - send_port_notification(Context1, Id, ?PORT_CANCELED); + send_port_notification(Context1, PortId, PortState); _ -> Context1 end. --spec do_patch(cb_context:context(), path_token()) -> cb_context:context(). -do_patch(Context, _Id) -> +%% @private +-spec do_patch(cb_context:context()) -> cb_context:context(). +do_patch(Context) -> UpdatedDoc = kz_json:merge_recursive( cb_context:doc(Context) ,kz_json:public_fields(cb_context:req_data(Context)) ), - Setters = [fun update_port_request_for_save/1 - ,{fun cb_context:set_doc/2, UpdatedDoc} - ], - Context1 = crossbar_doc:save(cb_context:setters(Context, Setters)), + Context1 = crossbar_doc:save(update_port_request_for_save(Context, UpdatedDoc)), case cb_context:resp_status(Context1) of 'success' -> cb_context:set_resp_data( @@ -464,12 +431,20 @@ do_patch(Context, _Id) -> -spec post(cb_context:context(), path_token()) -> cb_context:context(). -spec post(cb_context:context(), path_token(), path_token(), path_token()) -> cb_context:context(). post(Context, Id) -> - do_post(Context, Id). + Context1 = crossbar_doc:save(update_port_request_for_save(Context)), + case cb_context:resp_status(Context1) of + 'success' -> + _ = maybe_send_port_comment_notification(Context1, Id), + Doc = cb_context:doc(Context1), + cb_context:set_resp_data(Context1, knm_port_request:public_fields(Doc)); + _Status -> + Context1 + end. post(Context, Id, ?PORT_ATTACHMENT, AttachmentId) -> [{_Filename, FileJObj}] = cb_context:req_files(Context), Contents = kz_json:get_value(<<"contents">>, FileJObj), - CT = kz_json:get_string_value([<<"headers">>, <<"content_type">>], FileJObj), + CT = kz_json:get_value([<<"headers">>, <<"content_type">>], FileJObj), Opts = [{'content_type', CT} | ?TYPE_CHECK_OPTION(<<"port_request">>)], case kz_doc:attachment(cb_context:doc(Context), AttachmentId) of @@ -485,20 +460,6 @@ post(Context, Id, ?PORT_ATTACHMENT, AttachmentId) -> ,Opts ). --spec do_post(cb_context:context(), path_token()) -> cb_context:context(). -do_post(Context, Id) -> - Context1 = - crossbar_doc:save( - update_port_request_for_save(Context) - ), - case cb_context:resp_status(Context1) of - 'success' -> - _ = maybe_send_port_comment_notification(Context1, Id), - cb_context:set_resp_data(Context1, knm_port_request:public_fields(cb_context:doc(Context1))); - _Status -> - Context1 - end. - %%-------------------------------------------------------------------- %% @public %% @doc @@ -536,7 +497,7 @@ load_port_request(Context, Id) -> %%-------------------------------------------------------------------- -spec validate_load_summary(cb_context:context(), ne_binary()) -> cb_context:context(). -validate_load_summary(Context, ?PORT_COMPLETE = Type) -> +validate_load_summary(Context, ?PORT_COMPLETED = Type) -> case cb_modules_util:range_view_options(Context, ?MAX_RANGE, <<"modified">>) of {From, To} -> load_summary_by_range(Context, Type, From, To); Context1 -> Context1 @@ -583,10 +544,10 @@ validate_port_request(Context, Id, ?PORT_PENDING, ?HTTP_PATCH) -> maybe_move_state(Context, Id, ?PORT_PENDING); validate_port_request(Context, Id, ?PORT_SCHEDULED, ?HTTP_PATCH) -> maybe_move_state(Context, Id, ?PORT_SCHEDULED); -validate_port_request(Context, Id, ?PORT_COMPLETE, ?HTTP_PATCH) -> - maybe_move_state(Context, Id, ?PORT_COMPLETE); -validate_port_request(Context, Id, ?PORT_REJECT, ?HTTP_PATCH) -> - maybe_move_state(Context, Id, ?PORT_REJECT); +validate_port_request(Context, Id, ?PORT_COMPLETED, ?HTTP_PATCH) -> + maybe_move_state(Context, Id, ?PORT_COMPLETED); +validate_port_request(Context, Id, ?PORT_REJECTED, ?HTTP_PATCH) -> + maybe_move_state(Context, Id, ?PORT_REJECTED); validate_port_request(Context, Id, ?PORT_CANCELED, ?HTTP_PATCH) -> maybe_move_state(Context, Id, ?PORT_CANCELED). @@ -626,14 +587,12 @@ validate_attachment(Context, Id, AttachmentId, ?HTTP_DELETE) -> is_deletable(Context) -> is_deletable(Context, knm_port_request:current_state(cb_context:doc(Context))). is_deletable(Context, ?PORT_UNCONFIRMED) -> Context; -is_deletable(Context, ?PORT_REJECT) -> Context; +is_deletable(Context, ?PORT_REJECTED) -> Context; is_deletable(Context, ?PORT_CANCELED) -> Context; is_deletable(Context, _PortState) -> lager:debug("port is in state ~s, can't modify", [_PortState]), - cb_context:add_system_error('invalid_method' - ,<<"port request is not modifiable in this state">> - ,Context - ). + Msg = <<"port request is not modifiable in this state">>, + cb_context:add_system_error('invalid_method', Msg, Context). %%-------------------------------------------------------------------- %% @private @@ -713,7 +672,7 @@ load_summary_by_range(Context, From, To) -> lists:foldl(fun(?PORT_SUBMITTED=Type, C) -> load_summary_fold(C, Type); (?PORT_PENDING=Type, C) -> load_summary_fold(C, Type); (?PORT_SCHEDULED=Type, C) -> load_summary_fold(C, Type); - (?PORT_REJECT=Type, C) -> load_summary_fold(C, Type); + (?PORT_REJECTED=Type, C) -> load_summary_fold(C, Type); (Type, C) -> load_summary_by_range_fold(C, Type, From, To) end, @@ -771,9 +730,33 @@ load_summary_by_range_fold(Context, Type, From, To) -> -spec load_summary_by_number(cb_context:context(), ne_binary()) -> cb_context:context(). load_summary_by_number(Context, Number) -> - case should_summarize_descendant_requests(Context) of - 'true' -> summary_descendants_by_number(Context, Number); - 'false' -> summary_by_number(Context, Number) + ViewOptions = [{'keys', build_keys(Context, Number)} + ,'include_docs' + ], + crossbar_doc:load_view( + ?ALL_PORT_REQ_NUMBERS + ,ViewOptions + ,cb_context:set_account_db(Context, ?KZ_PORT_REQUESTS_DB) + ,fun normalize_view_results/2 + ). + +-spec build_keys(cb_context:context(), ne_binary()) -> [ne_binaries()]. +build_keys(Context, Number) -> + E164 = knm_converters:normalize(Number), + case props:get_value(<<"accounts">>, cb_context:req_nouns(Context)) of + [AccountId] -> + [[AccountId, E164]]; + [AccountId, ?PORT_DESCENDANTS] -> + ViewOptions = [{'startkey', [AccountId]} + ,{'endkey', [AccountId, kz_json:new()]} + ], + case kz_datamgr:get_results(?KZ_ACCOUNTS_DB, ?AGG_VIEW_DESCENDANTS, ViewOptions) of + {'error', _R} -> + lager:error("failed to query view ~p", [_R]), + []; + {'ok', JObjs} -> + lists:reverse([[kz_doc:id(JObj), E164] || JObj <- JObjs]) + end end. -spec load_summary(cb_context:context(), crossbar_doc:view_options()) -> @@ -842,76 +825,6 @@ should_summarize_descendant_requests(Context) -> _Params -> 'false' end. --spec summary_by_number(cb_context:context(), ne_binary()) -> - cb_context:context(). -summary_by_number(Context, Number) -> - ViewOptions = [{'keys', build_keys(Context, Number)} - ,'include_docs' - ], - crossbar_doc:load_view( - ?ALL_PORT_REQ_NUMBERS - ,ViewOptions - ,cb_context:set_account_db(Context, ?KZ_PORT_REQUESTS_DB) - ,fun normalize_view_results/2 - ). - --spec summary_descendants_by_number(cb_context:context(), ne_binary()) -> - cb_context:context(). -summary_descendants_by_number(Context, Number) -> - ViewOptions = [{'keys', build_keys(Context, Number)} - ,'include_docs' - ], - crossbar_doc:load_view( - ?ALL_PORT_REQ_NUMBERS - ,ViewOptions - ,cb_context:set_account_db(Context, ?KZ_PORT_REQUESTS_DB) - ,fun normalize_view_results/2 - ). - --type descendant_keys() :: [ne_binaries()]. - --spec build_keys(cb_context:context(), ne_binary()) -> - descendant_keys(). -build_keys(Context, Number) -> - build_keys_from_account( - knm_converters:normalize(Number) - ,props:get_value(<<"accounts">>, cb_context:req_nouns(Context)) - ). - --spec build_keys_from_account(ne_binary(), ne_binaries()) -> - descendant_keys(). -build_keys_from_account(E164, [AccountId]) -> - [[AccountId, E164]]; -build_keys_from_account(E164, [AccountId, ?PORT_DESCENDANTS]) -> - ViewOptions = [{'startkey', [AccountId]} - ,{'endkey', [AccountId, kz_json:new()]} - ], - case kz_datamgr:get_results( - ?KZ_ACCOUNTS_DB - ,?AGG_VIEW_DESCENDANTS - ,ViewOptions - ) - of - {'error', _R} -> - lager:error("failed to query view ~p", [_R]), - []; - {'ok', JObjs} -> - lists:foldl( - fun(JObj, Acc) -> - build_descendant_key(JObj, Acc, E164) - end - ,[] - ,JObjs - ) - end. - --spec build_descendant_key(kz_json:object(), descendant_keys(), ne_binary()) -> - descendant_keys(). -build_descendant_key(JObj, Acc, E164) -> - [[kz_doc:id(JObj), E164] - |Acc - ]. - %%-------------------------------------------------------------------- %% @private %% @doc @@ -950,9 +863,7 @@ leak_pvt_fields(Res, JObj) -> summary_attachments(Context, Id) -> Context1 = load_port_request(Context, Id), As = kz_doc:attachments(cb_context:doc(Context1), kz_json:new()), - cb_context:set_resp_data(Context1 - ,knm_port_request:normalize_attachments(As) - ). + cb_context:set_resp_data(Context1, knm_port_request:normalize_attachments(As)). %%-------------------------------------------------------------------- %% @private @@ -1017,7 +928,7 @@ can_update_port_request(Context) -> can_update_port_request(_Context, ?PORT_UNCONFIRMED) -> 'true'; -can_update_port_request(_Context, ?PORT_REJECT) -> +can_update_port_request(_Context, ?PORT_REJECTED) -> 'true'; can_update_port_request(Context, _) -> cb_modules_util:is_superduper_admin(cb_context:auth_account_id(Context)). @@ -1031,7 +942,7 @@ can_update_port_request(Context, _) -> successful_validation(Context, 'undefined') -> Normalized = knm_port_request:normalize_numbers(cb_context:doc(Context)), Unconf = [{<<"pvt_type">>, <<"port_request">>} - ,{?PORT_PVT_STATE, ?PORT_UNCONFIRMED} + ,{?PORT_PVT_STATE, ?PORT_UNCONFIRMED} ], cb_context:set_doc(Context, kz_json:set_values(Unconf, Normalized)); successful_validation(Context, _Id) -> @@ -1056,39 +967,35 @@ check_number_portability(PortId, Number, Context) -> {'ok', [PortReq]} -> check_number_portability(PortId, Number, Context, E164, PortReq); {'ok', [_|_]=_PortReqs} -> - Message = <<"Number is currently on multiple port requests. Contact a system admin to rectify">>, - lager:debug("number ~s(~s) exists on multiple port request docs. That's bad!", [E164, Number]), - number_validation_error(Context, Number, Message); + lager:debug("number ~s(~s) exists on multiple port request docs. That's bad!", + [E164, Number]), + Msg = <<"Number is currently on multiple port requests. Contact a system admin to rectify">>, + number_validation_error(Context, Number, Msg); {'error', _E} -> - Message = <<"Failed to query back-end services, cannot port at this time">>, lager:debug("failed to query the port request view: ~p", [_E]), + Message = <<"Failed to query back-end services, cannot port at this time">>, number_validation_error(Context, Number, Message) end. check_number_portability(PortId, Number, Context, E164, PortReq) -> case {kz_json:get_value(<<"value">>, PortReq) =:= cb_context:account_id(Context) - ,kz_doc:id(PortReq) =:= PortId + ,kz_doc:id(PortReq) =:= PortId } of {'true', 'true'} -> - lager:debug( - "number ~s(~s) is on this existing port request for this account(~s)" - ,[E164, Number, cb_context:account_id(Context)] - ), + lager:debug("number ~s(~s) is on this existing port request for this account(~s)" + ,[E164, Number, cb_context:account_id(Context)]), cb_context:set_resp_status(Context, 'success'); {'true', 'false'} -> - lager:debug( - "number ~s(~s) is on a different port request in this account(~s): ~s" - ,[E164, Number, cb_context:account_id(Context), kz_doc:id(PortReq)] - ), + lager:debug("number ~s(~s) is on a different port request in this account(~s): ~s" + ,[E164, Number, cb_context:account_id(Context), kz_doc:id(PortReq)]), Message = <<"Number is on a port request already: ", (kz_doc:id(PortReq))/binary>>, number_validation_error(Context, Number, Message); {'false', _} -> - lager:debug( - "number ~s(~s) is on existing port request for other account(~s)" - ,[E164, Number, kz_json:get_value(<<"value">>, PortReq)] - ), - number_validation_error(Context, Number, <<"Number is being ported for a different account">>) + lager:debug("number ~s(~s) is on existing port request for other account(~s)" + ,[E164, Number, kz_json:get_value(<<"value">>, PortReq)]), + Message = <<"Number is being ported for a different account">>, + number_validation_error(Context, Number, Message) end. -spec number_validation_error(cb_context:context(), ne_binary(), ne_binary()) -> @@ -1111,6 +1018,9 @@ check_number_existence(E164, Number, Context) -> {'ok', _AccountId, _} -> lager:debug("number ~s exists and belongs to ~s", [E164, _AccountId]), number_validation_error(Context, Number, <<"Number exists on the system already">>); + {'error', {'not_in_service', _AccountId}} -> + lager:debug("number ~s exists and belongs to ~s", [E164, _AccountId]), + number_validation_error(Context, Number, <<"Number exists on the system already">>); {'error', 'not_found'} -> lager:debug("number ~s not found in numbers db (portable!)", [E164]), cb_context:set_resp_status(Context, 'success'); @@ -1209,21 +1119,18 @@ generate_loa(Context, _RespStatus) -> %% @doc %% @end %%-------------------------------------------------------------------- --spec find_template(ne_binary()) -> ne_binary(). -spec find_template(ne_binary(), api_binary()) -> ne_binary(). -find_template(ResellerId) -> - {'ok', Template} = kz_pdf:find_template(ResellerId, <<"loa">>), - Template. - find_template(ResellerId, 'undefined') -> - find_template(ResellerId); + {'ok', Template} = kz_pdf:find_template(ResellerId, <<"loa">>), + Template; find_template(ResellerId, CarrierName) -> - TemplateName = <<(kz_util:to_lower_binary(kz_util:uri_encode(CarrierName)))/binary, ".tmpl">>, + EncodedCarrierName = kz_util:to_lower_binary(kz_util:uri_encode(CarrierName)), + TemplateName = <>, lager:debug("looking for carrier template ~s or plain template for reseller ~s" ,[TemplateName, ResellerId] ), case kz_pdf:find_template(ResellerId, <<"loa">>, TemplateName) of - {'error', _} -> find_template(ResellerId); + {'error', _} -> find_template(ResellerId, 'undefined'); {'ok', Template} -> Template end. @@ -1279,9 +1186,9 @@ send_port_notification(Context, Id, ?PORT_PENDING=State) -> send_port_notification(Context, Id, State, fun send_port_pending_notification/2); send_port_notification(Context, Id, ?PORT_SCHEDULED=State) -> send_port_notification(Context, Id, State, fun send_port_scheduled_notification/2); -send_port_notification(Context, Id, ?PORT_COMPLETE=State) -> +send_port_notification(Context, Id, ?PORT_COMPLETED=State) -> send_port_notification(Context, Id, State, fun send_ported_notification/2); -send_port_notification(Context, Id, ?PORT_REJECT=State) -> +send_port_notification(Context, Id, ?PORT_REJECTED=State) -> send_port_notification(Context, Id, State, fun send_port_rejected_notification/2); send_port_notification(Context, Id, ?PORT_CANCELED=State) -> _ = remove_from_phone_numbers_doc(Context), @@ -1546,33 +1453,19 @@ save_phone_numbers_doc(Context, JObj) -> cb_context:context(). generate_loa_from_port(Context, PortRequest) -> AccountId = cb_context:account_id(Context), - ResellerId = kz_services:find_reseller_id(AccountId), ResellerDoc = cb_context:account_doc(cb_context:set_account_id(Context, ResellerId)), + TemplateData = props:filter_undefined( + [{<<"reseller">>, kz_json:to_proplist(ResellerDoc)} + ,{<<"account">>, kz_json:to_proplist(cb_context:account_doc(Context))} + ,{<<"numbers">>, [knm_util:pretty_print(N) || N <- kz_json:get_keys(<<"numbers">>, PortRequest)]} + ,{<<"bill">>, kz_json:to_proplist(kz_json:get_value(<<"bill">>, PortRequest, kz_json:new()))} + ,{<<"request">>, kz_json:to_proplist(PortRequest)} + ,{<<"qr_code">>, create_QR_code(AccountId, kz_doc:id(PortRequest))} + ,{<<"type">>, <<"loa">>} + ]), + Carrier = kz_json:get_value(<<"carrier">>, PortRequest), - AccountDoc = cb_context:account_doc(Context), - - Numbers = [knm_util:pretty_print(N) || N <- kz_json:get_keys(<<"numbers">>, PortRequest)], - - QRCode = create_qr_code(cb_context:account_id(Context), kz_doc:id(PortRequest)), - - generate_loa_from_template(Context - ,props:filter_undefined( - [{<<"reseller">>, kz_json:to_proplist(ResellerDoc)} - ,{<<"account">>, kz_json:to_proplist(AccountDoc)} - ,{<<"numbers">>, Numbers} - ,{<<"bill">>, kz_json:to_proplist(kz_json:get_value(<<"bill">>, PortRequest, kz_json:new()))} - ,{<<"request">>, kz_json:to_proplist(PortRequest)} - ,{<<"qr_code">>, QRCode} - ,{<<"type">>, <<"loa">>} - ]) - ,ResellerId - ,kz_json:get_value(<<"carrier">>, PortRequest) - ). - --spec generate_loa_from_template(cb_context:context(), kz_proplist(), ne_binary(), api_binary()) -> - cb_context:context(). -generate_loa_from_template(Context, TemplateData, ResellerId, Carrier) -> Template = find_template(ResellerId, Carrier), case kz_pdf:generate(ResellerId, TemplateData, Template) of {'error', _R} -> cb_context:set_resp_status(Context, 'error'); @@ -1583,17 +1476,19 @@ generate_loa_from_template(Context, TemplateData, ResellerId, Carrier) -> ) end. --spec create_qr_code(api_binary(), api_binary()) -> kz_proplist() | 'undefined'. -create_qr_code('undefined', _) -> 'undefined'; -create_qr_code(_, 'undefined') -> 'undefined'; -create_qr_code(AccountId, PortRequestId) -> +-spec create_QR_code(api_binary(), api_binary()) -> kz_proplist() | 'undefined'. +create_QR_code('undefined', _) -> 'undefined'; +create_QR_code(_, 'undefined') -> 'undefined'; +create_QR_code(AccountId, PortRequestId) -> lager:debug("create qr code for ~s - ~s", [AccountId, PortRequestId]), - CHL = <>, - Url = <<"https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=", CHL/binary, "&choe=UTF-8">>, + CHL = [binary_to_list(AccountId), "-", binary_to_list(PortRequestId)], + Url = ["https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=", CHL, "&choe=UTF-8"], - case kz_http:get(kz_util:to_list(Url)) of + case kz_http:get(lists:flatten(Url)) of {'ok', 200, _RespHeaders, RespBody} -> - lager:debug("generated QR code from ~s: ~s", [Url, RespBody]), + lager:debug("generated QR code from ~s", [Url]), + lager:debug("QR code size: ~p, head: ~w" + ,[byte_size(RespBody), binary:part(RespBody, 0, min(50, byte_size(RespBody)))]), [{<<"image">>, base64:encode(RespBody)}]; _E -> lager:debug("failed to generate QR code: ~p", [_E]), diff --git a/core/kazoo/src/kz_pdf.erl b/core/kazoo/src/kz_pdf.erl index 6b375f7f809..0ae5ce6a6dd 100644 --- a/core/kazoo/src/kz_pdf.erl +++ b/core/kazoo/src/kz_pdf.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- -%%% @copyright (C) 2011-2015 2600Hz, INC +%%% @copyright (C) 2011-2016 2600Hz, INC %%% @doc -%%% Builds an LOA doc using HTMLDoc (http://www.msweet.org/projects.php?Z1) +%%% Builds PDF from an HTML template using HTMLDoc (http://www.msweet.org/projects.php?Z1) %%% @end %%% @contributors: %%% Peter Defebvre @@ -30,21 +30,23 @@ -define(PDF_CONFIG_CAT, <<(?CONFIG_CAT)/binary, ".pdf">>). -define(HTML_TO_PDF, <<"/usr/bin/htmldoc --quiet --webpage -f $pdf$ $html$">>). +-type ret() :: {'ok', ne_binary()} | {'error', any()}. + %%-------------------------------------------------------------------- %% @public %% @doc %% @end %%-------------------------------------------------------------------- --spec find_template(ne_binary(), kz_proplist() | ne_binary()) -> {'ok', ne_binary()} | {'error', any()}. +-spec find_template(ne_binary(), kz_proplist() | ne_binary()) -> ret(). find_template(AccountId, DocType) when is_binary(DocType) -> find_template(AccountId, DocType, <>); find_template(AccountId, Props) -> DocType = props:get_first_defined([<<"type">>, <<"pvt_type">>], Props), find_template(AccountId, Props, <>). --spec find_template(ne_binary(), kz_proplist() | ne_binary(), ne_binary()) -> {'ok', ne_binary()} | {'error', any()}. +-spec find_template(ne_binary(), ne_binary() | kz_proplist(), ne_binary()) -> ret(). find_template(AccountId, DocType, AttachmentId) when is_binary(DocType) -> - AccountDb = kz_util:format_account_id(AccountId, 'encoded'), + AccountDb = kz_util:format_account_db(AccountId), case kz_datamgr:fetch_attachment(AccountDb, ?TEMPLATE_DOC_ID(DocType), AttachmentId) of {'ok', _}=OK -> OK; {'error', _R} -> @@ -61,8 +63,8 @@ find_template(AccountId, Props, AttachmentId) -> %% @doc %% @end %%-------------------------------------------------------------------- --spec generate(ne_binary(), kz_proplist()) -> {'ok', ne_binary()} | {'error', any()}. --spec generate(ne_binary(), kz_proplist(), ne_binary()) -> {'ok', ne_binary()} | {'error', any()}. +-spec generate(ne_binary(), kz_proplist()) -> ret(). +-spec generate(ne_binary(), kz_proplist(), ne_binary()) -> ret(). generate(AccountId, Props) -> case find_template(AccountId, Props) of {'error', _R}=Error -> Error; @@ -75,6 +77,7 @@ generate(Account, Props, Template) -> DocType = props:get_first_defined([<<"type">>, <<"pvt_type">>], Props), Rand = kz_util:rand_hex_binary(5), + %% TODO: fix that atom creation! Renderer = kz_util:to_atom(<<"kz_pdf_", DocType/binary, "_", Rand/binary>>, 'true'), {'ok', Renderer} = erlydtl:compile_template(Template, Renderer, [{'out_dir', 'false'}]), {'ok', Rendered} = Renderer:render(Props), @@ -130,7 +133,7 @@ cmd_fold({Search, Replace}, Subject) -> %% @doc %% @end %%-------------------------------------------------------------------- --spec default_template(ne_binary(), ne_binary()) -> {'ok', ne_binary()} | {'error', any()}. +-spec default_template(ne_binary(), ne_binary()) -> ret(). default_template(DocType, AttachmentId) -> lager:debug("searching for default template ~s", [AttachmentId]), case kz_datamgr:fetch_attachment(?KZ_CONFIG_DB, ?TEMPLATE_DOC_ID(DocType), AttachmentId) of @@ -138,12 +141,11 @@ default_template(DocType, AttachmentId) -> {'error', 'not_found'} -> maybe_create_default_template(DocType, AttachmentId); {'error', _R}=Error -> lager:error("failed to find default template ~s/~s : ~p" - ,[?TEMPLATE_DOC_ID(DocType), AttachmentId, _R] - ), + ,[?TEMPLATE_DOC_ID(DocType), AttachmentId, _R]), Error end. --spec maybe_create_default_template(ne_binary(), ne_binary()) -> {'ok', ne_binary()} | {'error', any()}. +-spec maybe_create_default_template(ne_binary(), ne_binary()) -> ret(). maybe_create_default_template(DocType, AttachmentId) -> PrivDir = code:priv_dir('crossbar'), TemplateFile = filename:join([PrivDir, <<"couchdb">>, <<"templates">>, AttachmentId]), @@ -155,7 +157,7 @@ maybe_create_default_template(DocType, AttachmentId) -> {'ok', Template} -> create_default_template(Template, DocType, AttachmentId) end. --spec create_default_template(binary(), ne_binary(), ne_binary()) -> {'ok', ne_binary()} | {'error', any()}. +-spec create_default_template(binary(), ne_binary(), ne_binary()) -> ret(). create_default_template(Template, DocType, AttachmentId) -> lager:debug("creating default template ~s", [DocType]), Default = kz_json:from_list([{<<"template_name">>, DocType}]), @@ -174,12 +176,11 @@ create_default_template(Template, DocType, AttachmentId) -> {'error', 'conflict'} -> save_default_attachment(Template, DocType, AttachmentId); {'error', _R}=Error -> lager:error("failed to create default template doc for ~s : ~p" - ,[?TEMPLATE_DOC_ID(DocType), _R] - ), + ,[?TEMPLATE_DOC_ID(DocType), _R]), Error end. --spec save_default_attachment(binary(), ne_binary(), ne_binary()) -> {'ok', ne_binary()} | {'error', any()}. +-spec save_default_attachment(binary(), ne_binary(), ne_binary()) -> ret(). save_default_attachment(Template, DocType, AttachmentId) -> lager:debug("saving default template ~s attachment", [DocType]), case @@ -192,8 +193,7 @@ save_default_attachment(Template, DocType, AttachmentId) -> of {'error', _R}=Error -> lager:error("failed to save default template attachment for ~s : ~p" - ,[?TEMPLATE_DOC_ID(DocType), _R] - ), + ,[?TEMPLATE_DOC_ID(DocType), _R]), Error; {'ok', _} -> {'ok', Template} end. diff --git a/core/kazoo_number_manager/include/knm_port_request.hrl b/core/kazoo_number_manager/include/knm_port_request.hrl index 47dc6fd66b4..8bfb7072575 100644 --- a/core/kazoo_number_manager/include/knm_port_request.hrl +++ b/core/kazoo_number_manager/include/knm_port_request.hrl @@ -1,12 +1,14 @@ -ifndef(KNM_PORT_REQUEST_HRL). +-define(PORT_KEY, <<"port">>). + -define(PORT_UNCONFIRMED, <<"unconfirmed">>). -define(PORT_WAITING, ?PORT_UNCONFIRMED). -define(PORT_SUBMITTED, <<"submitted">>). -define(PORT_PENDING, <<"pending">>). -define(PORT_SCHEDULED, <<"scheduled">>). --define(PORT_COMPLETE, <<"completed">>). --define(PORT_REJECT, <<"rejected">>). +-define(PORT_COMPLETED, <<"completed">>). +-define(PORT_REJECTED, <<"rejected">>). -define(PORT_CANCELED, <<"canceled">>). -define(PORT_ATTACHMENT, <<"attachments">>). -define(PORT_DESCENDANTS, <<"descendants">>). @@ -17,8 +19,8 @@ ,?PORT_SUBMITTED ,?PORT_PENDING ,?PORT_SCHEDULED - ,?PORT_COMPLETE - ,?PORT_REJECT + ,?PORT_COMPLETED + ,?PORT_REJECTED ,?PORT_CANCELED ]). diff --git a/core/kazoo_number_manager/src/knm_errors.erl b/core/kazoo_number_manager/src/knm_errors.erl index dbbfbf03e27..270cd9bd5eb 100644 --- a/core/kazoo_number_manager/src/knm_errors.erl +++ b/core/kazoo_number_manager/src/knm_errors.erl @@ -44,7 +44,13 @@ -type kn() :: knm_number:knm_number(). -type kpn() :: knm_phone_number:knm_phone_number(). --export_type([error/0]). +-type thrown_error() :: {'error', atom()} | + {'error', atom(), any()} | + {'error', atom(), any(), any()}. + +-export_type([error/0 + ,thrown_error/0 + ]). -spec unauthorized() -> no_return(). unauthorized() -> diff --git a/core/kazoo_number_manager/src/knm_number.erl b/core/kazoo_number_manager/src/knm_number.erl index b7ecefe3105..b2c50cc8f64 100644 --- a/core/kazoo_number_manager/src/knm_number.erl +++ b/core/kazoo_number_manager/src/knm_number.erl @@ -6,6 +6,7 @@ %%% @contributors %%% Peter Defebvre %%% James Aimonetti +%%% Pierre Fenoll %%%------------------------------------------------------------------- -module(knm_number). @@ -117,23 +118,23 @@ get_number(Num, Options) -> create(Num, Options) -> attempt(fun create_or_load/2, [Num, Options]). --spec create_or_load(ne_binary(), knm_number_options:options()) -> - knm_number() | dry_run_return(). +-spec create_or_load(ne_binary(), knm_number_options:options()) -> knm_number() | + dry_run_return(). create_or_load(Num, Options) -> create_or_load(Num, Options, knm_phone_number:fetch(Num)). -spec create_or_load(ne_binary(), knm_number_options:options(), knm_phone_number_return()) -> knm_number() | dry_run_return(). -create_or_load(Num, Options, {'error', 'not_found'}) -> - ensure_can_create(Num, Options), - Updates = create_updaters(Num, Options), - {'ok', PhoneNumber} = knm_phone_number:setters(knm_phone_number:new(), Updates), - create_phone_number(set_phone_number(new(), PhoneNumber)); create_or_load(Num, Options, {'ok', PhoneNumber}) -> ensure_can_load_to_create(PhoneNumber), Updates = create_updaters(Num, Options), {'ok', NewPhoneNumber} = knm_phone_number:setters(PhoneNumber, Updates), - create_phone_number(set_phone_number(new(), NewPhoneNumber)). + create_phone_number(set_phone_number(new(), NewPhoneNumber)); +create_or_load(Num, Options, {'error', 'not_found'}) -> + ensure_can_create(Num, Options), + Updates = create_updaters(Num, Options), + {'ok', PhoneNumber} = knm_phone_number:setters(knm_phone_number:new(), Updates), + create_phone_number(set_phone_number(new(), PhoneNumber)). -spec ensure_can_load_to_create(knm_phone_number:knm_phone_number()) -> 'true'. ensure_can_load_to_create(PhoneNumber) -> @@ -164,7 +165,6 @@ create_phone_number(Number) -> %% @public %% @doc %% Fetches then transition an existing number to the reserved state. -%% Similar to kz_number_manager:reserve_number %% @end %%-------------------------------------------------------------------- -spec reserve(ne_binary(), knm_number_options:options()) -> knm_number_return(). @@ -213,7 +213,7 @@ dry_run_or_number(Number) -> -spec ensure_can_create(ne_binary(), knm_number_options:options()) -> 'true'. ensure_can_create(Num, Options) -> ensure_account_can_create(Options) - andalso ensure_number_is_not_porting(Num). + andalso ensure_number_is_not_porting(Num, Options). -spec ensure_account_can_create(knm_number_options:options()) -> 'true'. ensure_account_can_create(Options) -> @@ -240,14 +240,16 @@ ensure_account_is_allowed_to_create(_Options, _AccountId) -> 'false' -> knm_errors:unauthorized() end. --spec ensure_number_is_not_porting(ne_binary()) -> 'true'. +-spec ensure_number_is_not_porting(ne_binary(), knm_number_options:options()) -> 'true'. -ifdef(TEST). -ensure_number_is_not_porting(?TEST_CREATE_NUM) -> 'true'; -ensure_number_is_not_porting(?TEST_AVAILABLE_NUM = Num) -> +ensure_number_is_not_porting(?TEST_CREATE_NUM, _Options) -> 'true'; +ensure_number_is_not_porting(?TEST_AVAILABLE_NUM = Num, _Options) -> knm_errors:number_is_porting(Num). -else. -ensure_number_is_not_porting(Num) -> - case knm_port_request:get(Num) of +ensure_number_is_not_porting(Num, Options) -> + JustPorted = knm_number_options:ported_in(Options), + case JustPorted orelse knm_port_request:get(Num) of + 'true' -> 'true'; {'ok', _Doc} -> knm_errors:number_is_porting(Num); {'error', 'not_found'} -> 'true' end. diff --git a/core/kazoo_number_manager/src/knm_number_states.erl b/core/kazoo_number_manager/src/knm_number_states.erl index 73098be2338..44ed2cd3206 100644 --- a/core/kazoo_number_manager/src/knm_number_states.erl +++ b/core/kazoo_number_manager/src/knm_number_states.erl @@ -22,10 +22,8 @@ -type kn() :: knm_number:knm_number(). --spec to_state(ne_binary() | kn(), ne_binary()) -> - kn(). --spec to_state(ne_binary() | kn(), ne_binary(), kz_proplist()) -> - kn(). +-spec to_state(ne_binary() | kn(), ne_binary()) -> kn(). +-spec to_state(ne_binary() | kn(), ne_binary(), kz_proplist()) -> kn(). to_state(DID, ToState) -> to_state(DID, ToState, knm_number_options:default()). to_state(?NE_BINARY = DID, ToState, Options) -> @@ -41,7 +39,10 @@ change_state(Number, ?NUMBER_STATE_RESERVED) -> to_reserved(Number); change_state(Number, ?NUMBER_STATE_DELETED) -> to_deleted(Number); +change_state(Number, ?NUMBER_STATE_IN_SERVICE) -> + to_in_service(Number); change_state(Number, _State) -> + lager:debug("unhandled state change to ~p", [_State]), knm_errors:unspecified('invalid_state', Number). -spec to_reserved(kn()) -> kn(). diff --git a/core/kazoo_number_manager/src/knm_numbers.erl b/core/kazoo_number_manager/src/knm_numbers.erl index d4f74126abc..c3173b24067 100644 --- a/core/kazoo_number_manager/src/knm_numbers.erl +++ b/core/kazoo_number_manager/src/knm_numbers.erl @@ -1,21 +1,24 @@ %%%------------------------------------------------------------------- -%%% @copyright (C) 2015, 2600Hz INC +%%% @copyright (C) 2015-2016, 2600Hz INC %%% @doc -%%% +%%% Bulk operations on numbers. Follows knm_number's API. %%% @end %%% @contributors %%% Peter Defebvre +%%% Pierre Fenoll %%%------------------------------------------------------------------- -module(knm_numbers). -export([get/1, get/2 - ,create/1, create/2 - ,move/1, move/2, move/3 - ,update/1, update/2, update/3 - ,reconcile/2 + ,create/2 + ,move/2, move/3 + ,update/2, update/3 ,release/1, release/2 - ,change_state/1, change_state/2 - ,assigned_to_app/1, assigned_to_app/2 + ,reconcile/2 + ,reserve/2 + + ,to_state/2, to_state/3 + ,assign_to_app/2, assign_to_app/3 ,free/1 ,emergency_enabled/1 @@ -48,73 +51,54 @@ get(Nums, Options) -> %% @doc %% @end %%-------------------------------------------------------------------- --spec create(kz_proplist()) -> numbers_return(). -create(Props) -> - do_create(Props, []). - -spec create(ne_binaries(), knm_number_options:options()) -> numbers_return(). create(Nums, Options) -> [{Num, knm_number:create(Num, Options)} || Num <- Nums]. --spec do_create(kz_proplist(), numbers_return()) -> numbers_return(). -do_create([], Acc) -> Acc; -do_create([{Num, Data}|Props], Acc) -> - Return = knm_number:create(Num, Data), - do_create(Props, [{Num, Return}|Acc]). - %%-------------------------------------------------------------------- %% @public %% @doc %% @end %%-------------------------------------------------------------------- --spec move(kz_proplist()) -> - numbers_return(). --spec move(kz_proplist(), knm_number_options:options()) -> +-spec move(ne_binaries(), ne_binary()) -> numbers_return(). -spec move(ne_binaries(), ne_binary(), knm_number_options:options()) -> numbers_return(). -move(Props) -> - move(Props, knm_number_options:default()). - -move(Props, Options) -> - do_move(Props, Options, []). +move(Nums, MoveTo) -> + move(Nums, MoveTo, knm_number_options:default()). move(Nums, MoveTo, Options) -> [{Num, knm_number:move(Num, MoveTo, Options)} || Num <- Nums]. --spec do_move(kz_proplist(), knm_number_options:options(), numbers_return()) -> - numbers_return(). -do_move([], _Options, Acc) -> Acc; -do_move([{Num, MoveTo}|Props], Options, Acc) -> - Return = knm_number:move(Num, MoveTo, Options), - do_move(Props, Options, [{Num, Return}|Acc]). - %%-------------------------------------------------------------------- %% @public %% @doc %% @end %%-------------------------------------------------------------------- --spec update(kz_proplist()) -> - numbers_return(). --spec update(kz_proplist(), knm_number_options:options()) -> +-spec update(ne_binaries(), knm_phone_number:set_functions()) -> numbers_return(). -spec update(ne_binaries(), knm_phone_number:set_functions(), knm_number_options:options()) -> numbers_return(). -update(Props) -> - update(Props, knm_number_options:default()). - -update(Props, Options) -> - do_update(Props, Options, []). +update(Nums, Routines) -> + update(Nums, Routines, knm_number_options:default()). update(Nums, Routines, Options) -> [{Num, knm_number:update(Num, Routines, Options)} || Num <- Nums]. --spec do_update(kz_proplist(), knm_number_options:options(), numbers_return()) -> - numbers_return(). -do_update([], _Options, Acc) -> Acc; -do_update([{Num, Data}|Props], Options, Acc) -> - Return = knm_number:update(Num, Data, Options), - do_update(Props, Options, [{Num, Return}|Acc]). +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% @end +%%-------------------------------------------------------------------- +-spec release(ne_binaries()) -> + numbers_return(). +-spec release(ne_binaries(), knm_number_options:options()) -> + numbers_return(). +release(Nums) -> + release(Nums, knm_number_options:default()). + +release(Nums, Options) -> + [{Num, knm_number:release(Num, Options)} || Num <- Nums]. %%-------------------------------------------------------------------- %% @public @@ -123,55 +107,42 @@ do_update([{Num, Data}|Props], Options, Acc) -> %%-------------------------------------------------------------------- -spec reconcile(ne_binaries(), knm_number_options:options()) -> numbers_return(). -reconcile(Numbers, Options) -> - do_reconcile(Numbers, Options, []). - --spec do_reconcile(ne_binaries(), knm_number_options:options(), numbers_return()) -> - numbers_return(). -do_reconcile([], _Options, Acc) -> Acc; -do_reconcile([Number|Numbers], Options, Acc) -> - Return = knm_number:reconcile(Number, Options), - do_reconcile(Numbers, Options, [{Number, Return}|Acc]). +reconcile(Nums, Options) -> + [{Num, knm_number:reconcile(Num, Options)} || Num <- Nums]. %%-------------------------------------------------------------------- %% @public %% @doc %% @end %%-------------------------------------------------------------------- --spec release(ne_binaries()) -> +-spec reserve(ne_binaries(), knm_number_options:options()) -> numbers_return(). --spec release(ne_binaries(), knm_number_options:options()) -> - numbers_return(). -release(Nums) -> - release(Nums, knm_number_options:default()). - -release(Nums, Options) -> - [{Num, knm_number:release(Num, Options)} || Num <- Nums]. +reserve(Nums, Options) -> + [{Num, knm_number:reserve(Num, Options)} || Num <- Nums]. %%-------------------------------------------------------------------- %% @public %% @doc %% @end %%-------------------------------------------------------------------- --spec change_state(kz_proplist()) -> - numbers_return(). --spec change_state(kz_proplist(), knm_number_options:options()) -> - numbers_return(). -change_state(Props) -> - change_state(Props, knm_number_options:default()). - -change_state(Props, Options) -> - do_change_state(Props, Options, []). - --spec do_change_state(kz_proplist(), knm_number_options:options(), numbers_return()) -> - numbers_return(). -do_change_state([], _Options, Acc) -> Acc; -do_change_state([{Num, State}|Props], Options, Acc) -> - try knm_number_states:to_state(Num, State, Options) of - Number -> do_change_state(Props, Options, [{Num, {'ok', Number}}|Acc]) +-spec to_state(ne_binaries(), ne_binary()) -> + numbers_return(). +-spec to_state(ne_binaries(), ne_binary(), knm_number_options:options()) -> + numbers_return(). +to_state(Nums, ToState) -> + to_state(Nums, ToState, knm_number_options:default()). + +to_state(Nums, ToState, Options) -> + [{Num, change_state(Num, ToState, Options)} || Num <- Nums]. + +-spec change_state(ne_binary(), ne_binary(), knm_number_options:options()) -> + {'ok', knm_number:knm_number()} | + knm_errors:thrown_error(). +change_state(Num, ToState, Options) -> + try knm_number_states:to_state(Num, ToState, Options) of + Number -> {'ok', Number} catch - 'throw':R -> - do_change_state(Props, Options, [{Num, R} | Acc]) + 'throw':R -> R end. %%-------------------------------------------------------------------- @@ -179,26 +150,19 @@ do_change_state([{Num, State}|Props], Options, Acc) -> %% @doc %% @end %%-------------------------------------------------------------------- --spec assigned_to_app(kz_proplist()) -> - numbers_return(). --spec assigned_to_app(kz_proplist(), knm_number_options:options()) -> - numbers_return(). -assigned_to_app(Props) -> - assigned_to_app(Props, knm_number_options:default()). +-spec assign_to_app(ne_binaries(), api_binary()) -> + numbers_return(). +-spec assign_to_app(ne_binaries(), api_binary(), knm_number_options:options()) -> + numbers_return(). +assign_to_app(Nums, App) -> + assign_to_app(Nums, App, knm_number_options:default()). -assigned_to_app(Props, Options) -> - do_assigned_to_app(Props, Options, []). - --spec do_assigned_to_app(kz_proplist(), knm_number_options:options(), numbers_return()) -> - numbers_return(). -do_assigned_to_app([], _Options, Acc) -> Acc; -do_assigned_to_app([{Num, App}|Props], Options, Acc) -> - Return = knm_number:assign_to_app(Num, App, Options), - do_assigned_to_app(Props, Options, [{Num, Return}|Acc]). +assign_to_app(Nums, App, Options) -> + [{Num, knm_number:assign_to_app(Num, App, Options)} || Num <- Nums]. %%-------------------------------------------------------------------- %% @public -%% @doc +%% @doc Release all of an account's numbers %% @end %%-------------------------------------------------------------------- -spec free(ne_binary()) -> 'ok'. diff --git a/core/kazoo_number_manager/src/knm_port_request.erl b/core/kazoo_number_manager/src/knm_port_request.erl index 8cfa51a4d62..c1c8e880a40 100644 --- a/core/kazoo_number_manager/src/knm_port_request.erl +++ b/core/kazoo_number_manager/src/knm_port_request.erl @@ -34,6 +34,20 @@ -type transition_response() :: {'ok', kz_json:object()} | {'error', 'invalid_state_transition'}. +-define(NAME_KEY, <<"name">>). +-define(NUMBERS_KEY, <<"numbers">>). +-define(PVT_ACCOUNT_DB, <<"pvt_account_db">>). +-define(PVT_ACCOUNT_ID, <<"pvt_account_id">>). +-define(PVT_ID, <<"_id">>). +-define(PVT_REV, <<"_rev">>). +-ifndef(PVT_TREE). +- define(PVT_TREE, <<"pvt_tree">>). +-endif. +-define(PVT_VSN, <<"pvt_vsn">>). + + +%%% API + %%-------------------------------------------------------------------- %% @public %% @doc @@ -51,7 +65,7 @@ init() -> %%-------------------------------------------------------------------- -spec current_state(kz_json:object()) -> api_binary(). current_state(JObj) -> - kz_json:get_value(?PORT_PVT_STATE, JObj, ?PORT_WAITING). + kz_json:get_value(?PORT_PVT_STATE, JObj, ?PORT_UNCONFIRMED). %%-------------------------------------------------------------------- %% @public @@ -66,7 +80,7 @@ public_fields(JObj) -> ,{<<"created">>, kz_doc:created(JObj)} ,{<<"updated">>, kz_doc:modified(JObj)} ,{<<"uploads">>, normalize_attachments(As)} - ,{<<"port_state">>, kz_json:get_value(?PORT_PVT_STATE, JObj, ?PORT_WAITING)} + ,{<<"port_state">>, kz_json:get_value(?PORT_PVT_STATE, JObj, ?PORT_UNCONFIRMED)} ,{<<"sent">>, kz_json:get_value(?PVT_SENT, JObj, 'false')} ] ,kz_doc:public_fields(JObj) @@ -77,10 +91,9 @@ public_fields(JObj) -> %% @doc %% @end %%-------------------------------------------------------------------- --spec get(ne_binary() | knm_phone_number:knm_phone_number()) -> - {'ok', kz_json:object()} | - {'error', 'not_found'}. -get(DID) when is_binary(DID) -> +-spec get(ne_binary()) -> {'ok', kz_json:object()} | + {'error', 'not_found'}. +get(DID=?NE_BINARY) -> ViewOptions = [{'key', DID}, 'include_docs'], case kz_datamgr:get_results( @@ -94,9 +107,7 @@ get(DID) when is_binary(DID) -> {'error', _E} -> lager:debug("failed to query for port number '~s': ~p", [DID, _E]), {'error', 'not_found'} - end; -get(Number) -> - ?MODULE:get(knm_phone_number:number(Number)). + end. %%-------------------------------------------------------------------- %% @public @@ -148,10 +159,10 @@ normalize_attachments_map(K, V) -> %% @end %%-------------------------------------------------------------------- -spec normalize_numbers(kz_json:object()) -> kz_json:object(). -normalize_numbers(JObj) -> - Numbers = kz_json:get_value(<<"numbers">>, JObj, kz_json:new()), +normalize_numbers(PortReq) -> + Numbers = kz_json:get_value(?NUMBERS_KEY, PortReq, kz_json:new()), Normalized = kz_json:map(fun normalize_number_map/2, Numbers), - kz_json:set_value(<<"numbers">>, Normalized, JObj). + kz_json:set_value(?NUMBERS_KEY, Normalized, PortReq). %% @private -spec normalize_number_map(kz_json:key(), kz_json:json_term()) -> @@ -172,7 +183,7 @@ normalize_number_map(N, Meta) -> -spec transition_to_canceled(kz_json:object()) -> transition_response(). transition_to_submitted(JObj) -> - transition(JObj, [?PORT_WAITING, ?PORT_REJECT], ?PORT_SUBMITTED). + transition(JObj, [?PORT_UNCONFIRMED, ?PORT_REJECTED], ?PORT_SUBMITTED). transition_to_pending(JObj) -> transition(JObj, [?PORT_SUBMITTED], ?PORT_PENDING). @@ -181,16 +192,16 @@ transition_to_scheduled(JObj) -> transition(JObj, [?PORT_PENDING], ?PORT_SCHEDULED). transition_to_complete(JObj) -> - case transition(JObj, [?PORT_PENDING, ?PORT_SCHEDULED, ?PORT_REJECT], ?PORT_COMPLETE) of + case transition(JObj, [?PORT_PENDING, ?PORT_SCHEDULED, ?PORT_REJECTED], ?PORT_COMPLETED) of {'error', _}=E -> E; {'ok', Transitioned} -> completed_port(Transitioned) end. transition_to_rejected(JObj) -> - transition(JObj, [?PORT_SUBMITTED, ?PORT_PENDING, ?PORT_SCHEDULED], ?PORT_REJECT). + transition(JObj, [?PORT_SUBMITTED, ?PORT_PENDING, ?PORT_SCHEDULED], ?PORT_REJECTED). transition_to_canceled(JObj) -> - transition(JObj, [?PORT_WAITING, ?PORT_SUBMITTED, ?PORT_PENDING, ?PORT_SCHEDULED, ?PORT_REJECT], ?PORT_CANCELED). + transition(JObj, [?PORT_UNCONFIRMED, ?PORT_SUBMITTED, ?PORT_PENDING, ?PORT_SCHEDULED, ?PORT_REJECTED], ?PORT_CANCELED). %%-------------------------------------------------------------------- %% @public @@ -204,9 +215,9 @@ maybe_transition(PortReq, ?PORT_PENDING) -> transition_to_pending(PortReq); maybe_transition(PortReq, ?PORT_SCHEDULED) -> transition_to_scheduled(PortReq); -maybe_transition(PortReq, ?PORT_COMPLETE) -> +maybe_transition(PortReq, ?PORT_COMPLETED) -> transition_to_complete(PortReq); -maybe_transition(PortReq, ?PORT_REJECT) -> +maybe_transition(PortReq, ?PORT_REJECTED) -> transition_to_rejected(PortReq); maybe_transition(PortReq, ?PORT_CANCELED) -> transition_to_canceled(PortReq). @@ -300,15 +311,28 @@ completed_port(PortReq) -> %%-------------------------------------------------------------------- -spec transition_numbers(kz_json:object()) -> transition_response(). transition_numbers(PortReq) -> - Numbers = kz_json:get_keys(<<"numbers">>, PortReq), - PortOps = [enable_number(N) || N <- Numbers], - case lists:all(fun kz_util:is_true/1, PortOps) of - 'true' -> + PortReqId = kz_doc:id(PortReq), + Options = [{'assign_to', kz_json:get_value(?PVT_ACCOUNT_ID, PortReq)} + ,{'auth_by', ?KNM_DEFAULT_AUTH_BY} + ,{'module_name', ?CARRIER_LOCAL} + ,{'ported_in', 'true'} + ,{'public_fields', kz_json:from_list([{<<"port_id">>, PortReqId}])} + ,{'state', ?NUMBER_STATE_IN_SERVICE} + ], + lager:debug("creating local numbers for port ~s", [PortReqId]), + Numbers = kz_json:get_keys(?NUMBERS_KEY, PortReq), + Results = knm_numbers:create(Numbers, Options), + IsOK = fun ({_Num, {'ok',_}}) -> 'true'; ({_Num, _}) -> 'false' end, + {_OK, Errored} = lists:partition(IsOK, Results), + case Errored of + [] -> lager:debug("all numbers ported, removing from port request"), ClearedPortRequest = clear_numbers_from_port(PortReq), {'ok', ClearedPortRequest}; - 'false' -> - lager:debug("failed to transition numbers: ~p", [PortOps]), + _ -> + lager:debug("failed to transition ~p/~p numbers", [length(Errored), length(_OK)]), + _ = [lager:debug("~s error: ~p", [Num, Error]) || {Num,Error} <- Errored], + _ = [lager:debug("~s success", [Num]) || {Num,_} <- _OK], {'error', PortReq} end. @@ -319,9 +343,11 @@ transition_numbers(PortReq) -> %%-------------------------------------------------------------------- -spec clear_numbers_from_port(kz_json:object()) -> kz_json:object(). clear_numbers_from_port(PortReq) -> - Cleared = kz_json:set_value(<<"numbers">>, kz_json:new(), PortReq), + Cleared = kz_json:set_value(?NUMBERS_KEY, kz_json:new(), PortReq), case kz_datamgr:save_doc(?KZ_PORT_REQUESTS_DB, Cleared) of - {'ok', PortReq1} -> lager:debug("port numbers cleared"), PortReq1; + {'ok', PortReq1} -> + lager:debug("port numbers cleared"), + PortReq1; {'error', 'conflict'} -> lager:debug("port request doc was updated before we could re-save"), PortReq; @@ -330,21 +356,6 @@ clear_numbers_from_port(PortReq) -> PortReq end. -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% @end -%%-------------------------------------------------------------------- --spec enable_number(ne_binary()) -> boolean(). -enable_number(Num) -> - try knm_number_states:to_state(Num, ?NUMBER_STATE_IN_SERVICE) of - _Number -> 'true' - catch - 'throw':_R -> - lager:error("failed to enable number ~s : ~p", [Num, _R]), - 'false' - end. - %%-------------------------------------------------------------------- %% @private %% @doc @@ -365,7 +376,7 @@ maybe_send_request(JObj) -> maybe_send_request(JObj, 'undefined')-> lager:debug("'submitted_port_requests_url' is not set for account ~s" - ,[kz_doc:account_id(JObj)]); + ,[kz_doc:account_id(JObj)]); maybe_send_request(JObj, Url)-> case send_request(JObj, Url) of 'error' -> 'ok'; @@ -384,24 +395,24 @@ maybe_send_request(JObj, Url)-> -spec send_request(kz_json:object(), ne_binary()) -> 'error' | 'ok'. send_request(JObj, Url) -> Headers = [{"Content-Type", "application/json"} - ,{"User-Agent", kz_util:to_list(erlang:node())} + ,{"User-Agent", kz_util:to_list(node())} ], Uri = kz_util:to_list(<>), - Remove = [<<"_rev">> + Remove = [?PVT_REV ,<<"ui_metadata">> ,<<"_attachments">> ,<<"pvt_request_id">> - ,<<"pvt_type">> - ,<<"pvt_vsn">> - ,<<"pvt_account_db">> + ,?PVT_TYPE + ,?PVT_VSN + ,?PVT_ACCOUNT_DB ], - Replace = [{<<"_id">>, <<"id">>} - ,{<<"pvt_port_state">>, <<"port_state">>} - ,{<<"pvt_account_id">>, <<"account_id">>} - ,{<<"pvt_created">>, <<"created">>} - ,{<<"pvt_modified">>, <<"modified">>} + Replace = [{?PVT_ID, <<"id">>} + ,{?PORT_PVT_STATE, <<"port_state">>} + ,{?PVT_ACCOUNT_ID, <<"account_id">>} + ,{?PVT_CREATED, <<"created">>} + ,{?PVT_MODIFIED, <<"modified">>} ], Data = kz_json:encode(kz_json:normalize_jobj(JObj, Remove, Replace)), @@ -461,7 +472,7 @@ send_attachment(Url, Id, Name, Options, Attachment) -> ContentType = kz_json:get_value(<<"content_type">>, Options), Headers = [{"Content-Type", kz_util:to_list(ContentType)} - ,{"User-Agent", kz_util:to_list(erlang:node())} + ,{"User-Agent", kz_util:to_list(node())} ], Uri = kz_util:to_list(<>), @@ -524,7 +535,7 @@ prepare_docs_for_migrate(Docs) -> -spec migrate_doc(kz_json:object()) -> api_object(). migrate_doc(PortRequest) -> - case kz_json:get_value(<<"pvt_tree">>, PortRequest) of + case kz_json:get_value(?PVT_TREE, PortRequest) of 'undefined' -> update_doc(PortRequest); _Tree -> 'undefined' end. @@ -539,7 +550,7 @@ update_doc(_Doc, 'undefined') -> 'undefined'; update_doc(PortRequest, AccountId) -> {'ok', AccountDoc} = kz_account:fetch(AccountId), - kz_json:set_value(<<"pvt_tree">>, kz_account:tree(AccountDoc), PortRequest). + kz_json:set_value(?PVT_TREE, kz_account:tree(AccountDoc), PortRequest). -spec fetch_docs(binary(), pos_integer()) -> {'ok', kz_json:objects()}. fetch_docs(StartKey, Limit) -> diff --git a/core/kazoo_number_manager/src/knm_port_request_crawler.erl b/core/kazoo_number_manager/src/knm_port_request_crawler.erl index 0a9b4f3bcf5..50fd8be9f56 100644 --- a/core/kazoo_number_manager/src/knm_port_request_crawler.erl +++ b/core/kazoo_number_manager/src/knm_port_request_crawler.erl @@ -37,12 +37,9 @@ %%%=================================================================== %%-------------------------------------------------------------------- -%% @doc -%% Starts the server -%% -%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} -%% @end +%% @doc Starts the server %%-------------------------------------------------------------------- +-spec start_link() -> startlink_ret(). start_link() -> gen_server:start_link(?MODULE, [], []). diff --git a/core/kazoo_number_manager/src/providers/knm_port_notifier.erl b/core/kazoo_number_manager/src/providers/knm_port_notifier.erl index e807c66088e..b68fa61a8e4 100644 --- a/core/kazoo_number_manager/src/providers/knm_port_notifier.erl +++ b/core/kazoo_number_manager/src/providers/knm_port_notifier.erl @@ -16,8 +16,7 @@ -export([has_emergency_services/1]). -include("knm.hrl"). - --define(PORT_KEY, <<"port">>). +-include("knm_port_request.hrl"). %% PORT_KEY %%-------------------------------------------------------------------- %% @public diff --git a/doc/announcements.md b/doc/announcements.md index 538cf96f421..4597ed6398c 100644 --- a/doc/announcements.md +++ b/doc/announcements.md @@ -65,7 +65,7 @@ The enabled Monster UI applications were moved from an object on the account doc #### Company Directory PDF -If you plan to support the new API to download the company directory as a PDF you will need to install 'htmldoc' on any server running crossbar. +If you plan to support the new API to download the company directory as a PDF you will need to install `htmldoc` on any server running crossbar. #### Default WebRTC Port change diff --git a/doc/installation.md b/doc/installation.md index 3b767d22a75..519e5e06634 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -7,11 +7,15 @@ This is a guide to installing Kazoo on a Debian 8 (Jessie) base installation. Ot ### Packages Required ```shell -#> sudo apt-get install build-essential libxslt-dev \ +$ sudo apt-get install build-essential libxslt-dev \ zip unzip expat zlib1g-dev libssl-dev curl \ - libncurses5-dev git-core libexpat1-dev + libncurses5-dev git-core libexpat1-dev \ + htmldoc ``` +Note: `htmldoc` is required only if [you want to be able to download PDFs](./announcements.md#company-directory-pdf). + + ### Erlang Kazoo 4 targets Erlang 18+. There are a couple ways to install Erlang: @@ -34,7 +38,7 @@ $ . /usr/lib/erlang/activate ### Short version -``` +```shell $ cd /opt $ git clone https://github.com/2600Hz/kazoo.git $ cd kazoo @@ -118,8 +122,9 @@ There are a few concerns that should be planned for when designing a Kazoo clust The following should be done to prepare a server for installation (this should be done on all servers in a cluster prior to installing anything) ### Add 2600hz repo -``` -# wget -P /etc/yum.repos.d/ http://repo.2600hz.com/2600hz.repo + +```shell +$ wget -P /etc/yum.repos.d/ http://repo.2600hz.com/2600hz.repo ``` ### Set correct IP / hostname @@ -136,8 +141,8 @@ Example: During installation you should disable any firewalls and SELinux, this is to prevent them from causing any problems during installation and initial testing. -``` -# service iptables save && service iptables stop && chkconfig iptables off +```shell +$ service iptables save && service iptables stop && chkconfig iptables off ``` Edit /etc/selinux/config (restart required) @@ -150,31 +155,35 @@ SELINUX=disabled It is important for the time and dates to be correct and in sync on all servers in a cluster. It is highly reccomended that you use NTP to facilitate this. Select the correct timezone -``` -# tzselect + +```shell +$ tzselect ``` Symlink the timezone file to make the configuration change persistent -``` -# ln -sf /usr/share/zoneinfo/UTC /etc/localtime + +```shell +$ ln -sf /usr/share/zoneinfo/UTC /etc/localtime ``` Enable NTP -``` -# service ntpd start && chkconfig ntpd on + +```shell +$ service ntpd start && chkconfig ntpd on ``` Check date / time and verify it is correct -``` -# date + +```shell +$ date ``` ## Single server installation ### Install packages -``` -# yum install -y kazoo-bigcouch-R15B haproxy kazoo-freeswitch-R15B kazoo-kamailio kazoo-R15B httpd monster-ui* +```shell +$ yum install -y kazoo-bigcouch-R15B haproxy kazoo-freeswitch-R15B kazoo-kamailio kazoo-R15B httpd monster-ui* ``` ### Configure packages @@ -194,8 +203,9 @@ n=1 ``` Restart bigcouch for changes to take effect -``` -# service bigcouch restart + +```shell +$ service bigcouch restart ``` You should now be ready to validate the installation @@ -209,16 +219,19 @@ Server 1 (zone 1): bigcouch, kazoo Hostname: test1.cluster1.2600hz.com IP Addr : 192.168.1.100 ``` + ``` Server 2 (zone 1): bigcouch, freeswitch Hostname: test2.cluster1.2600hz.com IP Addr : 192.168.1.101 ``` + ``` Server 3 (zone 2): bigcouch, kazoo, monster-ui Hostname: test3.cluster1.2600hz.com IP Addr : 192.168.1.102 ``` + ``` Server 4 (zone 2): bigcouch, freeswitch Hostname: test4.cluster1.2600hz.com @@ -228,39 +241,45 @@ Server 4 (zone 2): bigcouch, freeswitch ### Install and configure bigcouch On all servers: -``` -# yum install -y kazoo-bigcouch-R15B + +```shell +$ yum install -y kazoo-bigcouch-R15B ``` **Set the Erlang cookie correctly for bigcouch (see section below)** **Set up bigcouch cluster** -``` -# curl -X PUT test1.cluster1.2600hz.com:5986/nodes/bigcouch@test2.cluster1.2600hz.com -d {} -# curl -X PUT test1.cluster1.2600hz.com:5986/nodes/bigcouch@test3.cluster1.2600hz.com -d {} -# curl -X PUT test1.cluster1.2600hz.com:5986/nodes/bigcouch@test4.cluster1.2600hz.com -d {} + +```shell +$ curl -X PUT test1.cluster1.2600hz.com:5986/nodes/bigcouch@test2.cluster1.2600hz.com -d {} +$ curl -X PUT test1.cluster1.2600hz.com:5986/nodes/bigcouch@test3.cluster1.2600hz.com -d {} +$ curl -X PUT test1.cluster1.2600hz.com:5986/nodes/bigcouch@test4.cluster1.2600hz.com -d {} ``` ### Install remaining packages **Server 1** -``` -# yum install -y kazoo-kamailio kazoo-R15B + +```shell +$ yum install -y kazoo-kamailio kazoo-R15B ``` **Server 2** -``` -# yum install -y haproxy kazoo-freeswitch-R15B + +```shell +$ yum install -y haproxy kazoo-freeswitch-R15B ``` **Server 3** -``` -# yum install -y kazoo-kamailio kazoo-R15B httpd monster-ui* + +```shell +$ yum install -y kazoo-kamailio kazoo-R15B httpd monster-ui* ``` **Server 4** -``` -# yum install -y haproxy kazoo-freeswitch-R15B + +```shell +$ yum install -y haproxy kazoo-freeswitch-R15B ``` ### Configure packages @@ -313,9 +332,10 @@ cookie = COOKIEHERE ### Configure HAProxy Symlink the Kazoo HAProxy configruation file -``` -# rm -f /etc/haproxy/haproxy.cfg -# ln -s /etc/kazoo/haproxy/haproxy.cfg /etc/haproxy/ + +```shell +$ rm -f /etc/haproxy/haproxy.cfg +$ ln -s /etc/kazoo/haproxy/haproxy.cfg /etc/haproxy/ ``` ### Configure Kamailio @@ -352,7 +372,8 @@ On both Server 1 and Server 3 update /etc/kazoo/kamailio/dbtext/dispatcher to co ### Configure Kazoo / RabbitMQ We will now create 2 zones, one for each Kazoo server. Edit the zone, kazoo_apps, and ecallmgr sections of /etc/kazoo/config.ini to look like the following: -``` + +```ini [zone] name = "c1_zone1" amqp_uri = "amqp://guest:guest@192.168.1.100:5672" @@ -395,94 +416,104 @@ default: 'http://192.168.1.102:8000/v2/' Start all services **Server 1** -``` -# service bigcouch restart -# service rabbitmq-server restart -# service rsyslog restart -# service kz-kazoo_apps restart -# service kz-ecallmgr restart -# service kamailio restart -# service httpd restart + +```shell +$ service bigcouch restart +$ service rabbitmq-server restart +$ service rsyslog restart +$ service kz-kazoo_apps restart +$ service kz-ecallmgr restart +$ service kamailio restart +$ service httpd restart ``` **Server 2** -``` -# service bigcouch restart -# service rsyslog restart -# service haproxy restart -# service freeswitch restart + +```shell +$ service bigcouch restart +$ service rsyslog restart +$ service haproxy restart +$ service freeswitch restart ``` **Server 3** -``` -# service bigcouch restart -# service rabbitmq-server restart -# service rsyslog restart -# service kz-kazoo_apps restart -# service kz-ecallmgr restart -# service kamailio restart -# service httpd restart + +```shell +$ service bigcouch restart +$ service rabbitmq-server restart +$ service rsyslog restart +$ service kz-kazoo_apps restart +$ service kz-ecallmgr restart +$ service kamailio restart +$ service httpd restart ``` Enable auto-startup for all services **Server 1** -``` -# chkconfig --add rabbitmq-server -# chkconfig --add kz-ecallmgr -# chkconfig --add kz-kazoo_apps -# chkconfig rabbitmq-server on -# chkconfig kz-ecallmgr on -# chkconfig kz-kazoo_apps on -# chkconfig kamailio on -# chkconfig bigcouch on -# chkconfig httpd on + +```shell +$ chkconfig --add rabbitmq-server +$ chkconfig --add kz-ecallmgr +$ chkconfig --add kz-kazoo_apps +$ chkconfig rabbitmq-server on +$ chkconfig kz-ecallmgr on +$ chkconfig kz-kazoo_apps on +$ chkconfig kamailio on +$ chkconfig bigcouch on +$ chkconfig httpd on ``` **Server 2** -``` -# chkconfig haproxy on -# chkconfig freeswitch on + +```shell +$ chkconfig haproxy on +$ chkconfig freeswitch on ``` **Server 3** -``` -# chkconfig --add rabbitmq-server -# chkconfig --add kz-ecallmgr -# chkconfig --add kz-kazoo_apps -# chkconfig rabbitmq-server on -# chkconfig kz-ecallmgr on -# chkconfig kz-kazoo_apps on -# chkconfig kamailio on -# chkconfig bigcouch on -# chkconfig httpd on + +```shell +$ chkconfig --add rabbitmq-server +$ chkconfig --add kz-ecallmgr +$ chkconfig --add kz-kazoo_apps +$ chkconfig rabbitmq-server on +$ chkconfig kz-ecallmgr on +$ chkconfig kz-kazoo_apps on +$ chkconfig kamailio on +$ chkconfig bigcouch on +$ chkconfig httpd on ``` ### Import media files *Server 1 OR Server 3* -``` -# sup kazoo_media_maintenance import_prompts /opt/kazoo/system_media/en-us en-us + +```shell +$ sup kazoo_media_maintenance import_prompts /opt/kazoo/system_media/en-us en-us ``` ### Configure ecallmgr Add freeswitch nodes *Server 1* -``` -# sup -n ecallmgr ecallmgr_maintenance add_fs_node freeswitch@test2.cluster1.2600hz.com + +```shell +$ sup -n ecallmgr ecallmgr_maintenance add_fs_node freeswitch@test2.cluster1.2600hz.com ``` *Server 3* -``` -# sup -n ecallmgr ecallmgr_maintenance add_fs_node freeswitch@test4.cluster1.2600hz.com + +```shell +$ sup -n ecallmgr ecallmgr_maintenance add_fs_node freeswitch@test4.cluster1.2600hz.com ``` Add acl entries for SIP servers *Server 1 OR Server 3* -``` -# sup -n ecallmgr ecallmgr_maintenance allow_sbc test1.cluster1.2600hz.com 192.168.1.100 -# sup -n ecallmgr ecallmgr_maintenance allow_sbc test3.cluster1.2600hz.com 192.168.1.102 + +```shell +$ sup -n ecallmgr ecallmgr_maintenance allow_sbc test1.cluster1.2600hz.com 192.168.1.100 +$ sup -n ecallmgr ecallmgr_maintenance allow_sbc test3.cluster1.2600hz.com 192.168.1.102 ``` ## Validate installation @@ -492,15 +523,24 @@ Add acl entries for SIP servers On all servers, curl the database ip/port to verify that it is reachable: ```shell -#> curl localhost:5984 -{"couchdb":"Welcome", "uuid":"0d13a8a56e2fbd9338531c4063c41910", "version":"1.6.1", "vendor":{"version":"12.04", "name":"Ubuntu"}} +$ curl localhost:5984 -vs | python -m json.tool +{ + "couchdb": "Welcome", + "uuid": "0d13a8a56e2fbd9338531c4063c41910", + "vendor": { + "name": "Ubuntu", + "version": "12.04" + }, + "version": "1.6.1" +} ``` ### Check FreeSWITCH Connect to the cli and verify that you have at least one profile running, also verify that BOTH ecallmgr nodes are connected -``` -# fs_cli + +```shell +$ fs_cli > sofia status < should show at least one profile> @@ -512,15 +552,17 @@ Connect to the cli and verify that you have at least one profile running, also v ### Check Kamailio status *Server 1 and Server 3* -``` -# kamctl fifo ds_list + +```shell +$ kamctl fifo ds_list ``` ### Check federation (for cluster installations) *Server 1 and Server 3* -``` -# service kz-kazoo_apps status + +```shell +$ service kz-kazoo_apps status ``` Verify that the status shows nodes for BOTH Server 1 and Server 3 @@ -528,19 +570,22 @@ Verify that the status shows nodes for BOTH Server 1 and Server 3 ### Create master account *Server 1 OR Server 3* -``` -# sup crossbar_maintenance create_account {ACCT NAME} {REALM} {LOGIN} {PASSWORD} + +```shell +$ sup crossbar_maintenance create_account {ACCT NAME} {REALM} {LOGIN} {PASSWORD} ``` ### Load applications *Server 1* -``` -# sup crossbar_maintenance init_apps /var/www/html/monster-ui/apps/ http://192.168.1.100:8000/v2 + +```shell +$ sup crossbar_maintenance init_apps /var/www/html/monster-ui/apps/ http://192.168.1.100:8000/v2 ``` *Server 3* -``` -# sup crossbar_maintenance init_apps /var/www/html/monster-ui/apps/ http://192.168.1.102:8000/v2 + +```shell +$ sup crossbar_maintenance init_apps /var/www/html/monster-ui/apps/ http://192.168.1.102:8000/v2 ``` ## Notes / Credits