diff --git a/applications/crossbar/doc/ref/metaflows.md b/applications/crossbar/doc/ref/metaflows.md index 7c6a1d6b8e2..ab69d8f20f2 100644 --- a/applications/crossbar/doc/ref/metaflows.md +++ b/applications/crossbar/doc/ref/metaflows.md @@ -9,12 +9,10 @@ Key | Description | Type | Default | Required `binding_digit` | What DTMF will trigger the collection and analysis of the subsequent DTMF sequence | `string('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '*', '#')` | `*` | `false` `digit_timeout` | How long to wait between DTMF presses before processing the collected sequence (milliseconds) | `integer` | | `false` `listen_on` | Which leg(s) of the call to listen for DTMF | `string('both', 'self', 'peer')` | | `false` -`numbers` | A list of static numbers that the metaflow should match for | `object` | | `false` -`numbers.[0-9\*\#]+` | | `object` | | `false` -`numbers.[0-9\*\#]+.children` | | `#/flow` | `{}` | `false` -`numbers.[0-9\*\#]+.data` | Module Data | `object` | `{}` | `true` -`numbers.[0-9\*\#]+.module` | | `string(0..15)` | | `true` -`patterns` | The metaflow patterns | `object` | | `false` +`numbers` | A list of static numbers with their flows | `object` | | `false` +`numbers.^[0-9]+$` | | `#/definitions/metaflow` | | `false` +`patterns` | A list of patterns with their flows | `object` | | `false` +`patterns..+` | | `#/definitions/metaflow` | | `false` #### Remove diff --git a/applications/crossbar/doc/ref/storage.md b/applications/crossbar/doc/ref/storage.md new file mode 100644 index 00000000000..025944a6dd9 --- /dev/null +++ b/applications/crossbar/doc/ref/storage.md @@ -0,0 +1,103 @@ +### Storage + +#### About Storage + +#### Schema + +Key | Description | Type | Default | Required +--- | ----------- | ---- | ------- | -------- +`attachments` | | `#/definitions/storage.attachments` | | `false` +`connections` | | `#/definitions/storage.connections` | | `false` +`plan` | | `#/definitions/storage.plan` | | `false` + + +#### Remove + +> DELETE /v2/accounts/{ACCOUNT_ID}/storage + +```shell +curl -v -X DELETE \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage +``` + +#### Fetch + +> GET /v2/accounts/{ACCOUNT_ID}/storage + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage +``` + +#### Change + +> POST /v2/accounts/{ACCOUNT_ID}/storage + +```shell +curl -v -X POST \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage +``` + +#### Create + +> PUT /v2/accounts/{ACCOUNT_ID}/storage + +```shell +curl -v -X PUT \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage +``` + +#### Fetch + +> GET /v2/accounts/{ACCOUNT_ID}/storage/plans + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage/plans +``` + +#### Create + +> PUT /v2/accounts/{ACCOUNT_ID}/storage/plans + +```shell +curl -v -X PUT \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage/plans +``` + +#### Remove + +> DELETE /v2/accounts/{ACCOUNT_ID}/storage/plans/{STORAGE_PLAN_ID} + +```shell +curl -v -X DELETE \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage/plans/{STORAGE_PLAN_ID} +``` + +#### Fetch + +> GET /v2/accounts/{ACCOUNT_ID}/storage/plans/{STORAGE_PLAN_ID} + +```shell +curl -v -X GET \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage/plans/{STORAGE_PLAN_ID} +``` + +#### Change + +> POST /v2/accounts/{ACCOUNT_ID}/storage/plans/{STORAGE_PLAN_ID} + +```shell +curl -v -X POST \ + -H "X-Auth-Token: {AUTH_TOKEN}" \ + http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/storage/plans/{STORAGE_PLAN_ID} +``` + diff --git a/applications/crossbar/priv/api/swagger.json b/applications/crossbar/priv/api/swagger.json index af86ae581d0..6d5c43f2727 100644 --- a/applications/crossbar/priv/api/swagger.json +++ b/applications/crossbar/priv/api/swagger.json @@ -4601,7 +4601,35 @@ "required": true, "type": "object" }, + "metaflow": { + "additionalProperties": false, + "description": "A metaflow node defines a module to execute, data to provide to that module, and one or more children to branch to", + "properties": { + "children": { + "default": {}, + "patternProperties": { + ".+": { + "$ref": "metaflow" + } + }, + "required": false, + "type": "object" + }, + "data": { + "default": {}, + "description": "Module Data", + "required": true, + "type": "object" + }, + "module": { + "maxLength": 15, + "required": true, + "type": "string" + } + } + }, "metaflows": { + "additionalProperties": false, "description": "Actions applied to a call outside of the normal callflow, initiated by the caller(s)", "properties": { "binding_digit": { @@ -4643,45 +4671,28 @@ "type": "string" }, "numbers": { - "description": "A list of static numbers that the metaflow should match for", + "additionalProperties": false, + "description": "A list of static numbers with their flows", "patternProperties": { - "[0-9\\*\\#]+": { - "properties": { - "children": { - "default": {}, - "required": false, - "type": "#/flow" - }, - "data": { - "default": {}, - "description": "Module Data", - "required": true, - "type": "object" - }, - "module": { - "maxLength": 15, - "required": true, - "type": "string" - } - }, - "required": false, - "type": "object" + "^[0-9]+$": { + "$ref": "metaflow" } }, "required": false, "type": "object" }, "patterns": { - "description": "The metaflow patterns", - "itmes": { - "required": false, - "type": "string" + "additionalProperties": false, + "description": "A list of patterns with their flows", + "patternProperties": { + ".+": { + "$ref": "metaflow" + } }, "required": false, "type": "object" } }, - "required": true, "type": "object" }, "notifications": { @@ -6153,6 +6164,316 @@ "required": true, "type": "object" }, + "storage": { + "additionalProperties": false, + "patternProperties": { + "^_": { + "type": [ + "string", + "integer", + "boolean", + "object" + ] + }, + "^pvt_": { + "type": [ + "string", + "integer", + "boolean" + ] + } + }, + "properties": { + "attachments": { + "$ref": "storage.attachments" + }, + "connections": { + "$ref": "storage.connections" + }, + "plan": { + "$ref": "storage.plan" + } + }, + "type": "object" + }, + "storage.attachment.aws": { + "description": "schema for aws attachment entry", + "properties": { + "handler": { + "enum": [ + "s3" + ] + }, + "name": { + "type": "string" + }, + "settings": { + "properties": { + "bucket": { + "type": "string" + }, + "key": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret": { + "type": "string" + } + }, + "required": [ + "bucket", + "key", + "secret" + ], + "type": "object" + } + }, + "required": [ + "handler", + "settings" + ], + "type": "object" + }, + "storage.attachment.google_drive": { + "description": "schema for google drive attachment entry", + "properties": { + "handler": { + "enum": [ + "google_drive" + ] + }, + "name": { + "type": "string" + }, + "settings": { + "properties": { + "oauth_doc_id": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "oauth_doc_id" + ], + "type": "object" + } + }, + "required": [ + "handler", + "settings" + ], + "type": "object" + }, + "storage.attachments": { + "patternProperties": { + "^[a-z,0-9]{32}$": { + "oneOf": [ + { + "$ref": "storage.attachment.aws" + }, + { + "$ref": "storage.attachment.google_drive" + } + ] + } + }, + "type": "object" + }, + "storage.connection.couchdb": { + "definitions": { + "credentials": { + "additionalProperties": false, + "properties": { + "password": { + "type": "integer" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "type": "object" + }, + "pool": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "required": [ + "name", + "size" + ], + "type": "object" + } + }, + "description": "schema for couchdb connection entry", + "properties": { + "driver": { + "enum": [ + "kazoo_couch" + ] + }, + "name": { + "type": "string" + }, + "settings": { + "properties": { + "connect_options": { + "properties": { + "keepalive": { + "type": "boolean" + } + }, + "type": "object" + }, + "connect_timeout": { + "type": "integer" + }, + "credentials": { + "$ref": "#/definitions/credentials" + }, + "ip": { + "type": "string" + }, + "max_pipeline_size": { + "type": "integer" + }, + "max_sessions": { + "type": "integer" + }, + "pool": { + "$ref": "#/definitions/pool" + }, + "port": { + "type": "integer" + } + }, + "required": [ + "ip", + "port" + ], + "type": "object" + } + }, + "required": [ + "driver", + "settings" + ], + "type": "object" + }, + "storage.connections": { + "patternProperties": { + "^([a-z,0-9]){32}$": { + "oneOf": [ + { + "$ref": "storage.connection.couchdb" + } + ] + }, + "local": { + "type": "object" + } + }, + "type": "object" + }, + "storage.plan": { + "additionalProperties": false, + "description": "schema for storage plan", + "properties": { + "account": { + "$ref": "storage.plan.database" + }, + "modb": { + "$ref": "storage.plan.database" + }, + "system": { + "$ref": "storage.plan.database" + } + }, + "type": "object" + }, + "storage.plan.database": { + "additionalProperties": false, + "definitions": { + "database": { + "properties": { + "create_options": { + "type": "object" + } + }, + "type": "object" + } + }, + "description": "schema for database storage plan", + "properties": { + "attachments": { + "$ref": "storage.plan.database.attachment" + }, + "connection": { + "type": "string" + }, + "database": { + "$ref": "#/definitions/database" + }, + "types": { + "additionalProperties": false, + "properties": { + "call_recording": { + "$ref": "storage.plan.database.document" + }, + "fax": { + "$ref": "storage.plan.database.document" + }, + "mailbox_message": { + "$ref": "storage.plan.database.document" + }, + "media": { + "$ref": "storage.plan.database.document" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "storage.plan.database.attachment": { + "description": "schema for attachment ref type storage plan", + "properties": { + "handler": { + "type": "string" + }, + "params": { + "type": "object" + }, + "stub": { + "type": "boolean" + } + } + }, + "storage.plan.database.document": { + "description": "schema for document type storage plan", + "properties": { + "attachments": { + "$ref": "storage.plan.database.attachment" + }, + "connection": { + "type": "string" + } + }, + "type": "object" + }, "system_config.accounts": { "description": "Schema for accounts system_config", "properties": { @@ -6851,6 +7172,11 @@ "description": "crossbar default language", "type": "string" }, + "ensure_valid_schema": { + "default": true, + "description": "crossbar ensure valid schema", + "type": "boolean" + }, "expiry_percentage": { "default": 75, "description": "crossbar expiry percentage", @@ -11876,6 +12202,10 @@ "required": true, "type": "string" }, + "storage_plan_id": { + "$ref": "#/parameters/id", + "name": "{STORAGE_PLAN_ID}" + }, "system_config_id": { "in": "path", "name": "{SYSTEM_CONFIG_ID}", @@ -18091,6 +18421,152 @@ ] } }, + "/accounts/{ACCOUNT_ID}/storage": { + "delete": { + "parameters": [ + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + }, + "get": { + "parameters": [ + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + }, + "post": { + "parameters": [ + { + "in": "body", + "name": "storage", + "required": true, + "schema": { + "$ref": "#/definitions/storage" + } + }, + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + }, + "put": { + "parameters": [ + { + "in": "body", + "name": "storage", + "required": true, + "schema": { + "$ref": "#/definitions/storage" + } + }, + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + } + }, + "/accounts/{ACCOUNT_ID}/storage/plans": { + "get": { + "parameters": [ + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + }, + "put": { + "parameters": [ + { + "in": "body", + "name": "storage", + "required": true, + "schema": { + "$ref": "#/definitions/storage" + } + }, + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + } + }, + "/accounts/{ACCOUNT_ID}/storage/plans/{STORAGE_PLAN_ID}": { + "delete": { + "parameters": [ + { + "$ref": "#/parameters/storage_plan_id" + }, + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + }, + "get": { + "parameters": [ + { + "$ref": "#/parameters/storage_plan_id" + }, + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + }, + "post": { + "parameters": [ + { + "in": "body", + "name": "storage", + "required": true, + "schema": { + "$ref": "#/definitions/storage" + } + }, + { + "$ref": "#/parameters/storage_plan_id" + }, + { + "$ref": "#/parameters/auth_token_header", + "required": true + }, + { + "$ref": "#/parameters/account_id" + } + ] + } + }, "/accounts/{ACCOUNT_ID}/tasks": { "get": { "parameters": [ diff --git a/applications/crossbar/priv/couchdb/schemas/metaflow.json b/applications/crossbar/priv/couchdb/schemas/metaflow.json new file mode 100644 index 00000000000..2c0b83b6dc8 --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/metaflow.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "_id": "metaflow", + "additionalProperties": false, + "description": "A metaflow node defines a module to execute, data to provide to that module, and one or more children to branch to", + "properties": { + "children": { + "default": {}, + "patternProperties": { + ".+": { + "$ref": "metaflow" + } + }, + "required": false, + "type": "object" + }, + "data": { + "default": {}, + "description": "Module Data", + "required": true, + "type": "object" + }, + "module": { + "maxLength": 15, + "required": true, + "type": "string" + } + } +} diff --git a/applications/crossbar/priv/couchdb/schemas/metaflows.json b/applications/crossbar/priv/couchdb/schemas/metaflows.json index acbae52cd05..47aac013556 100644 --- a/applications/crossbar/priv/couchdb/schemas/metaflows.json +++ b/applications/crossbar/priv/couchdb/schemas/metaflows.json @@ -1,6 +1,7 @@ { "$schema": "http://json-schema.org/draft-03/schema#", "_id": "metaflows", + "additionalProperties": false, "description": "Actions applied to a call outside of the normal callflow, initiated by the caller(s)", "properties": { "binding_digit": { @@ -42,44 +43,27 @@ "type": "string" }, "numbers": { - "description": "A list of static numbers that the metaflow should match for", + "additionalProperties": false, + "description": "A list of static numbers with their flows", "patternProperties": { - "[0-9\\*\\#]+": { - "properties": { - "children": { - "default": {}, - "required": false, - "type": "#/flow" - }, - "data": { - "default": {}, - "description": "Module Data", - "required": true, - "type": "object" - }, - "module": { - "maxLength": 15, - "required": true, - "type": "string" - } - }, - "required": false, - "type": "object" + "^[0-9]+$": { + "$ref": "metaflow" } }, "required": false, "type": "object" }, "patterns": { - "description": "The metaflow patterns", - "itmes": { - "required": false, - "type": "string" + "additionalProperties": false, + "description": "A list of patterns with their flows", + "patternProperties": { + ".+": { + "$ref": "metaflow" + } }, "required": false, "type": "object" } }, - "required": true, "type": "object" } diff --git a/applications/crossbar/priv/couchdb/schemas/storage.attachment.aws.json b/applications/crossbar/priv/couchdb/schemas/storage.attachment.aws.json new file mode 100644 index 00000000000..446618cfce0 --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.attachment.aws.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.attachment.aws", + "description": "schema for aws attachment entry", + "properties": { + "handler": { + "enum": [ + "s3" + ] + }, + "name": { + "type": "string" + }, + "settings": { + "properties": { + "bucket": { + "type": "string" + }, + "key": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret": { + "type": "string" + } + }, + "required": [ + "bucket", + "key", + "secret" + ], + "type": "object" + } + }, + "required": [ + "handler", + "settings" + ], + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.attachment.google_drive.json b/applications/crossbar/priv/couchdb/schemas/storage.attachment.google_drive.json new file mode 100644 index 00000000000..6bc766cc00f --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.attachment.google_drive.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.attachment.google_drive", + "description": "schema for google drive attachment entry", + "properties": { + "handler": { + "enum": [ + "google_drive" + ] + }, + "name": { + "type": "string" + }, + "settings": { + "properties": { + "oauth_doc_id": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "oauth_doc_id" + ], + "type": "object" + } + }, + "required": [ + "handler", + "settings" + ], + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.attachments.json b/applications/crossbar/priv/couchdb/schemas/storage.attachments.json new file mode 100644 index 00000000000..d68348ac342 --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.attachments.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.attachments", + "patternProperties": { + "^[a-z,0-9]{32}$": { + "oneOf": [ + { + "$ref": "storage.attachment.aws" + }, + { + "$ref": "storage.attachment.google_drive" + } + ] + } + }, + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.connection.couchdb.json b/applications/crossbar/priv/couchdb/schemas/storage.connection.couchdb.json new file mode 100644 index 00000000000..f23a07d3317 --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.connection.couchdb.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.connection.couchdb", + "definitions": { + "credentials": { + "additionalProperties": false, + "properties": { + "password": { + "type": "integer" + }, + "username": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "type": "object" + }, + "pool": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "required": [ + "name", + "size" + ], + "type": "object" + } + }, + "description": "schema for couchdb connection entry", + "properties": { + "driver": { + "enum": [ + "kazoo_couch" + ] + }, + "name": { + "type": "string" + }, + "settings": { + "properties": { + "connect_options": { + "properties": { + "keepalive": { + "type": "boolean" + } + }, + "type": "object" + }, + "connect_timeout": { + "type": "integer" + }, + "credentials": { + "$ref": "#/definitions/credentials" + }, + "ip": { + "type": "string" + }, + "max_pipeline_size": { + "type": "integer" + }, + "max_sessions": { + "type": "integer" + }, + "pool": { + "$ref": "#/definitions/pool" + }, + "port": { + "type": "integer" + } + }, + "required": [ + "ip", + "port" + ], + "type": "object" + } + }, + "required": [ + "driver", + "settings" + ], + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.connections.json b/applications/crossbar/priv/couchdb/schemas/storage.connections.json new file mode 100644 index 00000000000..068f6cf7fd7 --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.connections.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.connections", + "patternProperties": { + "^([a-z,0-9]){32}$": { + "oneOf": [ + { + "$ref": "storage.connection.couchdb" + } + ] + }, + "local": { + "type": "object" + } + }, + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.json b/applications/crossbar/priv/couchdb/schemas/storage.json new file mode 100644 index 00000000000..9f8724ca08b --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage", + "additionalProperties": false, + "patternProperties": { + "^_": { + "type": [ + "string", + "integer", + "boolean", + "object" + ] + }, + "^pvt_": { + "type": [ + "string", + "integer", + "boolean" + ] + } + }, + "properties": { + "attachments": { + "$ref": "storage.attachments" + }, + "connections": { + "$ref": "storage.connections" + }, + "plan": { + "$ref": "storage.plan" + } + }, + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.plan.database.attachment.json b/applications/crossbar/priv/couchdb/schemas/storage.plan.database.attachment.json new file mode 100644 index 00000000000..1810cbd15d6 --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.plan.database.attachment.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.plan.database.attachment", + "description": "schema for attachment ref type storage plan", + "properties": { + "handler": { + "type": "string" + }, + "params": { + "type": "object" + }, + "stub": { + "type": "boolean" + } + } +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.plan.database.document.json b/applications/crossbar/priv/couchdb/schemas/storage.plan.database.document.json new file mode 100644 index 00000000000..fc6b4c323ae --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.plan.database.document.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.plan.database.document", + "description": "schema for document type storage plan", + "properties": { + "attachments": { + "$ref": "storage.plan.database.attachment" + }, + "connection": { + "type": "string" + } + }, + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.plan.database.json b/applications/crossbar/priv/couchdb/schemas/storage.plan.database.json new file mode 100644 index 00000000000..443262adf5c --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.plan.database.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.plan.database", + "additionalProperties": false, + "definitions": { + "database": { + "properties": { + "create_options": { + "type": "object" + } + }, + "type": "object" + } + }, + "description": "schema for database storage plan", + "properties": { + "attachments": { + "$ref": "storage.plan.database.attachment" + }, + "connection": { + "type": "string" + }, + "database": { + "$ref": "#/definitions/database" + }, + "types": { + "additionalProperties": false, + "properties": { + "call_recording": { + "$ref": "storage.plan.database.document" + }, + "fax": { + "$ref": "storage.plan.database.document" + }, + "mailbox_message": { + "$ref": "storage.plan.database.document" + }, + "media": { + "$ref": "storage.plan.database.document" + } + }, + "type": "object" + } + }, + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/storage.plan.json b/applications/crossbar/priv/couchdb/schemas/storage.plan.json new file mode 100644 index 00000000000..aa79d3d5dea --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/storage.plan.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "storage.plan", + "additionalProperties": false, + "description": "schema for storage plan", + "properties": { + "account": { + "$ref": "storage.plan.database" + }, + "modb": { + "$ref": "storage.plan.database" + }, + "system": { + "$ref": "storage.plan.database" + } + }, + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/system_config.crossbar.json b/applications/crossbar/priv/couchdb/schemas/system_config.crossbar.json index 93246afde28..4aa981262de 100644 --- a/applications/crossbar/priv/couchdb/schemas/system_config.crossbar.json +++ b/applications/crossbar/priv/couchdb/schemas/system_config.crossbar.json @@ -31,6 +31,11 @@ "description": "crossbar default language", "type": "string" }, + "ensure_valid_schema": { + "default": true, + "description": "crossbar ensure valid schema", + "type": "boolean" + }, "expiry_percentage": { "default": 75, "description": "crossbar expiry percentage", diff --git a/applications/crossbar/src/cb_context.erl b/applications/crossbar/src/cb_context.erl index 1d4b7e23dae..962e1e2c26b 100644 --- a/applications/crossbar/src/cb_context.erl +++ b/applications/crossbar/src/cb_context.erl @@ -26,6 +26,9 @@ ,is_authenticated/1 ,is_superduper_admin/1 + ,is_account_admin/1 + + ,system_error/2 %% Getters / Setters ,setters/2 @@ -217,6 +220,10 @@ is_superduper_admin(AccountId=?NE_BINARY) -> is_superduper_admin(Context) -> is_superduper_admin(auth_account_id(Context)). +-spec is_account_admin(context()) -> boolean(). +is_account_admin(#cb_context{auth_doc=Doc}) -> + kzd_user:priv_level(Doc) =:= <<"admin">>. + auth_token_type(#cb_context{auth_token_type=AuthTokenType}) -> AuthTokenType. auth_token(#cb_context{auth_token=AuthToken}) -> AuthToken. auth_doc(#cb_context{auth_doc=AuthDoc}) -> AuthDoc. @@ -632,7 +639,12 @@ response(#cb_context{resp_error_code=Code validate_request_data('undefined', Context) -> passed(Context); validate_request_data(<<_/binary>> = Schema, Context) -> + DefaultStrict = kapps_config:get_is_true(?CONFIG_CAT, <<"ensure_valid_schema">>, 'true'), + Strict = fetch(Context, 'ensure_valid_schema', DefaultStrict), case find_schema(Schema) of + 'undefined' when Strict -> + Msg = <<"schema ", Schema/binary, " not found.">>, + system_error(Context, Msg); 'undefined' -> passed(set_doc(Context, req_data(Context))); SchemaJObj -> @@ -928,3 +940,30 @@ maybe_fix_index(Keys) end, Keys); maybe_fix_index(Key) -> Key. + +-spec system_error_props(context()) -> kz_proplist(). +system_error_props(Context) -> + Extract = [{fun account_id/1, <<"account_id">>} + ,{fun account_name/1, <<"account_name">>} + ,{fun auth_account_id/1, <<"auth_account_id">>} + ,{fun(C) -> kz_json:from_list(req_headers(C)) end, <<"req_headers">>} + ,{fun req_json/1, <<"req_json">>} + ,{fun req_data/1, <<"req_data">>} + ,{fun query_string/1, <<"query_json">>} + ,{fun req_id/1, <<"req_id">>} + ], + Fun = fun({Fun, K}, KVs) -> [{K, Fun(Context)} | KVs] end, + Props = lists:foldl(Fun, [], Extract), + props:filter_undefined(Props). + +-spec system_error(context(), ne_binary()) -> context(). +system_error(Context, Error) -> + Notify = props:filter_undefined( + [{<<"Subject">>, <<"api error - ", Error/binary>>} + ,{<<"Message">>, Error} + ,{<<"Details">>, kz_json:from_list(system_error_props(Context))} + ,{<<"Account-ID">>, auth_account_id(Context)} + | kz_api:default_headers(?APP_NAME, ?APP_VERSION) + ]), + kz_amqp_worker:cast(Notify, fun kapi_notifications:publish_system_alert/1), + add_system_error(Error, Context). diff --git a/applications/crossbar/src/modules/cb_storage.erl b/applications/crossbar/src/modules/cb_storage.erl new file mode 100644 index 00000000000..024abffe631 --- /dev/null +++ b/applications/crossbar/src/modules/cb_storage.erl @@ -0,0 +1,388 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2011-2016, 2600Hz INC +%%% @doc +%%% +%%% storage +%%% +%%% @end +%%% @contributors: +%%%------------------------------------------------------------------- +-module(cb_storage). + +-export([init/0 + ,authorize/1, authorize/2, authorize/3 + ,allowed_methods/0, allowed_methods/1, allowed_methods/2 + ,resource_exists/0, resource_exists/1, resource_exists/2 + ,validate/1, validate/2, validate/3 + ,put/1, put/2 + ,post/1, post/3 + ,delete/1, delete/3 + ]). + +-include("crossbar.hrl"). + +-define(CB_ACCOUNT_LIST, <<"storage/plans_by_account">>). +-define(CB_SYSTEM_LIST, <<"storage/system_plans">>). + +-define(SYSTEM_DATAPLAN, <<"system">>). + +-define(PLANS_TOKEN, <<"plans">>). + +-define(STORAGE_TYPES, [<<"storage">>, <<"storage_plan">>]). +-define(STORAGE_CHECK_OPTION, {?OPTION_EXPECTED_TYPE, ?STORAGE_TYPES}). +-define(STORAGE_CHECK_OPTIONS, [?STORAGE_CHECK_OPTION]). + +-type scope() :: 'system' + | 'system_plans' + | {'system_plan', ne_binary()} + | {'user', ne_binary(), ne_binary()} + | {'account', ne_binary()} + | {'reseller_plans', ne_binary()} + | {'reseller_plan', ne_binary(), ne_binary()} + | 'invalid'. + +%%%=================================================================== +%%% API +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Initializes the bindings this module will respond to. +%% @end +%%-------------------------------------------------------------------- +-spec init() -> 'ok'. +init() -> + _ = crossbar_bindings:bind(<<"*.authorize">>, ?MODULE, 'authorize'), + _ = crossbar_bindings:bind(<<"*.allowed_methods.storage">>, ?MODULE, 'allowed_methods'), + _ = crossbar_bindings:bind(<<"*.resource_exists.storage">>, ?MODULE, 'resource_exists'), + _ = crossbar_bindings:bind(<<"*.validate.storage">>, ?MODULE, 'validate'), + _ = crossbar_bindings:bind(<<"*.execute.get.storage">>, ?MODULE, 'get'), + _ = crossbar_bindings:bind(<<"*.execute.put.storage">>, ?MODULE, 'put'), + _ = crossbar_bindings:bind(<<"*.execute.post.storage">>, ?MODULE, 'post'), + _ = crossbar_bindings:bind(<<"*.execute.delete.storage">>, ?MODULE, 'delete'). + + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Authorizes the incoming request, returning true if the requestor is +%% allowed to access the resource, or false if not. +%% @end +%%-------------------------------------------------------------------- +-spec authorize(cb_context:context()) -> boolean(). +authorize(Context) -> + do_authorize(set_scope(Context)). + +-spec authorize(cb_context:context(), path_token()) -> boolean(). +authorize(Context, ?PLANS_TOKEN) -> + do_authorize(set_scope(Context)). + +-spec authorize(cb_context:context(), path_token(), path_token()) -> boolean(). +authorize(Context, ?PLANS_TOKEN, _PlanId) -> + do_authorize(set_scope(Context)). + +-spec do_authorize(cb_context:context()) -> boolean(). +do_authorize(Context) -> + do_authorize(Context, scope(Context)). + +-spec do_authorize(cb_context:context(), scope()) -> boolean(). +do_authorize(_Context, 'invalid') -> 'false'; +do_authorize(Context, 'system') -> cb_context:is_superduper_admin(Context); +do_authorize(Context, 'system_plans') -> cb_context:is_superduper_admin(Context); +do_authorize(Context, {'system_plan', _PlanId}) -> cb_context:is_superduper_admin(Context); +do_authorize(Context, {'reseller_plans', _AccountId}) -> + kz_account:is_reseller(cb_context:account_doc(Context)); +do_authorize(Context, {'reseller_plan', _PlanId, _AccountId}) -> + kz_account:is_reseller(cb_context:account_doc(Context)); +do_authorize(Context, {'account', AccountId}) -> + cb_context:is_superduper_admin(Context) + orelse kz_services:get_reseller_id(AccountId) =:= cb_context:auth_account_id(Context) + orelse AccountId =:= cb_context:auth_account_id(Context); +do_authorize(Context, {'user', UserId, AccountId}) -> + cb_context:is_superduper_admin(Context) + orelse kz_services:get_reseller_id(AccountId) =:= cb_context:auth_account_id(Context) + orelse ( (AccountId =:= cb_context:auth_account_id(Context) + andalso cb_context:is_account_admin(Context) + ) + orelse + (AccountId =:= cb_context:auth_account_id(Context) + andalso UserId =:= cb_context:auth_user_id(Context) + ) + ). + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Given the path tokens related to this module, what HTTP methods are +%% going to be responded to. +%% @end +%%-------------------------------------------------------------------- +-spec allowed_methods() -> http_methods(). +allowed_methods() -> + [?HTTP_GET, ?HTTP_PUT, ?HTTP_POST, ?HTTP_DELETE]. + +-spec allowed_methods(path_token()) -> http_methods(). +allowed_methods(?PLANS_TOKEN) -> + [?HTTP_GET, ?HTTP_PUT]. + +-spec allowed_methods(path_token(), path_token()) -> http_methods(). +allowed_methods(?PLANS_TOKEN, _StoragePlanId) -> + [?HTTP_GET, ?HTTP_POST, ?HTTP_DELETE]. + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Does the path point to a valid resource +%% So /storage => [] +%% /storage/foo => [<<"foo">>] +%% /storage/foo/bar => [<<"foo">>, <<"bar">>] +%% @end +%%-------------------------------------------------------------------- +-spec resource_exists() -> 'true'. +resource_exists() -> 'true'. + +-spec resource_exists(path_token()) -> 'true'. +resource_exists(?PLANS_TOKEN) -> 'true'. + +-spec resource_exists(path_token(), path_token()) -> 'true'. +resource_exists(?PLANS_TOKEN, _PlanId) -> 'true'. + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Check the request (request body, query string params, path tokens, etc) +%% and load necessary information. +%% /storage mights load a list of storage objects +%% /storage/123 might load the storage object 123 +%% Generally, use crossbar_doc to manipulate the cb_context{} record +%% @end +%%-------------------------------------------------------------------- +-spec validate(cb_context:context()) -> cb_context:context(). +validate(Context) -> + validate_storage(set_scope(Context), cb_context:req_verb(Context)). + +-spec validate(cb_context:context(), path_token()) -> cb_context:context(). +validate(Context, ?PLANS_TOKEN) -> + validate_storage_plans(set_scope(Context), cb_context:req_verb(Context)). + +-spec validate(cb_context:context(), path_token(), path_token()) -> cb_context:context(). +validate(Context, ?PLANS_TOKEN, PlanId) -> + validate_storage_plan(set_scope(Context), PlanId, cb_context:req_verb(Context)). + + +-spec validate_storage(cb_context:context(), http_method()) -> cb_context:context(). +validate_storage(Context, ?HTTP_GET) -> + read(Context); +validate_storage(Context, ?HTTP_PUT) -> + create(Context); +validate_storage(Context, ?HTTP_POST) -> + update(Context); +validate_storage(Context, ?HTTP_DELETE) -> + read(Context). + +-spec validate_storage_plans(cb_context:context(), http_method()) -> cb_context:context(). +validate_storage_plans(Context, ?HTTP_GET) -> + summary(Context); +validate_storage_plans(Context, ?HTTP_PUT) -> + create(Context). + +-spec validate_storage_plan(cb_context:context(), ne_binary(), http_method()) -> cb_context:context(). +validate_storage_plan(Context, PlanId, ?HTTP_GET) -> + read(Context, PlanId); +validate_storage_plan(Context, PlanId, ?HTTP_POST) -> + update(Context, PlanId); +validate_storage_plan(Context, PlanId, ?HTTP_DELETE) -> + read(Context, PlanId). + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% If the HTTP verb is PUT, execute the actual action, usually a db save. +%% @end +%%-------------------------------------------------------------------- +-spec put(cb_context:context()) -> cb_context:context(). +put(Context) -> + crossbar_doc:save(Context). + +-spec put(cb_context:context(), path_token()) -> cb_context:context(). +put(Context, ?PLANS_TOKEN) -> + crossbar_doc:save(Context). + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% If the HTTP verb is POST, execute the actual action, usually a db save +%% (after a merge perhaps). +%% @end +%%-------------------------------------------------------------------- +-spec post(cb_context:context()) -> cb_context:context(). +post(Context) -> + crossbar_doc:save(Context). + +-spec post(cb_context:context(), path_token(), path_token()) -> cb_context:context(). +post(Context, ?PLANS_TOKEN, _PlanId) -> + crossbar_doc:save(Context). + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% If the HTTP verb is DELETE, execute the actual action, usually a db delete +%% @end +%%-------------------------------------------------------------------- +-spec delete(cb_context:context()) -> cb_context:context(). +delete(Context) -> + crossbar_doc:delete(Context). + +-spec delete(cb_context:context(), path_token(), path_token()) -> cb_context:context(). +delete(Context, ?PLANS_TOKEN, _PlanId) -> + crossbar_doc:delete(Context). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Create a new instance with the data provided, if it is valid +%% @end +%%-------------------------------------------------------------------- +-spec create(cb_context:context()) -> cb_context:context(). +create(Context) -> + OnSuccess = fun(C) -> on_successful_validation(doc_id(Context), C) end, + cb_context:validate_request_data(<<"storage">>, Context, OnSuccess). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Load an instance from the database +%% @end +%%-------------------------------------------------------------------- +-spec read(cb_context:context()) -> cb_context:context(). +read(Context) -> + crossbar_doc:load(doc_id(Context), Context, ?TYPE_CHECK_OPTION(<<"storage">>)). + +-spec read(cb_context:context(), path_token()) -> cb_context:context(). +read(Context, PlanId) -> + crossbar_doc:load(PlanId, Context, ?TYPE_CHECK_OPTION(<<"storage">>)). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Update an existing menu document with the data provided, if it is +%% valid +%% @end +%%-------------------------------------------------------------------- +-spec update(cb_context:context()) -> cb_context:context(). +update(Context) -> + OnSuccess = fun(C) -> on_successful_validation(doc_id(Context), C) end, + cb_context:validate_request_data(<<"storage">>, Context, OnSuccess). + +-spec update(cb_context:context(), path_token()) -> cb_context:context(). +update(Context, PlanId) -> + OnSuccess = fun(C) -> on_successful_validation(PlanId, C) end, + cb_context:validate_request_data(<<"storage">>, Context, OnSuccess). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Attempt to load a summarized listing of all instances of this +%% resource. +%% @end +%%-------------------------------------------------------------------- +-spec summary(cb_context:context()) -> cb_context:context(). +summary(Context) -> + summary(Context, scope(Context)). + +-spec summary(cb_context:context(), scope()) -> cb_context:context(). +summary(Context, 'system_plans') -> + crossbar_doc:load_view(?CB_SYSTEM_LIST, ['include_docs'], Context, fun normalize_view_results/2); + +summary(Context, {'reseller_plans', AccountId}) -> + Options = ['include_docs' + ,{'startkey', [AccountId]} + ,{'endkey', [AccountId, kz_json:new()]} + ], + crossbar_doc:load_view(?CB_ACCOUNT_LIST, Options, Context, fun normalize_view_results/2). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% +%% @end +%%-------------------------------------------------------------------- +-spec on_successful_validation(api_binary(), cb_context:context()) -> cb_context:context(). +on_successful_validation(Id, Context) -> + on_successful_validation(Id, cb_context:req_verb(Context), Context). + +-spec on_successful_validation(api_binary(), http_method(), cb_context:context()) -> cb_context:context(). +on_successful_validation('undefined', ?HTTP_PUT, Context) -> + IsSystemPlan = scope(Context) =:= 'system_plans', + JObj = cb_context:doc(Context), + Routines = [fun(Doc) -> kz_doc:set_type(Doc, <<"storage_plan">>) end + ,fun(Doc) -> kz_json:set_value(<<"pvt_system_plan">>, IsSystemPlan, Doc) end + ], + cb_context:set_doc(Context, kz_json:exec(Routines, JObj)); +on_successful_validation(Id, ?HTTP_PUT, Context) -> + JObj = cb_context:doc(Context), + Routines = [fun(Doc) -> kz_doc:set_type(Doc, <<"storage">>) end + ,fun(Doc) -> kz_doc:set_id(Doc, Id) end + ], + cb_context:set_doc(Context, kz_json:exec(Routines, JObj)); +on_successful_validation(Id, ?HTTP_POST, Context) -> + crossbar_doc:load_merge(Id, Context, ?STORAGE_CHECK_OPTIONS). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Normalizes the resuts of a view +%% @end +%%-------------------------------------------------------------------- +-spec normalize_view_results(kz_json:object(), kz_json:objects()) -> kz_json:objects(). +normalize_view_results(JObj, Acc) -> + [kz_json:get_value(<<"doc">>, JObj)|Acc]. + +-spec scope(cb_context:context()) -> scope(). +scope(Context) -> + cb_context:fetch(Context, 'scope'). + +-spec set_scope(cb_context:context()) -> cb_context:context(). +set_scope(Context) -> + Setters = [{fun cb_context:store/3, 'ensure_valid_schema', 'true'} + ,{fun cb_context:set_account_db/2, ?KZ_DATA_DB} + ], + set_scope(cb_context:setters(Context, Setters), cb_context:req_nouns(Context)). + +-spec set_scope(cb_context:context(), req_nouns()) -> scope(). +set_scope(Context, [{<<"storage">>, []}]) -> + cb_context:store(Context, 'scope', 'system'); +set_scope(Context, [{<<"storage">>, [?PLANS_TOKEN]}]) -> + cb_context:store(Context, 'scope', 'system_plans'); +set_scope(Context, [{<<"storage">>, [?PLANS_TOKEN, PlanId]}]) -> + cb_context:store(Context, 'scope', {'system_plan', PlanId}); +set_scope(Context, [{<<"storage">>, []} + ,{<<"accounts">>, [AccountId]} + ]) -> + cb_context:store(Context, 'scope', {'account', AccountId}); +set_scope(Context, [{<<"storage">>, [?PLANS_TOKEN]} + ,{<<"accounts">>, [AccountId]} + ]) -> + cb_context:store(Context, 'scope', {'reseller_plans', AccountId}); +set_scope(Context, [{<<"storage">>, [?PLANS_TOKEN, PlanId]} + ,{<<"accounts">>, [AccountId]} + ]) -> + cb_context:store(Context, 'scope', {'reseller_plan', PlanId, AccountId}); +set_scope(Context, [{<<"storage">>, []} + ,{<<"users">>, [UserId]} + ,{<<"accounts">>, [AccountId]} + ]) -> + cb_context:store(Context, 'scope', {'user', UserId, AccountId}); +set_scope(Context, _) -> + cb_context:store(Context, 'scope', 'invalid'). + +-spec doc_id(cb_context:context() | scope()) -> api_binary(). +doc_id('system') -> <<"system">>; +doc_id('system_plans') -> 'undefined'; +doc_id({'system_plan', PlanId}) -> PlanId; +doc_id({'account', AccountId}) -> AccountId; +doc_id({'user', UserId, _AccountId}) -> UserId; +doc_id({'reseller_plans', _AccountId}) -> 'undefined'; +doc_id({'reseller_plan', PlanId, _AccountId}) -> PlanId; +doc_id(Context) -> doc_id(scope(Context)). diff --git a/core/kazoo/src/kz_api.erl b/core/kazoo/src/kz_api.erl index 9c745de5870..e131e86c525 100644 --- a/core/kazoo/src/kz_api.erl +++ b/core/kazoo/src/kz_api.erl @@ -72,11 +72,11 @@ %%%=================================================================== -spec server_id(kz_json:object()) -> api_binary(). server_id(JObj) -> - kz_json:get_value(?KEY_SERVER_ID, JObj). + kz_json:get_ne_binary_value(?KEY_SERVER_ID, JObj). -spec queue_id(kz_json:object()) -> api_binary(). queue_id(JObj) -> - kz_json:get_value(?KEY_QUEUE_ID, JObj, server_id(JObj)). + kz_json:get_ne_binary_value(?KEY_QUEUE_ID, JObj, server_id(JObj)). -spec event_category(kz_json:object()) -> api_binary(). event_category(JObj) -> diff --git a/core/kazoo/src/kz_json_schema.erl b/core/kazoo/src/kz_json_schema.erl index 05eccd5fd37..7b72fd919c8 100644 --- a/core/kazoo/src/kz_json_schema.erl +++ b/core/kazoo/src/kz_json_schema.erl @@ -123,7 +123,8 @@ validate(SchemaJObj, DataJObj) -> ]). validate(<<_/binary>> = Schema, DataJObj, Options) -> - {'ok', SchemaJObj} = load(Schema), + Fun = props:get_value('schema_loader_fun', Options, fun load/1), + {'ok', SchemaJObj} = Fun(Schema), validate(SchemaJObj, DataJObj, Options); validate(SchemaJObj, DataJObj, Options) -> jesse:validate_with_schema(SchemaJObj, DataJObj, Options). diff --git a/core/kazoo_apps/src/kapps_maintenance.erl b/core/kazoo_apps/src/kapps_maintenance.erl index ae719170250..369d951530e 100644 --- a/core/kazoo_apps/src/kapps_maintenance.erl +++ b/core/kazoo_apps/src/kapps_maintenance.erl @@ -182,6 +182,8 @@ refresh(?KZ_CONFIG_DB) -> kz_datamgr:revise_doc_from_file(?KZ_CONFIG_DB, 'teletype', <<"views/notifications.json">>), cleanup_invalid_notify_docs(), delete_system_media_references(); +refresh(?KZ_DATA_DB) -> + kz_datamgr:revise_docs_from_folder(?KZ_DATA_DB, 'kazoo_data', <<"views">>); refresh(?KZ_OAUTH_DB) -> kz_datamgr:db_create(?KZ_OAUTH_DB), kazoo_oauth_maintenance:register_common_providers(); diff --git a/core/kazoo_ast/src/cb_api_endpoints.erl b/core/kazoo_ast/src/cb_api_endpoints.erl index c87492a5cee..61dc6aa7196 100644 --- a/core/kazoo_ast/src/cb_api_endpoints.erl +++ b/core/kazoo_ast/src/cb_api_endpoints.erl @@ -847,6 +847,7 @@ def_path_param(<<"{RATE_ID}">>=P) -> generic_id_path_param(P); def_path_param(<<"{RESOURCE_ID}">>=P) -> generic_id_path_param(P); def_path_param(<<"{RESOURCE_TEMPLATE_ID}">>=P) -> generic_id_path_param(P); def_path_param(<<"{SMS_ID}">>=P) -> generic_id_path_param(P); +def_path_param(<<"{STORAGE_PLAN_ID}">>=P) -> generic_id_path_param(P); def_path_param(<<"{TEMPLATE_ID}">>=P) -> generic_id_path_param(P); def_path_param(<<"{TEMPORAL_RULE_ID}">>=P) -> generic_id_path_param(P); def_path_param(<<"{TEMPORAL_RULE_SET}">>=P) -> generic_id_path_param(P); diff --git a/core/kazoo_config/src/kapps_config.erl b/core/kazoo_config/src/kapps_config.erl index 7c52d67d8ef..0602d8f6136 100644 --- a/core/kazoo_config/src/kapps_config.erl +++ b/core/kazoo_config/src/kapps_config.erl @@ -48,7 +48,7 @@ -type update_options() :: [update_option()]. -type fetch_ret() :: {'ok', kz_json:object()} | - {'error', 'not_found'}. + {'error', any()}. -define(KEY_DEFAULT, <<"default">>). @@ -323,6 +323,9 @@ get(Category, Keys, Default, Node) -> {'error', 'not_found'} -> lager:debug("missing category ~s(default) ~p: ~p", [Category, Keys, Default]), _ = set(Category, Keys, Default), + Default; + {'error', Error} -> + lager:debug("error ~p getting category ~s(default) ~p: ~p", [Error, Category, Keys, Default]), Default end. @@ -350,6 +353,9 @@ get_current(Category, Keys, Default, Node) -> {'error', 'not_found'} -> lager:debug("missing category ~s(default) ~p: ~p", [Category, Keys, Default]), _ = set(Category, Keys, Default), + Default; + {'error', Error} -> + lager:debug("error ~p getting category ~s(default) ~p: ~p", [Error, Category, Keys, Default]), Default end. @@ -720,9 +726,6 @@ get_category(Category, 'false') -> ,{{<<"callflow.mobile">>, <<"sms_interface">>} ,{<<"kazoo_endpoint.mobile">>, <<"sms_interface">>} } - ,{{<<"callflow.mobile">>, [<<"sms">>, <<"connections">>]} - ,{<<"kazoo_endpoint.mobile">>, [<<"sms">>, <<"connections">>]} - } ,{{<<"callflow">>, <<"recorder_module">>} ,{<<"kazoo_endpoint">>, <<"recorder_module">>} } diff --git a/core/kazoo_couch/src/kz_couch_util.erl b/core/kazoo_couch/src/kz_couch_util.erl index d8b9fbb0efc..76be377ee4d 100644 --- a/core/kazoo_couch/src/kz_couch_util.erl +++ b/core/kazoo_couch/src/kz_couch_util.erl @@ -123,6 +123,17 @@ convert_option({K, V} = KV) -> end. -spec connection_parse(any(), any(), couch_connection()) -> couch_connection(). +connection_parse(settings, Map, Conn) + when is_map(Map) -> + maps:fold(fun connection_parse/3, Conn, Map); +connection_parse(credentials, #{username := Username, password := Password}, Conn) -> + Conn#kz_couch_connection{username=Username, password=Password}; +connection_parse(pool, #{name := PoolName, size := PoolSize}, #kz_couch_connection{options=Options}=Conn) -> + KVs = [{pool, PoolName} + ,{pool_name, PoolName} + ,{pool_size, PoolSize} + ], + Conn#kz_couch_connection{options = Options ++ KVs}; connection_parse(ip, V, Conn) -> Conn#kz_couch_connection{host=V}; connection_parse(host, V, Conn) -> diff --git a/core/kazoo_data/priv/couchdb/views/storage.json b/core/kazoo_data/priv/couchdb/views/storage.json new file mode 100644 index 00000000000..c8d58fe061b --- /dev/null +++ b/core/kazoo_data/priv/couchdb/views/storage.json @@ -0,0 +1,15 @@ +{ + "_id": "_design/storage", + "language": "javascript", + "views": { + "crossbar_listing": { + "map": "function(doc) {if (doc.pvt_type != 'storage_plan' || doc.pvt_deleted) return; emit(doc._id, null);}" + }, + "plans_by_account": { + "map": "function(doc) { if (doc.pvt_type != 'storage_plan' || doc.pvt_deleted || doc.pvt_system_plan) return; emit([doc.pvt_account_id, doc._id], null); }" + }, + "system_plans": { + "map": "function(doc) { if (doc.pvt_type != 'storage_plan' || doc.pvt_deleted || !doc.pvt_system_plan) return; emit(doc._id, null); }" + } + } +} diff --git a/core/kazoo_data/src/kzs_cache.erl b/core/kazoo_data/src/kzs_cache.erl index 071cd3a90cc..b7fbcd7a3eb 100644 --- a/core/kazoo_data/src/kzs_cache.erl +++ b/core/kazoo_data/src/kzs_cache.erl @@ -90,7 +90,8 @@ maybe_cache_failure(DbName, DocId, Options, Error) -> case props:get_value('cache_failures', Options) of ErrorCodes when is_list(ErrorCodes) -> maybe_cache_failure(DbName, DocId, Options, Error, ErrorCodes); - 'true' -> add_to_doc_cache(DbName, DocId, Error); + 'true' -> + maybe_cache_failure(DbName, DocId, Options, Error, ['not_found']); _ -> 'ok' end. diff --git a/core/kazoo_data/src/kzs_plan.erl b/core/kazoo_data/src/kzs_plan.erl index 68ef91501ec..c9e7279ed0a 100644 --- a/core/kazoo_data/src/kzs_plan.erl +++ b/core/kazoo_data/src/kzs_plan.erl @@ -182,7 +182,7 @@ dataplan_match(Classification, Plan, AccountId) -> ,<<"settings">> := AttSettings } } = GAtt, - AttHandler = kz_util:to_atom(AttHandlerBin,'true'), + AttHandler = kz_util:to_atom(<<"kz_att_", AttHandlerBin/binary>>,'true'), Params = maps:merge(AttSettings, maps:get(<<"params">>, CAtt, #{})), #{tag => Tag @@ -230,7 +230,7 @@ dataplan_type_match(Classification, DocType, Plan, AccountId) -> ,<<"settings">> := AttSettings } } = GAtt, - AttHandler = kz_util:to_atom(AttHandlerBin,'true'), + AttHandler = kz_util:to_atom(<<"kz_att_", AttHandlerBin/binary>>,'true'), Params = maps:merge(AttSettings, maps:get(<<"params">>, TypeAttMap, #{})), #{tag => Tag ,server => Server diff --git a/core/kazoo_media/src/kz_media_recording.erl b/core/kazoo_media/src/kz_media_recording.erl index 85b5475e052..29063c00f3d 100644 --- a/core/kazoo_media/src/kz_media_recording.erl +++ b/core/kazoo_media/src/kz_media_recording.erl @@ -493,6 +493,20 @@ store_url(#state{doc_db=Db ,field_list => [<<"call_recording_">> ,{field, <<"call_id">>} ,<<".", Ext/binary>> + ,<<"?from=">> + ,{field, <<"from">>} + ,<<"&to=">> + ,{field, <<"to">>} + ,<<"&caller_id_name=">> + ,{field, <<"caller_id_name">>} + ,<<"&caller_id_number=">> + ,{field, <<"caller_id_number">>} + ,<<"&call_id=">> + ,{field, <<"call_id">>} + ,<<"&cdr_id=">> + ,{field, <<"cdr_id">>} + ,<<"&interaction_id=">> + ,{field, <<"interaction_id">>} ] }, Handler = #{att_proxy => 'true' diff --git a/make/deps.mk b/make/deps.mk index d21c86aee45..5a74929c09d 100644 --- a/make/deps.mk +++ b/make/deps.mk @@ -13,13 +13,22 @@ dep_amqp_client_commit = rabbitmq_v3_6_0 dep_eflame = git https://github.com/slfritchie/eflame 7b0bb1a7e8c8482a59421a3a50ae69d49af59d52 dep_detergent = git https://github.com/pap/detergent e86dfeded3e4f9f3f9278c6a1aea802079d38b54 dep_jiffy = hex 0.14.7 -dep_jesse = git https://github.com/for-GET/jesse 1.4.0 dep_nklib = git https://github.com/NetComposer/nklib -dep_couchbeam = git https://github.com/lazedo/couchbeam ccc80359707ce386fec6ea9a42803369d33f089a +dep_couchbeam = git https://github.com/lazedo/couchbeam bde6256afc1e9048c53db9b5fef4f8b1d83ebc7c ###dep_couchbeam = git https://github.com/benoitc/couchbeam 1.3.1 ### waiting for pull request ### https://github.com/benoitc/couchbeam/pull/149 +### after that one is merged, another will follow + +##dep_jesse = git https://github.com/for-GET/jesse 1.4.0 +## pull request pending +## https://github.com/for-GET/jesse/pull/26 +## other to come +## there are some commits after 1.4.0 that will go into 1.5.0 +## that break a loot of stuff. please be careful if updating to 1.5.0 +## from for-GET/jesse +dep_jesse = git https://github.com/lazedo/jesse 1.4.1 dep_lager = git https://github.com/basho/lager 3.2.1 dep_fs_sync = git https://github.com/jamhed/fs_sync