mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-07-24 21:12:10 +00:00
revamp db - refactor webhooks
This commit is contained in:
+55
-14
@@ -1,8 +1,35 @@
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const listSqlSp = 'SELECT * from accounts WHERE service_provider_sid = ?';
|
||||
const listSqlAccount = 'SELECT * from accounts WHERE account_sid = ?';
|
||||
const retrieveSql = 'SELECT * from accounts WHERE service_provider_sid = ? AND account_sid = ?';
|
||||
|
||||
const retrieveSql = `SELECT * from accounts acc
|
||||
LEFT JOIN webhooks AS rh
|
||||
ON acc.registration_hook_sid = rh.webhook_sid
|
||||
LEFT JOIN webhooks AS dh
|
||||
ON acc.device_calling_hook_sid = dh.webhook_sid
|
||||
LEFT JOIN webhooks AS eh
|
||||
ON acc.error_hook_sid = eh.webhook_sid`;
|
||||
|
||||
function transmogrifyResults(results) {
|
||||
return results.map((row) => {
|
||||
const obj = row.acc;
|
||||
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
|
||||
Object.assign(obj, {registration_hook: row.rh});
|
||||
}
|
||||
else obj.registration_hook = null;
|
||||
if (row.dh && Object.keys(row.dh).length && row.dh.url !== null) {
|
||||
Object.assign(obj, {device_calling_hook: row.dh});
|
||||
}
|
||||
else obj.device_calling_hook = null;
|
||||
if (row.eh && Object.keys(row.eh).length && row.eh.url !== null) {
|
||||
Object.assign(obj, {error_hook: row.eh});
|
||||
}
|
||||
else obj.error_hook = null;
|
||||
delete obj.registration_hook_sid;
|
||||
delete obj.device_calling_hook_sid;
|
||||
delete obj.error_hook_sid;
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
class Account extends Model {
|
||||
constructor() {
|
||||
@@ -13,16 +40,24 @@ class Account extends Model {
|
||||
* list all accounts
|
||||
*/
|
||||
static retrieveAll(service_provider_sid, account_sid) {
|
||||
if (!service_provider_sid && !account_sid) return super.retrieveAll();
|
||||
let sql = retrieveSql;
|
||||
const args = [];
|
||||
if (account_sid) {
|
||||
sql = `${sql} WHERE acc.account_sid = ?`;
|
||||
args.push(account_sid);
|
||||
}
|
||||
else if (service_provider_sid) {
|
||||
sql = `${sql} WHERE acc.service_provider_sid = ?`;
|
||||
args.push(service_provider_sid);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
const sql = account_sid ? listSqlAccount : listSqlSp;
|
||||
const args = account_sid ? [account_sid] : [service_provider_sid];
|
||||
conn.query(sql, args, (err, results, fields) => {
|
||||
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
const r = transmogrifyResults(results);
|
||||
resolve(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -32,14 +67,20 @@ class Account extends Model {
|
||||
* retrieve an account
|
||||
*/
|
||||
static retrieve(sid, service_provider_sid) {
|
||||
if (!service_provider_sid) return super.retrieve(sid);
|
||||
const args = [sid];
|
||||
let sql = `${retrieveSql} WHERE acc.account_sid = ?`;
|
||||
if (service_provider_sid) {
|
||||
sql = `${retrieveSql} WHERE acc.account_sid = ? AND acc.service_provider_sid = ?`;
|
||||
args.push(service_provider_sid);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(retrieveSql, [service_provider_sid, sid], (err, results, fields) => {
|
||||
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
const r = transmogrifyResults(results);
|
||||
resolve(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -69,15 +110,15 @@ Account.fields = [
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'registration_hook',
|
||||
name: 'registration_hook_sid',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'hook_basic_auth_user',
|
||||
name: 'device_calling_hook_sid',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'hook_basic_auth_password',
|
||||
name: 'error_hook_sid',
|
||||
type: 'string',
|
||||
}
|
||||
];
|
||||
|
||||
+51
-24
@@ -19,6 +19,29 @@ SELECT * from applications
|
||||
WHERE account_sid = ?
|
||||
AND application_sid = ?`;
|
||||
|
||||
const retrieveSql = `SELECT * from applications app
|
||||
LEFT JOIN webhooks AS ch
|
||||
ON app.call_hook_sid = ch.webhook_sid
|
||||
LEFT JOIN webhooks AS sh
|
||||
ON app.call_status_hook_sid = sh.webhook_sid`;
|
||||
|
||||
function transmogrifyResults(results) {
|
||||
return results.map((row) => {
|
||||
const obj = row.app;
|
||||
if (row.ch && Object.keys(row.ch).length && row.ch.url !== null) {
|
||||
Object.assign(obj, {call_hook: row.ch});
|
||||
}
|
||||
else obj.call_hook = null;
|
||||
if (row.sh && Object.keys(row.sh).length && row.sh.url !== null) {
|
||||
Object.assign(obj, {call_status_hook: row.sh});
|
||||
}
|
||||
else obj.call_status_hook = null;
|
||||
delete obj.call_hook_sid;
|
||||
delete obj.call_status_hook_sid;
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
class Application extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -28,16 +51,24 @@ class Application extends Model {
|
||||
* list all applications - for all service providers, for one service provider, or for one account
|
||||
*/
|
||||
static retrieveAll(service_provider_sid, account_sid) {
|
||||
if (!service_provider_sid && !account_sid) return super.retrieveAll();
|
||||
let sql = retrieveSql;
|
||||
const args = [];
|
||||
if (account_sid) {
|
||||
sql = `${sql} WHERE app.account_sid = ?`;
|
||||
args.push(account_sid);
|
||||
}
|
||||
else if (service_provider_sid) {
|
||||
sql = `${sql} WHERE account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
|
||||
args.push(service_provider_sid);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
const sql = account_sid ? listSqlAccount : listSqlSp;
|
||||
const args = account_sid ? [account_sid] : [service_provider_sid];
|
||||
conn.query(sql, args, (err, results, fields) => {
|
||||
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
const r = transmogrifyResults(results);
|
||||
resolve(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -47,16 +78,24 @@ class Application extends Model {
|
||||
* retrieve an application
|
||||
*/
|
||||
static retrieve(sid, service_provider_sid, account_sid) {
|
||||
if (!service_provider_sid && !account_sid) return super.retrieve(sid);
|
||||
const args = [sid];
|
||||
let sql = `${retrieveSql} WHERE app.application_sid = ?`;
|
||||
if (account_sid) {
|
||||
sql = `${sql} AND app.account_sid = ?`;
|
||||
args.push(account_sid);
|
||||
}
|
||||
if (service_provider_sid) {
|
||||
sql = `${sql} AND account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
|
||||
args.push(service_provider_sid);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
const sql = account_sid ? retrieveSqlAccount : retrieveSqlSp;
|
||||
const args = account_sid ? [account_sid, sid] : [service_provider_sid, sid];
|
||||
conn.query(sql, args, (err, results, fields) => {
|
||||
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
const r = transmogrifyResults(results);
|
||||
resolve(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -82,26 +121,14 @@ Application.fields = [
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'call_hook',
|
||||
name: 'call_hook_sid',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'call_status_hook',
|
||||
name: 'call_status_hook_sid',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'hook_basic_auth_user',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'hook_basic_auth_password',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'hook_http_method',
|
||||
type: 'string',
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
const Model = require('./model');
|
||||
|
||||
class Webhook extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
Webhook.table = 'webhooks';
|
||||
Webhook.fields = [
|
||||
{
|
||||
name: 'webhook_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'method',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = Webhook;
|
||||
@@ -1,6 +1,7 @@
|
||||
const router = require('express').Router();
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const Account = require('../../models/account');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('./error');
|
||||
@@ -25,6 +26,16 @@ async function validateAdd(req) {
|
||||
throw new DbErrorBadRequest(`service_provider not found for sid ${req.body.service_provider_sid}`);
|
||||
}
|
||||
}
|
||||
if (req.body.registration_hook && typeof req.body.registration_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'registration_hook\' must be an object when adding an account');
|
||||
}
|
||||
if (req.body.device_calling_hook && typeof req.body.device_calling_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'device_calling_hook\' must be an object when adding an account');
|
||||
}
|
||||
if (req.body.error_hook && typeof req.body.error_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'error_hook\' must be an object when adding an account');
|
||||
}
|
||||
|
||||
}
|
||||
async function validateUpdate(req, sid) {
|
||||
if (req.user.hasAccountAuth && req.user.account_sid !== sid) {
|
||||
@@ -53,7 +64,30 @@ async function validateDelete(req, sid) {
|
||||
}
|
||||
}
|
||||
|
||||
decorate(router, Account, ['add', 'update', 'delete'], preconditions);
|
||||
decorate(router, Account, ['delete'], preconditions);
|
||||
|
||||
/* add */
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateAdd(req);
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['registration_hook', 'device_calling_hook', 'error_hook']) {
|
||||
if (obj[prop]) {
|
||||
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
|
||||
delete obj[prop];
|
||||
}
|
||||
}
|
||||
|
||||
//logger.debug(`Attempting to add account ${JSON.stringify(obj)}`);
|
||||
const uuid = await Account.make(obj);
|
||||
res.status(201).json({sid: uuid});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
@@ -82,4 +116,40 @@ router.get('/:sid', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* update */
|
||||
router.put('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['registration_hook', 'device_calling_hook', 'error_hook']) {
|
||||
if (prop in obj && Object.keys(obj[prop]).length) {
|
||||
if ('webhook_sid' in obj[prop]) {
|
||||
const sid = obj[prop]['webhook_sid'];
|
||||
delete obj[prop]['webhook_sid'];
|
||||
await Webhook.update(sid, obj[prop]);
|
||||
}
|
||||
else {
|
||||
const sid = await Webhook.make(obj[prop]);
|
||||
obj[`${prop}_sid`] = sid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
obj[`${prop}_sid`] = null;
|
||||
}
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
await validateUpdate(req, sid);
|
||||
const rowsAffected = await Account.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -2,6 +2,7 @@ const router = require('express').Router();
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const Application = require('../../models/application');
|
||||
const Account = require('../../models/account');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('./error');
|
||||
const preconditions = {
|
||||
@@ -23,12 +24,24 @@ async function validateAdd(req) {
|
||||
throw new DbErrorBadRequest('insufficient privileges to create an application under the specified account');
|
||||
}
|
||||
}
|
||||
if (req.body.call_hook && typeof req.body.call_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'call_hook\' must be an object when adding an application');
|
||||
}
|
||||
if (req.body.call_status_hook && typeof req.body.call_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'call_status_hook\' must be an object when adding an application');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateUpdate(req, sid) {
|
||||
if (req.user.account_sid && sid !== req.user.account_sid) {
|
||||
throw new DbErrorBadRequest('you may not update or delete an application associated with a different account');
|
||||
}
|
||||
if (req.body.call_hook && typeof req.body.call_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'call_hook\' must be an object when updating an application');
|
||||
}
|
||||
if (req.body.call_status_hook && typeof req.body.call_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'call_status_hook\' must be an object when updating an application');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateDelete(req, sid) {
|
||||
@@ -39,7 +52,29 @@ async function validateDelete(req, sid) {
|
||||
if (assignedPhoneNumbers > 0) throw new DbErrorUnprocessableRequest('cannot delete application with phone numbers');
|
||||
}
|
||||
|
||||
decorate(router, Application, ['add', 'update', 'delete'], preconditions);
|
||||
decorate(router, Application, ['delete'], preconditions);
|
||||
|
||||
/* add */
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateAdd(req);
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['call_hook', 'call_status_hook']) {
|
||||
if (obj[prop]) {
|
||||
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
|
||||
delete obj[prop];
|
||||
}
|
||||
}
|
||||
|
||||
const uuid = await Application.make(obj);
|
||||
res.status(201).json({sid: uuid});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
@@ -69,4 +104,41 @@ router.get('/:sid', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* update */
|
||||
router.put('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateUpdate(req, sid);
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['call_hook', 'call_status_hook']) {
|
||||
if (prop in obj && Object.keys(obj[prop]).length) {
|
||||
if ('webhook_sid' in obj[prop]) {
|
||||
const sid = obj[prop]['webhook_sid'];
|
||||
delete obj[prop]['webhook_sid'];
|
||||
await Webhook.update(sid, obj[prop]);
|
||||
}
|
||||
else {
|
||||
const sid = await Webhook.make(obj[prop]);
|
||||
obj[`${prop}_sid`] = sid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
obj[`${prop}_sid`] = null;
|
||||
}
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
const rowsAffected = await Application.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
+50
-49
@@ -655,20 +655,18 @@ paths:
|
||||
description: sip realm for registration
|
||||
example: sip.mycompany.com
|
||||
registration_hook:
|
||||
type: string
|
||||
format: url
|
||||
$ref: '#/components/Webhook'
|
||||
description: authentication webhook for registration
|
||||
example: https://mycompany.com
|
||||
device_calling_hook:
|
||||
$ref: '#/components/Webhook'
|
||||
description: webhook for inbound call from registered devices
|
||||
error_hook:
|
||||
$ref: '#/components/Webhook'
|
||||
description: webhook for reporting errors from malformed applications
|
||||
service_provider_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
|
||||
hook_basic_auth_user:
|
||||
type: string
|
||||
description: username to use for http basic auth when calling hook
|
||||
hook_basic_auth_password:
|
||||
type: string
|
||||
description: password to use for http basic auth when calling hook
|
||||
required:
|
||||
- name
|
||||
- service_provider_sid
|
||||
@@ -797,10 +795,6 @@ paths:
|
||||
type: string
|
||||
format: url
|
||||
description: url of application to invoke when this device places a call
|
||||
call_status_hook:
|
||||
type: string
|
||||
format: url
|
||||
description: url of application to invoke for call status events on calls placed from this device
|
||||
expires:
|
||||
type: number
|
||||
description: |
|
||||
@@ -922,25 +916,19 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
call_hook:
|
||||
type: string
|
||||
format: url
|
||||
description: webhook to invoke when call is received
|
||||
$ref: '#/components/Webhook'
|
||||
description: authentication webhook for inbound calls from PSTN
|
||||
call_status_hook:
|
||||
$ref: '#/components/Webhook'
|
||||
description: webhook for call status events
|
||||
speech_synthesis_vendor:
|
||||
type: string
|
||||
format: url
|
||||
description: webhook to pass call status updates to
|
||||
hook_basic_auth_user:
|
||||
speech_synthesis_voice:
|
||||
type: string
|
||||
description: username to use for http basic auth when calling hook
|
||||
hook_basic_auth_password:
|
||||
speech_recognizer_vendor:
|
||||
type: string
|
||||
description: password to use for http basic auth when calling hook
|
||||
hook_http_method:
|
||||
speech_recognizer_language:
|
||||
type: string
|
||||
description: whether to use GET or POST
|
||||
enum:
|
||||
- get
|
||||
- post
|
||||
required:
|
||||
- name
|
||||
- account_sid
|
||||
@@ -1243,17 +1231,17 @@ components:
|
||||
sip_realm:
|
||||
type: string
|
||||
registration_hook:
|
||||
type: string
|
||||
format: url
|
||||
$ref: '#/components/Webhook'
|
||||
description: authentication webhook for registration
|
||||
device_calling_hook:
|
||||
$ref: '#/components/Webhook'
|
||||
description: webhook for inbound call from registered devices
|
||||
error_hook:
|
||||
$ref: '#/components/Webhook'
|
||||
description: webhook for reporting errors from malformed applications
|
||||
service_provider_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
hook_basic_auth_user:
|
||||
type: string
|
||||
format: url
|
||||
hook_basic_auth_password:
|
||||
type: string
|
||||
format: url
|
||||
required:
|
||||
- account_sid
|
||||
- name
|
||||
@@ -1270,29 +1258,25 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
call_hook:
|
||||
type: string
|
||||
format: url
|
||||
$ref: '#/components/Webhook'
|
||||
description: authentication webhook for registration
|
||||
call_status_hook:
|
||||
$ref: '#/components/Webhook'
|
||||
description: authentication webhook for registration
|
||||
speech_synthesis_vendor:
|
||||
type: string
|
||||
format: url
|
||||
hook_http_method:
|
||||
speech_synthesis_voice:
|
||||
type: string
|
||||
enum:
|
||||
- get
|
||||
- post
|
||||
default: get
|
||||
hook_basic_auth_user:
|
||||
speech_recognizer_vendor:
|
||||
type: string
|
||||
format: url
|
||||
hook_basic_auth_password:
|
||||
speech_recognizer_language:
|
||||
type: string
|
||||
format: url
|
||||
required:
|
||||
- application_sid
|
||||
- name
|
||||
- account_sid
|
||||
- call_hook
|
||||
- call_status_hook
|
||||
- inbound_hook
|
||||
- inbound_status_hook
|
||||
PhoneNumber:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1362,6 +1346,23 @@ components:
|
||||
- call_sid
|
||||
- application
|
||||
- direction
|
||||
Webhook:
|
||||
type: object
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
format: url
|
||||
method:
|
||||
type: string
|
||||
enum:
|
||||
- get
|
||||
- post
|
||||
username:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
Reference in New Issue
Block a user